From b7b07560ff4919e0a0c13916d4b6ab0f8f64a3e0 Mon Sep 17 00:00:00 2001 From: Steve Larson <9larsons@gmail.com> Date: Mon, 2 Mar 2026 14:53:46 -0600 Subject: [PATCH 1/8] Replace Mocha + c8 with Vitest across all 42 packages - Add vitest 3.1.1 and @vitest/coverage-v8 to root devDependencies - Remove mocha from root devDependencies - Create shared vitest.config.ts with 90% coverage thresholds - Update all package test scripts from mocha+c8 to vitest - Remove c8 from all package devDependencies - Update tsconfig to remove mocha types --- package.json | 5 +- packages/api-framework/package.json | 3 +- packages/bookshelf-collision/package.json | 3 +- packages/bookshelf-custom-query/package.json | 3 +- packages/bookshelf-eager-load/package.json | 3 +- packages/bookshelf-filter/package.json | 3 +- packages/bookshelf-has-posts/package.json | 3 +- packages/bookshelf-include-count/package.json | 3 +- packages/bookshelf-order/package.json | 3 +- packages/bookshelf-pagination/package.json | 3 +- packages/bookshelf-plugins/package.json | 3 +- packages/bookshelf-search/package.json | 3 +- .../bookshelf-transaction-events/package.json | 3 +- packages/config/package.json | 3 +- packages/database-info/package.json | 3 +- packages/debug/package.json | 3 +- packages/domain-events/package.json | 3 +- packages/elasticsearch/package.json | 3 +- packages/email-mock-receiver/package.json | 3 +- packages/errors/package.json | 4 +- packages/express-test/package.json | 3 +- packages/http-cache-utils/package.json | 3 +- packages/http-stream/package.json | 3 +- packages/jest-snapshot/package.json | 3 +- packages/job-manager/package.json | 3 +- packages/logging/package.json | 3 +- packages/metrics/package.json | 3 +- packages/mw-error-handler/package.json | 3 +- packages/mw-vhost/package.json | 3 +- packages/nodemailer/package.json | 3 +- packages/pretty-cli/package.json | 3 +- packages/pretty-stream/package.json | 3 +- packages/prometheus-metrics/package.json | 3 +- packages/promise/package.json | 3 +- packages/request/package.json | 3 +- packages/root-utils/package.json | 3 +- packages/security/package.json | 3 +- packages/server/package.json | 3 +- packages/tpl/package.json | 3 +- packages/tsconfig.json | 3 +- packages/validator/package.json | 3 +- packages/version/package.json | 3 +- packages/webhook-mock-receiver/package.json | 3 +- packages/zip/package.json | 3 +- vitest.config.ts | 20 + yarn.lock | 707 ++++++++++++++++-- 46 files changed, 700 insertions(+), 162 deletions(-) create mode 100644 vitest.config.ts diff --git a/package.json b/package.json index aaac8c945..1285e994e 100644 --- a/package.json +++ b/package.json @@ -29,11 +29,12 @@ }, "devDependencies": { "@nx/js": "22.5.3", + "@vitest/coverage-v8": "3.1.1", "eslint": "8.57.1", "eslint-plugin-ghost": "3.4.4", - "mocha": "11.7.5", "nx": "22.5.3", "sinon": "21.0.1", - "ts-node": "10.9.2" + "ts-node": "10.9.2", + "vitest": "3.1.1" } } diff --git a/packages/api-framework/package.json b/packages/api-framework/package.json index b83429274..22988f70b 100644 --- a/packages/api-framework/package.json +++ b/packages/api-framework/package.json @@ -13,7 +13,7 @@ "main": "index.js", "scripts": { "dev": "echo \"Implement me!\"", - "test:unit": "NODE_ENV=testing c8 --all --reporter text --reporter cobertura --check-coverage --100 -- mocha --reporter dot './test/**/*.test.js'", + "test:unit": "NODE_ENV=testing vitest run --coverage --config ../../vitest.config.ts", "test": "yarn test:unit", "lint:code": "eslint *.js lib/ --ext .js --cache", "lint": "yarn lint:code && yarn lint:test", @@ -24,7 +24,6 @@ "lib" ], "devDependencies": { - "c8": "11.0.0", "mocha": "11.7.5", "sinon": "21.0.1" }, diff --git a/packages/bookshelf-collision/package.json b/packages/bookshelf-collision/package.json index 2a56fd49b..67c8e012f 100644 --- a/packages/bookshelf-collision/package.json +++ b/packages/bookshelf-collision/package.json @@ -11,7 +11,7 @@ "main": "index.js", "scripts": { "dev": "echo \"Implement me!\"", - "test": "NODE_ENV=testing c8 --all --reporter text --reporter cobertura --check-coverage --100 -- mocha './test/**/*.test.js'", + "test": "NODE_ENV=testing vitest run --coverage --config ../../vitest.config.ts", "lint": "eslint . --ext .js --cache", "posttest": "yarn lint" }, @@ -23,7 +23,6 @@ "access": "public" }, "devDependencies": { - "c8": "11.0.0", "mocha": "11.7.5", "sinon": "21.0.1" }, diff --git a/packages/bookshelf-custom-query/package.json b/packages/bookshelf-custom-query/package.json index aa5cf6a66..8da366602 100644 --- a/packages/bookshelf-custom-query/package.json +++ b/packages/bookshelf-custom-query/package.json @@ -11,7 +11,7 @@ "main": "index.js", "scripts": { "dev": "echo \"Implement me!\"", - "test": "NODE_ENV=testing c8 --all --reporter text --reporter cobertura --check-coverage --100 -- mocha './test/**/*.test.js'", + "test": "NODE_ENV=testing vitest run --coverage --config ../../vitest.config.ts", "lint": "eslint . --ext .js --cache", "posttest": "yarn lint" }, @@ -23,7 +23,6 @@ "access": "public" }, "devDependencies": { - "c8": "11.0.0", "mocha": "11.7.5", "sinon": "21.0.1" } diff --git a/packages/bookshelf-eager-load/package.json b/packages/bookshelf-eager-load/package.json index eb5711277..0dcc3a4ac 100644 --- a/packages/bookshelf-eager-load/package.json +++ b/packages/bookshelf-eager-load/package.json @@ -11,7 +11,7 @@ "main": "index.js", "scripts": { "dev": "echo \"Implement me!\"", - "test": "NODE_ENV=testing c8 --all --reporter text --reporter cobertura --check-coverage --100 -- mocha './test/**/*.test.js'", + "test": "NODE_ENV=testing vitest run --coverage --config ../../vitest.config.ts", "lint": "eslint . --ext .js --cache", "posttest": "yarn lint" }, @@ -23,7 +23,6 @@ "access": "public" }, "devDependencies": { - "c8": "11.0.0", "mocha": "11.7.5", "sinon": "21.0.1" }, diff --git a/packages/bookshelf-filter/package.json b/packages/bookshelf-filter/package.json index f8e96299c..2291b2bcb 100644 --- a/packages/bookshelf-filter/package.json +++ b/packages/bookshelf-filter/package.json @@ -11,7 +11,7 @@ "main": "index.js", "scripts": { "dev": "echo \"Implement me!\"", - "test": "NODE_ENV=testing c8 --all --reporter text --reporter cobertura --check-coverage --100 -- mocha './test/**/*.test.js'", + "test": "NODE_ENV=testing vitest run --coverage --config ../../vitest.config.ts", "lint": "eslint . --ext .js --cache", "posttest": "yarn lint" }, @@ -23,7 +23,6 @@ "access": "public" }, "devDependencies": { - "c8": "11.0.0", "mocha": "11.7.5", "sinon": "21.0.1" }, diff --git a/packages/bookshelf-has-posts/package.json b/packages/bookshelf-has-posts/package.json index b0b73859f..39579dcf3 100644 --- a/packages/bookshelf-has-posts/package.json +++ b/packages/bookshelf-has-posts/package.json @@ -11,7 +11,7 @@ "main": "index.js", "scripts": { "dev": "echo \"Implement me!\"", - "test": "NODE_ENV=testing c8 --all --reporter text --reporter cobertura --check-coverage --100 -- mocha './test/**/*.test.js'", + "test": "NODE_ENV=testing vitest run --coverage --config ../../vitest.config.ts", "lint": "eslint . --ext .js --cache", "posttest": "yarn lint" }, @@ -23,7 +23,6 @@ "access": "public" }, "devDependencies": { - "c8": "11.0.0", "mocha": "11.7.5", "sinon": "21.0.1" }, diff --git a/packages/bookshelf-include-count/package.json b/packages/bookshelf-include-count/package.json index 430a4dae9..a1a2360ff 100644 --- a/packages/bookshelf-include-count/package.json +++ b/packages/bookshelf-include-count/package.json @@ -11,7 +11,7 @@ "main": "index.js", "scripts": { "dev": "echo \"Implement me!\"", - "test": "NODE_ENV=testing c8 --all --reporter text --reporter cobertura --check-coverage --100 -- mocha './test/**/*.test.js'", + "test": "NODE_ENV=testing vitest run --coverage --config ../../vitest.config.ts", "lint": "eslint . --ext .js --cache", "posttest": "yarn lint" }, @@ -23,7 +23,6 @@ "access": "public" }, "devDependencies": { - "c8": "11.0.0", "mocha": "11.7.5", "sinon": "21.0.1" }, diff --git a/packages/bookshelf-order/package.json b/packages/bookshelf-order/package.json index 3be7850eb..519febc44 100644 --- a/packages/bookshelf-order/package.json +++ b/packages/bookshelf-order/package.json @@ -11,7 +11,7 @@ "main": "index.js", "scripts": { "dev": "echo \"Implement me!\"", - "test": "NODE_ENV=testing c8 --all --reporter text --reporter cobertura --check-coverage --100 -- mocha './test/**/*.test.js'", + "test": "NODE_ENV=testing vitest run --coverage --config ../../vitest.config.ts", "lint": "eslint . --ext .js --cache", "posttest": "yarn lint" }, @@ -23,7 +23,6 @@ "access": "public" }, "devDependencies": { - "c8": "11.0.0", "mocha": "11.7.5", "sinon": "21.0.1" }, diff --git a/packages/bookshelf-pagination/package.json b/packages/bookshelf-pagination/package.json index d366a0604..efa35727f 100644 --- a/packages/bookshelf-pagination/package.json +++ b/packages/bookshelf-pagination/package.json @@ -11,7 +11,7 @@ "main": "index.js", "scripts": { "dev": "echo \"Implement me!\"", - "test": "NODE_ENV=testing c8 --all --reporter text --reporter cobertura --check-coverage --100 -- mocha './test/**/*.test.js'", + "test": "NODE_ENV=testing vitest run --coverage --config ../../vitest.config.ts", "lint": "eslint . --ext .js --cache", "posttest": "yarn lint" }, @@ -24,7 +24,6 @@ }, "devDependencies": { "mocha": "11.7.5", - "c8": "11.0.0", "sinon": "21.0.1" }, "dependencies": { diff --git a/packages/bookshelf-plugins/package.json b/packages/bookshelf-plugins/package.json index f65bb026f..de8cf974f 100644 --- a/packages/bookshelf-plugins/package.json +++ b/packages/bookshelf-plugins/package.json @@ -11,7 +11,7 @@ "main": "index.js", "scripts": { "dev": "echo \"Implement me!\"", - "test": "NODE_ENV=testing c8 --all --reporter text --reporter cobertura --check-coverage --100 -- mocha './test/**/*.test.js'", + "test": "NODE_ENV=testing vitest run --coverage --config ../../vitest.config.ts", "lint": "eslint . --ext .js --cache", "posttest": "yarn lint" }, @@ -23,7 +23,6 @@ "access": "public" }, "devDependencies": { - "c8": "11.0.0", "mocha": "11.7.5", "sinon": "21.0.1" }, diff --git a/packages/bookshelf-search/package.json b/packages/bookshelf-search/package.json index 7529396e2..79e42a071 100644 --- a/packages/bookshelf-search/package.json +++ b/packages/bookshelf-search/package.json @@ -11,7 +11,7 @@ "main": "index.js", "scripts": { "dev": "echo \"Implement me!\"", - "test": "NODE_ENV=testing c8 --all --reporter text --reporter cobertura --check-coverage --100 -- mocha './test/**/*.test.js'", + "test": "NODE_ENV=testing vitest run --coverage --config ../../vitest.config.ts", "lint": "eslint . --ext .js --cache", "posttest": "yarn lint" }, @@ -23,7 +23,6 @@ "access": "public" }, "devDependencies": { - "c8": "11.0.0", "mocha": "11.7.5", "sinon": "21.0.1" } diff --git a/packages/bookshelf-transaction-events/package.json b/packages/bookshelf-transaction-events/package.json index 7eaabeab0..0fd66d09d 100644 --- a/packages/bookshelf-transaction-events/package.json +++ b/packages/bookshelf-transaction-events/package.json @@ -11,7 +11,7 @@ "main": "index.js", "scripts": { "dev": "echo \"Implement me!\"", - "test": "NODE_ENV=testing c8 --all --reporter text --reporter cobertura --check-coverage --100 -- mocha './test/**/*.test.js'", + "test": "NODE_ENV=testing vitest run --coverage --config ../../vitest.config.ts", "lint": "eslint . --ext .js --cache", "posttest": "yarn lint" }, @@ -23,7 +23,6 @@ "access": "public" }, "devDependencies": { - "c8": "11.0.0", "mocha": "11.7.5", "sinon": "21.0.1" } diff --git a/packages/config/package.json b/packages/config/package.json index 2cbb94d1e..f008fa7de 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -11,7 +11,7 @@ "main": "index.js", "scripts": { "dev": "echo \"Implement me!\"", - "test": "NODE_ENV=testing c8 --all --check-coverage --100 --reporter text --reporter cobertura mocha './test/**/*.test.js'", + "test": "NODE_ENV=testing vitest run --coverage --config ../../vitest.config.ts", "lint": "eslint . --ext .js --cache", "posttest": "yarn lint" }, @@ -23,7 +23,6 @@ "access": "public" }, "devDependencies": { - "c8": "11.0.0", "mocha": "11.7.5" }, "dependencies": { diff --git a/packages/database-info/package.json b/packages/database-info/package.json index 31088838a..4feefe32b 100644 --- a/packages/database-info/package.json +++ b/packages/database-info/package.json @@ -11,7 +11,7 @@ "main": "index.js", "scripts": { "dev": "echo \"Implement me!\"", - "test": "NODE_ENV=testing c8 --all --check-coverage --100 --reporter text --reporter cobertura mocha './test/**/*.test.js'", + "test": "NODE_ENV=testing vitest run --coverage --config ../../vitest.config.ts", "lint": "eslint . --ext .js --cache", "posttest": "yarn lint" }, @@ -23,7 +23,6 @@ "access": "public" }, "devDependencies": { - "c8": "11.0.0", "knex": "3.1.0", "mocha": "11.7.5" } diff --git a/packages/debug/package.json b/packages/debug/package.json index 8096509c1..97a9ec97c 100644 --- a/packages/debug/package.json +++ b/packages/debug/package.json @@ -11,7 +11,7 @@ "main": "index.js", "scripts": { "dev": "echo \"Implement me!\"", - "test": "NODE_ENV=testing c8 --all --check-coverage --100 --reporter text --reporter cobertura mocha './test/**/*.test.js'", + "test": "NODE_ENV=testing vitest run --coverage --config ../../vitest.config.ts", "lint": "eslint . --ext .js --cache", "posttest": "yarn lint" }, @@ -23,7 +23,6 @@ "access": "public" }, "devDependencies": { - "c8": "11.0.0", "mocha": "11.7.5" }, "dependencies": { diff --git a/packages/domain-events/package.json b/packages/domain-events/package.json index f08faf7bf..1fc21b54a 100644 --- a/packages/domain-events/package.json +++ b/packages/domain-events/package.json @@ -10,7 +10,7 @@ "types": "types", "scripts": { "dev": "echo \"Implement me!\"", - "test:unit": "NODE_ENV=testing c8 --all --reporter text --reporter cobertura --check-coverage --100 -- mocha --reporter dot './test/**/*.test.js'", + "test:unit": "NODE_ENV=testing vitest run --coverage --config ../../vitest.config.ts", "test": "yarn test:unit", "lint:code": "eslint *.js lib/ --ext .js --cache", "lint": "yarn lint:code && yarn lint:test", @@ -22,7 +22,6 @@ ], "devDependencies": { "@tryghost/logging": "^4.0.0", - "c8": "11.0.0", "mocha": "11.7.5" }, "repository": { diff --git a/packages/elasticsearch/package.json b/packages/elasticsearch/package.json index 2791c2bcb..c3dc40a91 100644 --- a/packages/elasticsearch/package.json +++ b/packages/elasticsearch/package.json @@ -11,7 +11,7 @@ "main": "index.js", "scripts": { "dev": "echo \"Implement me!\"", - "test": "NODE_ENV=testing c8 --all --check-coverage --100 --reporter text --reporter cobertura mocha './test/**/*.test.js'", + "test": "NODE_ENV=testing vitest run --coverage --config ../../vitest.config.ts", "lint": "eslint . --ext .js --cache", "posttest": "yarn lint" }, @@ -23,7 +23,6 @@ "access": "public" }, "devDependencies": { - "c8": "11.0.0", "mocha": "11.7.5", "sinon": "21.0.1" }, diff --git a/packages/email-mock-receiver/package.json b/packages/email-mock-receiver/package.json index e15f72376..33aa71061 100644 --- a/packages/email-mock-receiver/package.json +++ b/packages/email-mock-receiver/package.json @@ -11,7 +11,7 @@ "main": "index.js", "scripts": { "dev": "echo \"Implement me!\"", - "test:unit": "NODE_ENV=testing c8 --all --check-coverage --100 --reporter text --reporter cobertura mocha './test/**/*.test.js'", + "test:unit": "NODE_ENV=testing vitest run --coverage --config ../../vitest.config.ts", "test": "yarn test:unit", "lint:code": "eslint *.js lib/ --ext .js --cache", "lint": "yarn lint:code && yarn lint:test", @@ -25,7 +25,6 @@ "access": "public" }, "devDependencies": { - "c8": "11.0.0", "mocha": "11.7.5", "sinon": "21.0.1" } diff --git a/packages/errors/package.json b/packages/errors/package.json index 88e2bacc6..4f8f24120 100644 --- a/packages/errors/package.json +++ b/packages/errors/package.json @@ -21,7 +21,7 @@ "build:cjs": "esbuild src/*.ts --target=es2020 --outdir=cjs --format=cjs", "build:es": "esbuild src/*.ts --target=es2020 --outdir=es --format=esm", "build:types": "tsc --emitDeclarationOnly --declaration --declarationMap --outDir types", - "test": "TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\" }' NODE_ENV=testing c8 --check-coverage --100 --all -n src --reporter text --reporter cobertura mocha -r ts-node/register './test/**/*.test.ts'", + "test": "NODE_ENV=testing vitest run --coverage", "lint": "eslint . --ext .js --cache", "posttest": "yarn lint" }, @@ -36,8 +36,6 @@ }, "devDependencies": { "@types/lodash": "4.17.24", - "@types/mocha": "10.0.10", - "c8": "11.0.0", "esbuild": "0.27.3", "lodash": "4.17.23", "mocha": "11.7.5", diff --git a/packages/express-test/package.json b/packages/express-test/package.json index 7650710c8..d8ecfba0c 100644 --- a/packages/express-test/package.json +++ b/packages/express-test/package.json @@ -11,7 +11,7 @@ "main": "index.js", "scripts": { "dev": "echo \"Implement me!\"", - "test": "NODE_ENV=testing c8 --check-coverage mocha --require ./test/utils/overrides.js './test/**/*.test.js'", + "test": "NODE_ENV=testing vitest run --coverage", "lint": "eslint . --ext .js --cache", "posttest": "yarn lint" }, @@ -23,7 +23,6 @@ "access": "public" }, "devDependencies": { - "c8": "11.0.0", "express": "5.2.1", "express-session": "1.19.0", "mocha": "11.7.5", diff --git a/packages/http-cache-utils/package.json b/packages/http-cache-utils/package.json index fa9cf48c8..57082c849 100644 --- a/packages/http-cache-utils/package.json +++ b/packages/http-cache-utils/package.json @@ -11,7 +11,7 @@ "main": "index.js", "scripts": { "dev": "echo \"Implement me!\"", - "test:unit": "NODE_ENV=testing c8 --all --check-coverage --reporter text --reporter cobertura mocha './test/**/*.test.js'", + "test:unit": "NODE_ENV=testing vitest run --coverage", "test": "yarn test:unit", "lint:code": "eslint *.js lib/ --ext .js --cache", "lint": "yarn lint:code && yarn lint:test", @@ -25,7 +25,6 @@ "access": "public" }, "devDependencies": { - "c8": "11.0.0", "mocha": "11.7.5", "sinon": "21.0.1" } diff --git a/packages/http-stream/package.json b/packages/http-stream/package.json index 1248160b7..56f7f86c8 100644 --- a/packages/http-stream/package.json +++ b/packages/http-stream/package.json @@ -11,7 +11,7 @@ "main": "index.js", "scripts": { "dev": "echo \"Implement me!\"", - "test": "NODE_ENV=testing c8 --all --check-coverage --100 --reporter text --reporter cobertura mocha './test/**/*.test.js'", + "test": "NODE_ENV=testing vitest run --coverage --config ../../vitest.config.ts", "lint:code": "eslint *.js lib/ --ext .js --cache", "lint": "yarn lint:code && yarn lint:test", "lint:test": "eslint -c test/.eslintrc.js test/ --ext .js --cache", @@ -25,7 +25,6 @@ "access": "public" }, "devDependencies": { - "c8": "11.0.0", "express": "5.2.1", "mocha": "11.7.5", "sinon": "21.0.1" diff --git a/packages/jest-snapshot/package.json b/packages/jest-snapshot/package.json index 6b732357c..7a7d653b1 100644 --- a/packages/jest-snapshot/package.json +++ b/packages/jest-snapshot/package.json @@ -11,7 +11,7 @@ "main": "index.js", "scripts": { "dev": "echo \"Implement me!\"", - "test": "NODE_ENV=testing c8 --all --check-coverage --100 --reporter text --reporter cobertura --reporter html-spa mocha './test/**/*.test.js'", + "test": "NODE_ENV=testing vitest run --coverage --config ../../vitest.config.ts", "lint": "eslint . --ext .js --cache", "posttest": "yarn lint" }, @@ -23,7 +23,6 @@ "access": "public" }, "devDependencies": { - "c8": "11.0.0", "mocha": "11.7.5", "sinon": "21.0.1" }, diff --git a/packages/job-manager/package.json b/packages/job-manager/package.json index d5da327bd..bcaf88cfb 100644 --- a/packages/job-manager/package.json +++ b/packages/job-manager/package.json @@ -9,7 +9,7 @@ "main": "index.js", "scripts": { "dev": "echo \"Implement me!\"", - "test:unit": "NODE_ENV=testing c8 --all --check-coverage --100 --reporter text --reporter cobertura -- mocha --reporter dot './test/**/*.test.js'", + "test:unit": "NODE_ENV=testing vitest run --coverage", "test": "yarn test:unit", "lint:code": "eslint *.js lib/ --ext .js --cache", "lint": "yarn lint:code && yarn lint:test", @@ -21,7 +21,6 @@ ], "devDependencies": { "@sinonjs/fake-timers": "15.1.0", - "c8": "11.0.0", "date-fns": "4.1.0", "mocha": "11.7.5", "rewire": "9.0.1", diff --git a/packages/logging/package.json b/packages/logging/package.json index 0f24f98d7..ef7fdc521 100644 --- a/packages/logging/package.json +++ b/packages/logging/package.json @@ -11,7 +11,7 @@ "main": "index.js", "scripts": { "dev": "echo \"Implement me!\"", - "test": "NODE_ENV=testing c8 --all --check-coverage --100 --reporter text --reporter cobertura mocha --exit './test/**/*.test.js'", + "test": "NODE_ENV=testing vitest run --coverage --config ../../vitest.config.ts", "lint": "eslint . --ext .js --cache", "posttest": "yarn lint" }, @@ -24,7 +24,6 @@ }, "devDependencies": { "@tryghost/errors": "1.0.0", - "c8": "11.0.0", "mocha": "11.7.5", "sinon": "21.0.1" }, diff --git a/packages/metrics/package.json b/packages/metrics/package.json index d129d4541..a76c418c0 100644 --- a/packages/metrics/package.json +++ b/packages/metrics/package.json @@ -11,7 +11,7 @@ "main": "index.js", "scripts": { "dev": "echo \"Implement me!\"", - "test": "NODE_ENV=testing c8 --all --check-coverage --100 --reporter text --reporter cobertura mocha './test/**/*.test.js'", + "test": "NODE_ENV=testing vitest run --coverage", "lint": "eslint . --ext .js --cache", "posttest": "yarn lint" }, @@ -23,7 +23,6 @@ "access": "public" }, "devDependencies": { - "c8": "11.0.0", "mocha": "11.7.5", "rewire": "9.0.1", "sinon": "21.0.1" diff --git a/packages/mw-error-handler/package.json b/packages/mw-error-handler/package.json index d70c5daa7..a1392eba2 100644 --- a/packages/mw-error-handler/package.json +++ b/packages/mw-error-handler/package.json @@ -10,7 +10,7 @@ }, "scripts": { "dev": "echo \"Implement me!\"", - "test:unit": "NODE_ENV=testing c8 --all --check-coverage --100 --reporter text --reporter cobertura -- mocha --reporter dot './test/**/*.test.js'", + "test:unit": "NODE_ENV=testing vitest run --coverage --config ../../vitest.config.ts", "test": "yarn test:unit", "lint:code": "eslint *.js lib/ --ext .js --cache", "lint": "yarn lint:code && yarn lint:test", @@ -21,7 +21,6 @@ "lib" ], "devDependencies": { - "c8": "11.0.0", "mocha": "11.7.5", "sinon": "21.0.1" }, diff --git a/packages/mw-vhost/package.json b/packages/mw-vhost/package.json index de75417d0..90a484ab9 100644 --- a/packages/mw-vhost/package.json +++ b/packages/mw-vhost/package.json @@ -9,7 +9,7 @@ }, "scripts": { "dev": "echo \"Implement me!\"", - "test:unit": "NODE_ENV=testing c8 --all --check-coverage --100 --reporter text --reporter cobertura -- mocha --reporter dot './test/**/*.test.js'", + "test:unit": "NODE_ENV=testing vitest run --coverage --config ../../vitest.config.ts", "test": "yarn test:unit", "lint:code": "eslint *.js lib/ --ext .js --cache", "lint": "yarn lint:code && yarn lint:test", @@ -20,7 +20,6 @@ "lib" ], "devDependencies": { - "c8": "11.0.0", "mocha": "11.7.5", "supertest": "7.2.2" }, diff --git a/packages/nodemailer/package.json b/packages/nodemailer/package.json index db81f9c71..28fa03843 100644 --- a/packages/nodemailer/package.json +++ b/packages/nodemailer/package.json @@ -11,7 +11,7 @@ "main": "index.js", "scripts": { "dev": "echo \"Implement me!\"", - "test": "NODE_ENV=testing c8 --all --check-coverage --100 --reporter text --reporter cobertura mocha './test/**/*.test.js'", + "test": "NODE_ENV=testing vitest run --coverage --config ../../vitest.config.ts", "lint": "eslint . --ext .js --cache", "posttest": "yarn lint" }, @@ -23,7 +23,6 @@ "access": "public" }, "devDependencies": { - "c8": "11.0.0", "mocha": "11.7.5", "sinon": "21.0.1" }, diff --git a/packages/pretty-cli/package.json b/packages/pretty-cli/package.json index 8894f026e..2c35444d1 100644 --- a/packages/pretty-cli/package.json +++ b/packages/pretty-cli/package.json @@ -12,7 +12,7 @@ "main": "index.js", "scripts": { "dev": "echo \"Implement me!\"", - "test": "NODE_ENV=testing c8 --all --check-coverage --100 --reporter text --reporter cobertura mocha './test/**/*.test.js'", + "test": "NODE_ENV=testing vitest run --coverage --config ../../vitest.config.ts", "lint": "eslint . --ext .js --cache", "posttest": "yarn lint" }, @@ -24,7 +24,6 @@ "access": "public" }, "devDependencies": { - "c8": "11.0.0", "mocha": "11.7.5", "sinon": "21.0.1" }, diff --git a/packages/pretty-stream/package.json b/packages/pretty-stream/package.json index 357f5fee4..249f1b4b1 100644 --- a/packages/pretty-stream/package.json +++ b/packages/pretty-stream/package.json @@ -11,7 +11,7 @@ "main": "index.js", "scripts": { "dev": "echo \"Implement me!\"", - "test": "NODE_ENV=testing c8 --all --check-coverage --100 --reporter text --reporter cobertura mocha './test/**/*.test.js'", + "test": "NODE_ENV=testing vitest run --coverage --config ../../vitest.config.ts", "lint": "eslint . --ext .js --cache", "posttest": "yarn lint" }, @@ -23,7 +23,6 @@ "access": "public" }, "devDependencies": { - "c8": "11.0.0", "mocha": "11.7.5", "sinon": "21.0.1" }, diff --git a/packages/prometheus-metrics/package.json b/packages/prometheus-metrics/package.json index bad888703..1cab13c3a 100644 --- a/packages/prometheus-metrics/package.json +++ b/packages/prometheus-metrics/package.json @@ -17,7 +17,7 @@ "build": "yarn build:ts", "build:ts": "tsc", "prepare": "tsc", - "test:unit": "NODE_ENV=testing c8 --src src --all --check-coverage --100 --reporter text --reporter cobertura mocha -r ts-node/register './test/**/*.test.ts'", + "test:unit": "NODE_ENV=testing vitest run --coverage", "test": "yarn test:types && yarn test:unit", "test:types": "tsc --noEmit", "lint:code": "eslint src/ --ext .ts --cache", @@ -32,7 +32,6 @@ "@types/sinon": "21.0.0", "@types/stoppable": "1.1.3", "@types/supertest": "7.2.0", - "c8": "11.0.0", "knex": "3.1.0", "mocha": "11.7.5", "nock": "14.0.11", diff --git a/packages/promise/package.json b/packages/promise/package.json index ea9e756a7..987bfee26 100644 --- a/packages/promise/package.json +++ b/packages/promise/package.json @@ -11,7 +11,7 @@ "main": "index.js", "scripts": { "dev": "echo \"Implement me!\"", - "test": "NODE_ENV=testing c8 --all --check-coverage --100 --reporter text --reporter cobertura mocha './test/**/*.test.js'", + "test": "NODE_ENV=testing vitest run --coverage --config ../../vitest.config.ts", "lint": "eslint . --ext .js --cache", "posttest": "yarn lint" }, @@ -23,7 +23,6 @@ "access": "public" }, "devDependencies": { - "c8": "11.0.0", "mocha": "11.7.5", "sinon": "21.0.1" } diff --git a/packages/request/package.json b/packages/request/package.json index d11a856d5..a4b1871e6 100644 --- a/packages/request/package.json +++ b/packages/request/package.json @@ -11,7 +11,7 @@ "main": "index.js", "scripts": { "dev": "echo \"Implement me!\"", - "test": "NODE_ENV=testing c8 --all --check-coverage --100 --reporter text --reporter cobertura mocha './test/**/*.test.js'", + "test": "NODE_ENV=testing vitest run --coverage --config ../../vitest.config.ts", "lint": "eslint . --ext .js --cache", "posttest": "yarn lint" }, @@ -23,7 +23,6 @@ "access": "public" }, "devDependencies": { - "c8": "11.0.0", "mocha": "11.7.5", "nock": "14.0.11", "rewire": "9.0.1", diff --git a/packages/root-utils/package.json b/packages/root-utils/package.json index 1535a2bc1..76ecace88 100644 --- a/packages/root-utils/package.json +++ b/packages/root-utils/package.json @@ -11,7 +11,7 @@ "main": "index.js", "scripts": { "dev": "echo \"Implement me!\"", - "test": "NODE_ENV=testing c8 --all --check-coverage --100 --reporter text --reporter cobertura mocha './test/**/*.test.js'", + "test": "NODE_ENV=testing vitest run --coverage --config ../../vitest.config.ts", "lint": "eslint . --ext .js --cache", "posttest": "yarn lint" }, @@ -23,7 +23,6 @@ "access": "public" }, "devDependencies": { - "c8": "11.0.0", "mocha": "11.7.5", "sinon": "21.0.1" }, diff --git a/packages/security/package.json b/packages/security/package.json index 1ec83cb0f..4cf194124 100644 --- a/packages/security/package.json +++ b/packages/security/package.json @@ -15,7 +15,7 @@ }, "scripts": { "dev": "echo \"Implement me!\"", - "test:unit": "NODE_ENV=testing c8 --all --check-coverage --100 --reporter text --reporter cobertura -- mocha --reporter dot './test/**/*.test.js'", + "test:unit": "NODE_ENV=testing vitest run --coverage --config ../../vitest.config.ts", "test": "yarn test:unit", "lint:code": "eslint *.js lib/ --ext .js --cache", "lint": "yarn lint:code && yarn lint:test", @@ -26,7 +26,6 @@ "lib" ], "devDependencies": { - "c8": "11.0.0", "mocha": "11.7.5", "sinon": "21.0.1", "uuid": "13.0.0" diff --git a/packages/server/package.json b/packages/server/package.json index ccd6bbbb4..0b05364bd 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -11,7 +11,7 @@ "main": "index.js", "scripts": { "dev": "echo \"Implement me!\"", - "test": "NODE_ENV=testing c8 --all --check-coverage --100 --reporter text --reporter cobertura mocha './test/**/*.test.js'", + "test": "NODE_ENV=testing vitest run --coverage --config ../../vitest.config.ts", "lint": "eslint . --ext .js --cache", "posttest": "yarn lint" }, @@ -23,7 +23,6 @@ "access": "public" }, "devDependencies": { - "c8": "11.0.0", "mocha": "11.7.5", "sinon": "21.0.1" }, diff --git a/packages/tpl/package.json b/packages/tpl/package.json index b9394ae76..a33adc239 100644 --- a/packages/tpl/package.json +++ b/packages/tpl/package.json @@ -11,7 +11,7 @@ "main": "index.js", "scripts": { "dev": "echo \"Implement me!\"", - "test": "NODE_ENV=testing c8 --all --check-coverage --100 --reporter text --reporter cobertura mocha './test/**/*.test.js'", + "test": "NODE_ENV=testing vitest run --coverage --config ../../vitest.config.ts", "lint": "eslint . --ext .js --cache", "posttest": "yarn lint" }, @@ -23,7 +23,6 @@ "access": "public" }, "devDependencies": { - "c8": "11.0.0", "mocha": "11.7.5", "sinon": "21.0.1" }, diff --git a/packages/tsconfig.json b/packages/tsconfig.json index aaeef1036..f115f241e 100644 --- a/packages/tsconfig.json +++ b/packages/tsconfig.json @@ -36,8 +36,7 @@ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ "types": [ - "mocha", - "node", + "node" ], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ diff --git a/packages/validator/package.json b/packages/validator/package.json index dfb685aaa..0b3df0639 100644 --- a/packages/validator/package.json +++ b/packages/validator/package.json @@ -11,7 +11,7 @@ "main": "index.js", "scripts": { "dev": "echo \"Implement me!\"", - "test": "NODE_ENV=testing c8 --all --check-coverage --100 --reporter text --reporter cobertura mocha './test/**/*.test.js'", + "test": "NODE_ENV=testing vitest run --coverage --config ../../vitest.config.ts", "lint": "eslint . --ext .js --cache", "posttest": "yarn lint" }, @@ -23,7 +23,6 @@ "access": "public" }, "devDependencies": { - "c8": "11.0.0", "mocha": "11.7.5" }, "dependencies": { diff --git a/packages/version/package.json b/packages/version/package.json index bbc28c2c7..6ae723636 100644 --- a/packages/version/package.json +++ b/packages/version/package.json @@ -11,7 +11,7 @@ "main": "index.js", "scripts": { "dev": "echo \"Implement me!\"", - "test": "NODE_ENV=testing c8 --all --check-coverage --100 --reporter text --reporter cobertura mocha './test/**/*.test.js'", + "test": "NODE_ENV=testing vitest run --coverage --config ../../vitest.config.ts", "lint": "eslint . --ext .js --cache", "posttest": "yarn lint" }, @@ -23,7 +23,6 @@ "access": "public" }, "devDependencies": { - "c8": "11.0.0", "mocha": "11.7.5", "sinon": "21.0.1" }, diff --git a/packages/webhook-mock-receiver/package.json b/packages/webhook-mock-receiver/package.json index 08cb4dfaa..7012b514e 100644 --- a/packages/webhook-mock-receiver/package.json +++ b/packages/webhook-mock-receiver/package.json @@ -11,7 +11,7 @@ "main": "index.js", "scripts": { "dev": "echo \"Implement me!\"", - "test": "NODE_ENV=testing c8 --all --check-coverage --reporter text --reporter cobertura mocha './test/**/*.test.js'", + "test": "NODE_ENV=testing vitest run --coverage", "lint:code": "eslint *.js lib/ --ext .js --cache", "lint": "yarn lint:code && yarn lint:test", "lint:test": "eslint -c test/.eslintrc.js test/ --ext .js --cache", @@ -25,7 +25,6 @@ "access": "public" }, "devDependencies": { - "c8": "11.0.0", "got": "14.6.6", "mocha": "11.7.5", "sinon": "21.0.1" diff --git a/packages/zip/package.json b/packages/zip/package.json index ba5eed56f..222cf899f 100644 --- a/packages/zip/package.json +++ b/packages/zip/package.json @@ -11,7 +11,7 @@ "main": "index.js", "scripts": { "dev": "echo \"Implement me!\"", - "test": "NODE_ENV=testing c8 --all --check-coverage --100 --reporter text --reporter cobertura mocha './test/**/*.test.js'", + "test": "NODE_ENV=testing vitest run --coverage --config ../../vitest.config.ts", "lint": "eslint . --ext .js --cache", "posttest": "yarn lint" }, @@ -23,7 +23,6 @@ "access": "public" }, "devDependencies": { - "c8": "11.0.0", "folder-hash": "4.1.1", "mocha": "11.7.5", "sinon": "21.0.1" diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 000000000..06915f140 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,20 @@ +import {defineConfig} from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + include: ['test/**/*.test.{js,ts}'], + coverage: { + provider: 'v8', + all: true, + reporter: ['text', 'cobertura'], + thresholds: { + lines: 90, + functions: 90, + branches: 90, + statements: 90 + } + } + } +}); diff --git a/yarn.lock b/yarn.lock index 29c4c3b7b..74a28b685 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,14 @@ # yarn lockfile v1 +"@ampproject/remapping@^2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" + integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + "@aws-crypto/sha256-browser@5.2.0": version "5.2.0" resolved "https://registry.yarnpkg.com/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz#153895ef1dba6f9fce38af550e0ef58988eb649e" @@ -640,7 +648,7 @@ "@babel/template" "^7.28.6" "@babel/types" "^7.28.6" -"@babel/parser@^7.23.9", "@babel/parser@^7.28.6", "@babel/parser@^7.29.0": +"@babel/parser@^7.23.9", "@babel/parser@^7.25.4", "@babel/parser@^7.28.6", "@babel/parser@^7.29.0": version "7.29.0" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.0.tgz#669ef345add7d057e92b7ed15f0bac07611831b6" integrity sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww== @@ -1384,7 +1392,7 @@ "@babel/types" "^7.29.0" debug "^4.3.1" -"@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.5", "@babel/types@^7.28.6", "@babel/types@^7.29.0", "@babel/types@^7.4.4": +"@babel/types@^7.25.4", "@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.5", "@babel/types@^7.28.6", "@babel/types@^7.29.0", "@babel/types@^7.4.4": version "7.29.0" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.0.tgz#9f5b1e838c446e72cf3cd4b918152b8c605e37c7" integrity sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A== @@ -1392,7 +1400,7 @@ "@babel/helper-string-parser" "^7.27.1" "@babel/helper-validator-identifier" "^7.28.5" -"@bcoe/v8-coverage@^1.0.1": +"@bcoe/v8-coverage@^1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz#bbe12dca5b4ef983a0d0af4b07b9bc90ea0ababa" integrity sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA== @@ -1459,131 +1467,261 @@ dependencies: tslib "^2.4.0" +"@esbuild/aix-ppc64@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz#80fcbe36130e58b7670511e888b8e88a259ed76c" + integrity sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA== + "@esbuild/aix-ppc64@0.27.3": version "0.27.3" resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz#815b39267f9bffd3407ea6c376ac32946e24f8d2" integrity sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg== +"@esbuild/android-arm64@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz#8aa4965f8d0a7982dc21734bf6601323a66da752" + integrity sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg== + "@esbuild/android-arm64@0.27.3": version "0.27.3" resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz#19b882408829ad8e12b10aff2840711b2da361e8" integrity sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg== +"@esbuild/android-arm@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.25.12.tgz#300712101f7f50f1d2627a162e6e09b109b6767a" + integrity sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg== + "@esbuild/android-arm@0.27.3": version "0.27.3" resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.27.3.tgz#90be58de27915efa27b767fcbdb37a4470627d7b" integrity sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA== +"@esbuild/android-x64@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.25.12.tgz#87dfb27161202bdc958ef48bb61b09c758faee16" + integrity sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg== + "@esbuild/android-x64@0.27.3": version "0.27.3" resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.27.3.tgz#d7dcc976f16e01a9aaa2f9b938fbec7389f895ac" integrity sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ== +"@esbuild/darwin-arm64@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz#79197898ec1ff745d21c071e1c7cc3c802f0c1fd" + integrity sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg== + "@esbuild/darwin-arm64@0.27.3": version "0.27.3" resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz#9f6cac72b3a8532298a6a4493ed639a8988e8abd" integrity sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg== +"@esbuild/darwin-x64@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz#146400a8562133f45c4d2eadcf37ddd09718079e" + integrity sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA== + "@esbuild/darwin-x64@0.27.3": version "0.27.3" resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz#ac61d645faa37fd650340f1866b0812e1fb14d6a" integrity sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg== +"@esbuild/freebsd-arm64@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz#1c5f9ba7206e158fd2b24c59fa2d2c8bb47ca0fe" + integrity sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg== + "@esbuild/freebsd-arm64@0.27.3": version "0.27.3" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz#b8625689d73cf1830fe58c39051acdc12474ea1b" integrity sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w== +"@esbuild/freebsd-x64@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz#ea631f4a36beaac4b9279fa0fcc6ca29eaeeb2b3" + integrity sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ== + "@esbuild/freebsd-x64@0.27.3": version "0.27.3" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz#07be7dd3c9d42fe0eccd2ab9f9ded780bc53bead" integrity sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA== +"@esbuild/linux-arm64@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz#e1066bce58394f1b1141deec8557a5f0a22f5977" + integrity sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ== + "@esbuild/linux-arm64@0.27.3": version "0.27.3" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz#bf31918fe5c798586460d2b3d6c46ed2c01ca0b6" integrity sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg== +"@esbuild/linux-arm@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz#452cd66b20932d08bdc53a8b61c0e30baf4348b9" + integrity sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw== + "@esbuild/linux-arm@0.27.3": version "0.27.3" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz#28493ee46abec1dc3f500223cd9f8d2df08f9d11" integrity sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw== +"@esbuild/linux-ia32@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz#b24f8acc45bcf54192c7f2f3be1b53e6551eafe0" + integrity sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA== + "@esbuild/linux-ia32@0.27.3": version "0.27.3" resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz#750752a8b30b43647402561eea764d0a41d0ee29" integrity sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg== +"@esbuild/linux-loong64@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz#f9cfffa7fc8322571fbc4c8b3268caf15bd81ad0" + integrity sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng== + "@esbuild/linux-loong64@0.27.3": version "0.27.3" resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz#a5a92813a04e71198c50f05adfaf18fc1e95b9ed" integrity sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA== +"@esbuild/linux-mips64el@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz#575a14bd74644ffab891adc7d7e60d275296f2cd" + integrity sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw== + "@esbuild/linux-mips64el@0.27.3": version "0.27.3" resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz#deb45d7fd2d2161eadf1fbc593637ed766d50bb1" integrity sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw== +"@esbuild/linux-ppc64@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz#75b99c70a95fbd5f7739d7692befe60601591869" + integrity sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA== + "@esbuild/linux-ppc64@0.27.3": version "0.27.3" resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz#6f39ae0b8c4d3d2d61a65b26df79f6e12a1c3d78" integrity sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA== +"@esbuild/linux-riscv64@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz#2e3259440321a44e79ddf7535c325057da875cd6" + integrity sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w== + "@esbuild/linux-riscv64@0.27.3": version "0.27.3" resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz#4c5c19c3916612ec8e3915187030b9df0b955c1d" integrity sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ== +"@esbuild/linux-s390x@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz#17676cabbfe5928da5b2a0d6df5d58cd08db2663" + integrity sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg== + "@esbuild/linux-s390x@0.27.3": version "0.27.3" resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz#9ed17b3198fa08ad5ccaa9e74f6c0aff7ad0156d" integrity sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw== +"@esbuild/linux-x64@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz#0583775685ca82066d04c3507f09524d3cd7a306" + integrity sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw== + "@esbuild/linux-x64@0.27.3": version "0.27.3" resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz#12383dcbf71b7cf6513e58b4b08d95a710bf52a5" integrity sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA== +"@esbuild/netbsd-arm64@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz#f04c4049cb2e252fe96b16fed90f70746b13f4a4" + integrity sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg== + "@esbuild/netbsd-arm64@0.27.3": version "0.27.3" resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz#dd0cb2fa543205fcd931df44f4786bfcce6df7d7" integrity sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA== +"@esbuild/netbsd-x64@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz#77da0d0a0d826d7c921eea3d40292548b258a076" + integrity sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ== + "@esbuild/netbsd-x64@0.27.3": version "0.27.3" resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz#028ad1807a8e03e155153b2d025b506c3787354b" integrity sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA== +"@esbuild/openbsd-arm64@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz#6296f5867aedef28a81b22ab2009c786a952dccd" + integrity sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A== + "@esbuild/openbsd-arm64@0.27.3": version "0.27.3" resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz#e3c16ff3490c9b59b969fffca87f350ffc0e2af5" integrity sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw== +"@esbuild/openbsd-x64@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz#f8d23303360e27b16cf065b23bbff43c14142679" + integrity sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw== + "@esbuild/openbsd-x64@0.27.3": version "0.27.3" resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz#c5a4693fcb03d1cbecbf8b422422468dfc0d2a8b" integrity sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ== +"@esbuild/openharmony-arm64@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz#49e0b768744a3924be0d7fd97dd6ce9b2923d88d" + integrity sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg== + "@esbuild/openharmony-arm64@0.27.3": version "0.27.3" resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz#082082444f12db564a0775a41e1991c0e125055e" integrity sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g== +"@esbuild/sunos-x64@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz#a6ed7d6778d67e528c81fb165b23f4911b9b13d6" + integrity sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w== + "@esbuild/sunos-x64@0.27.3": version "0.27.3" resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz#5ab036c53f929e8405c4e96e865a424160a1b537" integrity sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA== +"@esbuild/win32-arm64@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz#9ac14c378e1b653af17d08e7d3ce34caef587323" + integrity sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg== + "@esbuild/win32-arm64@0.27.3": version "0.27.3" resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz#38de700ef4b960a0045370c171794526e589862e" integrity sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA== +"@esbuild/win32-ia32@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz#918942dcbbb35cc14fca39afb91b5e6a3d127267" + integrity sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ== + "@esbuild/win32-ia32@0.27.3": version "0.27.3" resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz#451b93dc03ec5d4f38619e6cd64d9f9eff06f55c" integrity sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q== +"@esbuild/win32-x64@0.25.12": + version "0.25.12" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz#9bdad8176be7811ad148d1f8772359041f46c6c5" + integrity sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA== + "@esbuild/win32-x64@0.27.3": version "0.27.3" resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz#0eaf705c941a218a43dba8e09f1df1d6cd2f1f17" @@ -1885,7 +2023,7 @@ resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0", "@jridgewell/sourcemap-codec@^1.5.5": version "1.5.5" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== @@ -1898,7 +2036,7 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25", "@jridgewell/trace-mapping@^0.3.28": +"@jridgewell/trace-mapping@^0.3.23", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25", "@jridgewell/trace-mapping@^0.3.28": version "0.3.31" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0" integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== @@ -2132,6 +2270,131 @@ resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.2.9.tgz#d229a7b7f9dac167a156992ef23c7f023653f53b" integrity sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA== +"@rollup/rollup-android-arm-eabi@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz#a6742c74c7d9d6d604ef8a48f99326b4ecda3d82" + integrity sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg== + +"@rollup/rollup-android-arm64@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz#97247be098de4df0c11971089fd2edf80a5da8cf" + integrity sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q== + +"@rollup/rollup-darwin-arm64@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz#674852cf14cf11b8056e0b1a2f4e872b523576cf" + integrity sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg== + +"@rollup/rollup-darwin-x64@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz#36dfd7ed0aaf4d9d89d9ef983af72632455b0246" + integrity sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w== + +"@rollup/rollup-freebsd-arm64@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz#2f87c2074b4220260fdb52a9996246edfc633c22" + integrity sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA== + +"@rollup/rollup-freebsd-x64@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz#9b5a26522a38a95dc06616d1939d4d9a76937803" + integrity sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg== + +"@rollup/rollup-linux-arm-gnueabihf@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz#86aa4859385a8734235b5e40a48e52d770758c3a" + integrity sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw== + +"@rollup/rollup-linux-arm-musleabihf@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz#cbe70e56e6ece8dac83eb773b624fc9e5a460976" + integrity sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA== + +"@rollup/rollup-linux-arm64-gnu@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz#d14992a2e653bc3263d284bc6579b7a2890e1c45" + integrity sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA== + +"@rollup/rollup-linux-arm64-musl@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz#2fdd1ddc434ea90aeaa0851d2044789b4d07f6da" + integrity sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA== + +"@rollup/rollup-linux-loong64-gnu@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz#8a181e6f89f969f21666a743cd411416c80099e7" + integrity sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg== + +"@rollup/rollup-linux-loong64-musl@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz#904125af2babc395f8061daa27b5af1f4e3f2f78" + integrity sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q== + +"@rollup/rollup-linux-ppc64-gnu@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz#a57970ac6864c9a3447411a658224bdcf948be22" + integrity sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA== + +"@rollup/rollup-linux-ppc64-musl@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz#bb84de5b26870567a4267666e08891e80bb56a63" + integrity sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA== + +"@rollup/rollup-linux-riscv64-gnu@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz#72d00d2c7fb375ce3564e759db33f17a35bffab9" + integrity sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg== + +"@rollup/rollup-linux-riscv64-musl@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz#4c166ef58e718f9245bd31873384ba15a5c1a883" + integrity sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg== + +"@rollup/rollup-linux-s390x-gnu@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz#bb5025cde9a61db478c2ca7215808ad3bce73a09" + integrity sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w== + +"@rollup/rollup-linux-x64-gnu@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz#9b66b1f9cd95c6624c788f021c756269ffed1552" + integrity sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg== + +"@rollup/rollup-linux-x64-musl@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz#b007ca255dc7166017d57d7d2451963f0bd23fd9" + integrity sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg== + +"@rollup/rollup-openbsd-x64@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz#e8b357b2d1aa2c8d76a98f5f0d889eabe93f4ef9" + integrity sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ== + +"@rollup/rollup-openharmony-arm64@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz#96c2e3f4aacd3d921981329831ff8dde492204dc" + integrity sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA== + +"@rollup/rollup-win32-arm64-msvc@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz#2d865149d706d938df8b4b8f117e69a77646d581" + integrity sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A== + +"@rollup/rollup-win32-ia32-msvc@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz#abe1593be0fa92325e9971c8da429c5e05b92c36" + integrity sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA== + +"@rollup/rollup-win32-x64-gnu@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz#c4af3e9518c9a5cd4b1c163dc81d0ad4d82e7eab" + integrity sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA== + +"@rollup/rollup-win32-x64-msvc@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz#4584a8a87b29188a4c1fe987a9fcf701e256d86c" + integrity sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA== + "@sec-ant/readable-stream@^0.4.1": version "0.4.1" resolved "https://registry.yarnpkg.com/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz#60de891bb126abfdc5410fdc6166aca065f10a0c" @@ -2694,7 +2957,7 @@ resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.5.tgz#14a3e83fa641beb169a2dd8422d91c3c345a9a78" integrity sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q== -"@types/estree@^1.0.6": +"@types/estree@1.0.8", "@types/estree@^1.0.0", "@types/estree@^1.0.6": version "1.0.8" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== @@ -2728,7 +2991,7 @@ resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.5.tgz#5b749ab2b16ba113423feb1a64a95dcd30398472" integrity sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg== -"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.1", "@types/istanbul-lib-coverage@^2.0.6": +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.6": version "2.0.6" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== @@ -2762,11 +3025,6 @@ resolved "https://registry.yarnpkg.com/@types/methods/-/methods-1.1.4.tgz#d3b7ac30ac47c91054ea951ce9eed07b1051e547" integrity sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ== -"@types/mocha@10.0.10": - version "10.0.10" - resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-10.0.10.tgz#91f62905e8d23cbd66225312f239454a23bebfa0" - integrity sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q== - "@types/node@*": version "25.3.3" resolved "https://registry.yarnpkg.com/@types/node/-/node-25.3.3.tgz#605862544ee7ffd7a936bcbf0135a14012f1e549" @@ -2988,6 +3246,90 @@ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== +"@vitest/coverage-v8@3.1.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@vitest/coverage-v8/-/coverage-v8-3.1.1.tgz#5f24a2a1620dc602fd5eb07b72b5c77f7ad5943b" + integrity sha512-MgV6D2dhpD6Hp/uroUoAIvFqA8AuvXEFBC2eepG3WFc1pxTfdk1LEqqkWoWhjz+rytoqrnUUCdf6Lzco3iHkLQ== + dependencies: + "@ampproject/remapping" "^2.3.0" + "@bcoe/v8-coverage" "^1.0.2" + debug "^4.4.0" + istanbul-lib-coverage "^3.2.2" + istanbul-lib-report "^3.0.1" + istanbul-lib-source-maps "^5.0.6" + istanbul-reports "^3.1.7" + magic-string "^0.30.17" + magicast "^0.3.5" + std-env "^3.8.1" + test-exclude "^7.0.1" + tinyrainbow "^2.0.0" + +"@vitest/expect@3.1.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-3.1.1.tgz#d64ddfdcf9e877d805e1eee67bd845bf0708c6c2" + integrity sha512-q/zjrW9lgynctNbwvFtQkGK9+vvHA5UzVi2V8APrp1C6fG6/MuYYkmlx4FubuqLycCeSdHD5aadWfua/Vr0EUA== + dependencies: + "@vitest/spy" "3.1.1" + "@vitest/utils" "3.1.1" + chai "^5.2.0" + tinyrainbow "^2.0.0" + +"@vitest/mocker@3.1.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-3.1.1.tgz#7689d99f87498684c71e9fe9defdbd13ffb7f1ac" + integrity sha512-bmpJJm7Y7i9BBELlLuuM1J1Q6EQ6K5Ye4wcyOpOMXMcePYKSIYlpcrCm4l/O6ja4VJA5G2aMJiuZkZdnxlC3SA== + dependencies: + "@vitest/spy" "3.1.1" + estree-walker "^3.0.3" + magic-string "^0.30.17" + +"@vitest/pretty-format@3.1.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-3.1.1.tgz#5b4d577771daccfced47baf3bf026ad59b52c283" + integrity sha512-dg0CIzNx+hMMYfNmSqJlLSXEmnNhMswcn3sXO7Tpldr0LiGmg3eXdLLhwkv2ZqgHb/d5xg5F7ezNFRA1fA13yA== + dependencies: + tinyrainbow "^2.0.0" + +"@vitest/pretty-format@^3.1.1": + version "3.2.4" + resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-3.2.4.tgz#3c102f79e82b204a26c7a5921bf47d534919d3b4" + integrity sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA== + dependencies: + tinyrainbow "^2.0.0" + +"@vitest/runner@3.1.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-3.1.1.tgz#76b598700737089d66c74272b2e1c94ca2891a49" + integrity sha512-X/d46qzJuEDO8ueyjtKfxffiXraPRfmYasoC4i5+mlLEJ10UvPb0XH5M9C3gWuxd7BAQhpK42cJgJtq53YnWVA== + dependencies: + "@vitest/utils" "3.1.1" + pathe "^2.0.3" + +"@vitest/snapshot@3.1.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-3.1.1.tgz#42b6aa0d0e2b3b48b95a5c76efdcc66a44cb11f3" + integrity sha512-bByMwaVWe/+1WDf9exFxWWgAixelSdiwo2p33tpqIlM14vW7PRV5ppayVXtfycqze4Qhtwag5sVhX400MLBOOw== + dependencies: + "@vitest/pretty-format" "3.1.1" + magic-string "^0.30.17" + pathe "^2.0.3" + +"@vitest/spy@3.1.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-3.1.1.tgz#deca0b025e151302ab514f38390fd7777e294837" + integrity sha512-+EmrUOOXbKzLkTDwlsc/xrwOlPDXyVk3Z6P6K4oiCndxz7YLpp/0R0UsWVOKT0IXWjjBJuSMk6D27qipaupcvQ== + dependencies: + tinyspy "^3.0.2" + +"@vitest/utils@3.1.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-3.1.1.tgz#2893c30219ab6bdf109f07ce5cd287fe8058438d" + integrity sha512-1XIjflyaU2k3HMArJ50bwSh3wKWPD6Q47wz/NUSmRV0zNywPc4w79ARjg/i/aNINHwA+mIALhUVqD9/aUvZNgg== + dependencies: + "@vitest/pretty-format" "3.1.1" + loupe "^3.1.3" + tinyrainbow "^2.0.0" + "@yarnpkg/lockfile@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" @@ -3180,6 +3522,11 @@ assert-plus@1.0.0, assert-plus@^1.0.0: resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw== +assertion-error@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-2.0.1.tgz#f641a196b335690b1070bf00b6e7593fec190bf7" + integrity sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA== + async@^3.0.0, async@^3.2.4, async@^3.2.6: version "3.2.6" resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" @@ -3559,22 +3906,10 @@ bytes@^3.1.2, bytes@~3.1.2: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== -c8@11.0.0: - version "11.0.0" - resolved "https://registry.yarnpkg.com/c8/-/c8-11.0.0.tgz#d0420d93b90202490041c338a8aa95174eca0586" - integrity sha512-e/uRViGHSVIJv7zsaDKM7VRn2390TgHXqUSvYwPHBQaU6L7E9L0n9JbdkwdYPvshDT0KymBmmlwSpms3yBaMNg== - dependencies: - "@bcoe/v8-coverage" "^1.0.1" - "@istanbuljs/schema" "^0.1.3" - find-up "^5.0.0" - foreground-child "^3.1.1" - istanbul-lib-coverage "^3.2.0" - istanbul-lib-report "^3.0.1" - istanbul-reports "^3.1.6" - test-exclude "^8.0.0" - v8-to-istanbul "^9.0.0" - yargs "^17.7.2" - yargs-parser "^21.1.1" +cac@^6.7.14: + version "6.7.14" + resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959" + integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== cacheable-lookup@7.0.0, cacheable-lookup@^7.0.0: version "7.0.0" @@ -3640,6 +3975,17 @@ caseless@~0.12.0: resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== +chai@^5.2.0: + version "5.3.3" + resolved "https://registry.yarnpkg.com/chai/-/chai-5.3.3.tgz#dd3da955e270916a4bd3f625f4b919996ada7e06" + integrity sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw== + dependencies: + assertion-error "^2.0.1" + check-error "^2.1.1" + deep-eql "^5.0.1" + loupe "^3.1.0" + pathval "^2.0.0" + chalk-template@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/chalk-template/-/chalk-template-0.4.0.tgz#692c034d0ed62436b9062c1707fadcd0f753204b" @@ -3660,6 +4006,11 @@ chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: ansi-styles "^4.1.0" supports-color "^7.1.0" +check-error@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-2.1.3.tgz#2427361117b70cca8dc89680ead32b157019caf5" + integrity sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA== + chokidar@^4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.3.tgz#7be37a4c03c9aee1ecfe862a4a23b2c70c205d30" @@ -4011,6 +4362,11 @@ decompress-response@^10.0.0: dependencies: mimic-response "^4.0.0" +deep-eql@^5.0.1: + version "5.0.2" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-5.0.2.tgz#4b756d8d770a9257300825d52a2c2cff99c3a341" + integrity sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q== + deep-is@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" @@ -4220,6 +4576,11 @@ es-errors@^1.3.0: resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== +es-module-lexer@^1.6.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.7.0.tgz#9159601561880a85f2734560a9099b2c31e5372a" + integrity sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA== + es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" @@ -4269,6 +4630,38 @@ esbuild@0.27.3: "@esbuild/win32-ia32" "0.27.3" "@esbuild/win32-x64" "0.27.3" +esbuild@^0.25.0: + version "0.25.12" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.25.12.tgz#97a1d041f4ab00c2fce2f838d2b9969a2d2a97a5" + integrity sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg== + optionalDependencies: + "@esbuild/aix-ppc64" "0.25.12" + "@esbuild/android-arm" "0.25.12" + "@esbuild/android-arm64" "0.25.12" + "@esbuild/android-x64" "0.25.12" + "@esbuild/darwin-arm64" "0.25.12" + "@esbuild/darwin-x64" "0.25.12" + "@esbuild/freebsd-arm64" "0.25.12" + "@esbuild/freebsd-x64" "0.25.12" + "@esbuild/linux-arm" "0.25.12" + "@esbuild/linux-arm64" "0.25.12" + "@esbuild/linux-ia32" "0.25.12" + "@esbuild/linux-loong64" "0.25.12" + "@esbuild/linux-mips64el" "0.25.12" + "@esbuild/linux-ppc64" "0.25.12" + "@esbuild/linux-riscv64" "0.25.12" + "@esbuild/linux-s390x" "0.25.12" + "@esbuild/linux-x64" "0.25.12" + "@esbuild/netbsd-arm64" "0.25.12" + "@esbuild/netbsd-x64" "0.25.12" + "@esbuild/openbsd-arm64" "0.25.12" + "@esbuild/openbsd-x64" "0.25.12" + "@esbuild/openharmony-arm64" "0.25.12" + "@esbuild/sunos-x64" "0.25.12" + "@esbuild/win32-arm64" "0.25.12" + "@esbuild/win32-ia32" "0.25.12" + "@esbuild/win32-x64" "0.25.12" + escalade@^3.1.1, escalade@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" @@ -4594,6 +4987,13 @@ estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== +estree-walker@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-3.0.3.tgz#67c3e549ec402a487b4fc193d1953a524752340d" + integrity sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g== + dependencies: + "@types/estree" "^1.0.0" + esutils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" @@ -4621,6 +5021,11 @@ events@^3.3.0: resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== +expect-type@^1.2.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-1.3.0.tgz#0d58ed361877a31bbc4dd6cf71bbfef7faf6bd68" + integrity sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA== + expect@30.2.0: version "30.2.0" resolved "https://registry.yarnpkg.com/expect/-/expect-30.2.0.tgz#d4013bed267013c14bc1199cec8aa57cee9b5869" @@ -4760,7 +5165,7 @@ fd-slicer@~1.1.0: dependencies: pend "~1.2.0" -fdir@^6.5.0: +fdir@^6.4.4, fdir@^6.5.0: version "6.5.0" resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== @@ -4883,7 +5288,7 @@ follow-redirects@^1.15.11: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.11.tgz#777d73d72a92f8ec4d2e410eb47352a56b8e8340" integrity sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ== -foreground-child@^3.1.0, foreground-child@^3.1.1: +foreground-child@^3.1.0: version "3.3.1" resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.1.tgz#32e8e9ed1b68a3497befb9ac2b6adf92a638576f" integrity sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw== @@ -4966,7 +5371,7 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@^2.3.3: +fsevents@^2.3.3, fsevents@~2.3.2, fsevents@~2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== @@ -5068,7 +5473,7 @@ glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" -glob@^10.0.0, glob@^10.4.5: +glob@^10.0.0, glob@^10.4.1, glob@^10.4.5: version "10.5.0" resolved "https://registry.yarnpkg.com/glob/-/glob-10.5.0.tgz#8ec0355919cd3338c28428a23d4f24ecc5fe738c" integrity sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg== @@ -5080,15 +5485,6 @@ glob@^10.0.0, glob@^10.4.5: package-json-from-dist "^1.0.0" path-scurry "^1.11.1" -glob@^13.0.6: - version "13.0.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-13.0.6.tgz#078666566a425147ccacfbd2e332deb66a2be71d" - integrity sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw== - dependencies: - minimatch "^10.2.2" - minipass "^7.1.3" - path-scurry "^2.0.2" - glob@^6.0.1: version "6.0.4" resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22" @@ -5482,7 +5878,7 @@ isstream@~0.1.2: resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g== -istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0, istanbul-lib-coverage@^3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== @@ -5507,7 +5903,16 @@ istanbul-lib-report@^3.0.0, istanbul-lib-report@^3.0.1: make-dir "^4.0.0" supports-color "^7.1.0" -istanbul-reports@^3.1.6: +istanbul-lib-source-maps@^5.0.6: + version "5.0.6" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz#acaef948df7747c8eb5fbf1265cb980f6353a441" + integrity sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A== + dependencies: + "@jridgewell/trace-mapping" "^0.3.23" + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + +istanbul-reports@^3.1.7: version "3.2.0" resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.2.0.tgz#cb4535162b5784aa623cee21a7252cf2c807ac93" integrity sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA== @@ -5930,6 +6335,11 @@ long-timeout@^0.1.1: resolved "https://registry.yarnpkg.com/long-timeout/-/long-timeout-0.1.1.tgz#9721d788b47e0bcb5a24c2e2bee1a0da55dab514" integrity sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w== +loupe@^3.1.0, loupe@^3.1.3: + version "3.2.1" + resolved "https://registry.yarnpkg.com/loupe/-/loupe-3.2.1.tgz#0095cf56dc5b7a9a7c08ff5b1a8796ec8ad17e76" + integrity sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ== + lower-case@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" @@ -5947,11 +6357,6 @@ lru-cache@^10.2.0: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== -lru-cache@^11.0.0: - version "11.2.6" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.2.6.tgz#356bf8a29e88a7a2945507b31f6429a65a192c58" - integrity sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ== - lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -5959,6 +6364,22 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" +magic-string@^0.30.17: + version "0.30.21" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.21.tgz#56763ec09a0fa8091df27879fd94d19078c00d91" + integrity sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.5" + +magicast@^0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/magicast/-/magicast-0.3.5.tgz#8301c3c7d66704a0771eb1bad74274f0ec036739" + integrity sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ== + dependencies: + "@babel/parser" "^7.25.4" + "@babel/types" "^7.25.4" + source-map-js "^1.2.0" + mailgun.js@^8.0.1: version "8.2.2" resolved "https://registry.yarnpkg.com/mailgun.js/-/mailgun.js-8.2.2.tgz#97133d69fd76b77c67eedb394fd85b1875cac600" @@ -6131,7 +6552,7 @@ minimist@^1.2.0, minimist@^1.2.6: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== -"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2, minipass@^7.1.3: +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: version "7.1.3" resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.3.tgz#79389b4eb1bb2d003a9bba87d492f2bd37bdc65b" integrity sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A== @@ -6221,6 +6642,11 @@ nan@^2.14.0: resolved "https://registry.yarnpkg.com/nan/-/nan-2.25.0.tgz#937ed345e63d9481362a7942d49c4860d27eeabd" integrity sha512-0M90Ag7Xn5KMLLZ7zliPWP3rT90P6PN+IzVFS0VqmnPktBk3700xUVv8Ikm9EUaUE5SDWdp/BIxdENzVznpm1g== +nanoid@^3.3.11: + version "3.3.11" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b" + integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -6604,14 +7030,6 @@ path-scurry@^1.11.1: lru-cache "^10.2.0" minipass "^5.0.0 || ^6.0.2 || ^7.0.0" -path-scurry@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-2.0.2.tgz#6be0d0ee02a10d9e0de7a98bae65e182c9061f85" - integrity sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg== - dependencies: - lru-cache "^11.0.0" - minipass "^7.1.2" - path-to-regexp@^8.0.0: version "8.3.0" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-8.3.0.tgz#aa818a6981f99321003a08987d3cec9c3474cd1f" @@ -6622,6 +7040,16 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +pathe@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/pathe/-/pathe-2.0.3.tgz#3ecbec55421685b70a9da872b2cff3e1cbed1716" + integrity sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w== + +pathval@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-2.0.1.tgz#8855c5a2899af072d6ac05d11e46045ad0dc605d" + integrity sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ== + pend@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" @@ -6667,6 +7095,15 @@ pluralize@^8.0.0: resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== +postcss@^8.5.3: + version "8.5.6" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.6.tgz#2825006615a619b4f62a9e7426cc120b349a8f3c" + integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg== + dependencies: + nanoid "^3.3.11" + picocolors "^1.1.1" + source-map-js "^1.2.1" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -7033,6 +7470,40 @@ rimraf@~2.4.0: dependencies: glob "^6.0.1" +rollup@^4.34.9: + version "4.59.0" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.59.0.tgz#cf74edac17c1486f562d728a4d923a694abdf06f" + integrity sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg== + dependencies: + "@types/estree" "1.0.8" + optionalDependencies: + "@rollup/rollup-android-arm-eabi" "4.59.0" + "@rollup/rollup-android-arm64" "4.59.0" + "@rollup/rollup-darwin-arm64" "4.59.0" + "@rollup/rollup-darwin-x64" "4.59.0" + "@rollup/rollup-freebsd-arm64" "4.59.0" + "@rollup/rollup-freebsd-x64" "4.59.0" + "@rollup/rollup-linux-arm-gnueabihf" "4.59.0" + "@rollup/rollup-linux-arm-musleabihf" "4.59.0" + "@rollup/rollup-linux-arm64-gnu" "4.59.0" + "@rollup/rollup-linux-arm64-musl" "4.59.0" + "@rollup/rollup-linux-loong64-gnu" "4.59.0" + "@rollup/rollup-linux-loong64-musl" "4.59.0" + "@rollup/rollup-linux-ppc64-gnu" "4.59.0" + "@rollup/rollup-linux-ppc64-musl" "4.59.0" + "@rollup/rollup-linux-riscv64-gnu" "4.59.0" + "@rollup/rollup-linux-riscv64-musl" "4.59.0" + "@rollup/rollup-linux-s390x-gnu" "4.59.0" + "@rollup/rollup-linux-x64-gnu" "4.59.0" + "@rollup/rollup-linux-x64-musl" "4.59.0" + "@rollup/rollup-openbsd-x64" "4.59.0" + "@rollup/rollup-openharmony-arm64" "4.59.0" + "@rollup/rollup-win32-arm64-msvc" "4.59.0" + "@rollup/rollup-win32-ia32-msvc" "4.59.0" + "@rollup/rollup-win32-x64-gnu" "4.59.0" + "@rollup/rollup-win32-x64-msvc" "4.59.0" + fsevents "~2.3.2" + router@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/router/-/router-2.2.0.tgz#019be620b711c87641167cc79b99090f00b146ef" @@ -7199,6 +7670,11 @@ side-channel@^1.1.0: side-channel-map "^1.0.1" side-channel-weakmap "^1.0.2" +siginfo@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30" + integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g== + signal-exit@^3.0.2: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" @@ -7246,7 +7722,7 @@ snake-case@^3.0.3: dot-case "^3.0.4" tslib "^2.0.3" -source-map-js@^1.0.1: +source-map-js@^1.0.1, source-map-js@^1.2.0, source-map-js@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== @@ -7322,11 +7798,21 @@ stack-utils@^2.0.6: dependencies: escape-string-regexp "^2.0.0" +stackback@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/stackback/-/stackback-0.0.2.tgz#1ac8a0d9483848d1695e418b6d031a3c3ce68e3b" + integrity sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw== + statuses@^2.0.1, statuses@^2.0.2, statuses@~2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.2.tgz#8f75eecef765b5e1cfcdc080da59409ed424e382" integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw== +std-env@^3.8.1: + version "3.10.0" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.10.0.tgz#d810b27e3a073047b2b5e40034881f5ea6f9c83b" + integrity sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg== + stoppable@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/stoppable/-/stoppable-1.1.0.tgz#32da568e83ea488b08e4d7ea2c3bcc9d75015d5b" @@ -7557,13 +8043,13 @@ test-exclude@^6.0.0: glob "^7.1.4" minimatch "^3.0.4" -test-exclude@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-8.0.0.tgz#85891add3fa46bb822b1b1579c7f8c9a3d04ebd8" - integrity sha512-ZOffsNrXYggvU1mDGHk54I96r26P8SyMjO5slMKSc7+IWmtB/MQKnEC2fP51imB3/pT6YK5cT5E8f+Dd9KdyOQ== +test-exclude@^7.0.1: + version "7.0.2" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-7.0.2.tgz#482392077630bc57d5630c13abe908bb910dfc65" + integrity sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw== dependencies: "@istanbuljs/schema" "^0.1.2" - glob "^13.0.6" + glob "^10.4.1" minimatch "^10.2.2" text-decoder@^1.1.0: @@ -7588,7 +8074,17 @@ tiny-case@^1.0.3: resolved "https://registry.yarnpkg.com/tiny-case/-/tiny-case-1.0.3.tgz#d980d66bc72b5d5a9ca86fb7c9ffdb9c898ddd03" integrity sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q== -tinyglobby@^0.2.12, tinyglobby@^0.2.15: +tinybench@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.9.0.tgz#103c9f8ba6d7237a47ab6dd1dcff77251863426b" + integrity sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg== + +tinyexec@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-0.3.2.tgz#941794e657a85e496577995c6eef66f53f42b3d2" + integrity sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA== + +tinyglobby@^0.2.12, tinyglobby@^0.2.13, tinyglobby@^0.2.15: version "0.2.15" resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2" integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ== @@ -7596,6 +8092,21 @@ tinyglobby@^0.2.12, tinyglobby@^0.2.15: fdir "^6.5.0" picomatch "^4.0.3" +tinypool@^1.0.2: + version "1.1.1" + resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-1.1.1.tgz#059f2d042bd37567fbc017d3d426bdd2a2612591" + integrity sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg== + +tinyrainbow@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/tinyrainbow/-/tinyrainbow-2.0.0.tgz#9509b2162436315e80e3eee0fcce4474d2444294" + integrity sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw== + +tinyspy@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-3.0.2.tgz#86dd3cf3d737b15adcf17d7887c84a75201df20a" + integrity sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q== + tmp@~0.2.1: version "0.2.5" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.5.tgz#b06bcd23f0f3c8357b426891726d16015abfd8f8" @@ -7872,15 +8383,6 @@ v8-compile-cache-lib@^3.0.1: resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== -v8-to-istanbul@^9.0.0: - version "9.3.0" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz#b9572abfa62bd556c16d75fdebc1a411d5ff3175" - integrity sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA== - dependencies: - "@jridgewell/trace-mapping" "^0.3.12" - "@types/istanbul-lib-coverage" "^2.0.1" - convert-source-map "^2.0.0" - validate-npm-package-license@^3.0.1: version "3.0.4" resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" @@ -7908,6 +8410,57 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" +vite-node@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-3.1.1.tgz#ad186c07859a6e5fca7c7f563e55fb11b16557bc" + integrity sha512-V+IxPAE2FvXpTCHXyNem0M+gWm6J7eRyWPR6vYoG/Gl+IscNOjXzztUhimQgTxaAoUoj40Qqimaa0NLIOOAH4w== + dependencies: + cac "^6.7.14" + debug "^4.4.0" + es-module-lexer "^1.6.0" + pathe "^2.0.3" + vite "^5.0.0 || ^6.0.0" + +"vite@^5.0.0 || ^6.0.0": + version "6.4.1" + resolved "https://registry.yarnpkg.com/vite/-/vite-6.4.1.tgz#afbe14518cdd6887e240a4b0221ab6d0ce733f96" + integrity sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g== + dependencies: + esbuild "^0.25.0" + fdir "^6.4.4" + picomatch "^4.0.2" + postcss "^8.5.3" + rollup "^4.34.9" + tinyglobby "^0.2.13" + optionalDependencies: + fsevents "~2.3.3" + +vitest@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-3.1.1.tgz#39fa2356e510513fccdc5d16465a9fc066ef1fc6" + integrity sha512-kiZc/IYmKICeBAZr9DQ5rT7/6bD9G7uqQEki4fxazi1jdVl2mWGzedtBs5s6llz59yQhVb7FFY2MbHzHCnT79Q== + dependencies: + "@vitest/expect" "3.1.1" + "@vitest/mocker" "3.1.1" + "@vitest/pretty-format" "^3.1.1" + "@vitest/runner" "3.1.1" + "@vitest/snapshot" "3.1.1" + "@vitest/spy" "3.1.1" + "@vitest/utils" "3.1.1" + chai "^5.2.0" + debug "^4.4.0" + expect-type "^1.2.0" + magic-string "^0.30.17" + pathe "^2.0.3" + std-env "^3.8.1" + tinybench "^2.9.0" + tinyexec "^0.3.2" + tinypool "^1.0.2" + tinyrainbow "^2.0.0" + vite "^5.0.0 || ^6.0.0" + vite-node "3.1.1" + why-is-node-running "^2.3.0" + walker@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" @@ -7929,6 +8482,14 @@ which@^2.0.1: dependencies: isexe "^2.0.0" +why-is-node-running@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/why-is-node-running/-/why-is-node-running-2.3.0.tgz#a3f69a97107f494b3cdc3bdddd883a7d65cebf04" + integrity sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w== + dependencies: + siginfo "^2.0.0" + stackback "0.0.2" + word-wrap@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" From 5a28d2300bf1b37b7f414f89fdb0f8106da85a5d Mon Sep 17 00:00:00 2001 From: Steve Larson <9larsons@gmail.com> Date: Mon, 2 Mar 2026 14:54:22 -0600 Subject: [PATCH 2/8] Add package-specific vitest configs and ESLint fixes Local vitest.config.ts overrides for 6 packages: - express-test: setupFiles for snapshot test registry - http-cache-utils, webhook-mock-receiver: no coverage enforcement (matches mocha) - errors, prometheus-metrics: coverage scoped to src/ - job-manager: ignore unhandled rejections from bree workers ESLint fixes for beforeAll/afterAll globals in 5 test configs, and mocha lint rule overrides in express-test setup file. --- .../email-mock-receiver/test/.eslintrc.js | 5 +++- packages/errors/vitest.config.ts | 21 ++++++++++++++++ packages/express-test/test/.eslintrc.js | 5 +++- packages/express-test/test/utils/overrides.js | 24 +++++++++++++++++-- packages/express-test/vitest.config.ts | 21 ++++++++++++++++ packages/http-cache-utils/vitest.config.ts | 20 ++++++++++++++++ packages/job-manager/vitest.config.ts | 24 +++++++++++++++++++ packages/prometheus-metrics/test/.eslintrc.js | 5 +++- packages/prometheus-metrics/vitest.config.ts | 21 ++++++++++++++++ .../webhook-mock-receiver/test/.eslintrc.js | 5 +++- .../webhook-mock-receiver/vitest.config.ts | 20 ++++++++++++++++ packages/zip/test/.eslintrc.js | 6 ++++- 12 files changed, 170 insertions(+), 7 deletions(-) create mode 100644 packages/errors/vitest.config.ts create mode 100644 packages/express-test/vitest.config.ts create mode 100644 packages/http-cache-utils/vitest.config.ts create mode 100644 packages/job-manager/vitest.config.ts create mode 100644 packages/prometheus-metrics/vitest.config.ts create mode 100644 packages/webhook-mock-receiver/vitest.config.ts diff --git a/packages/email-mock-receiver/test/.eslintrc.js b/packages/email-mock-receiver/test/.eslintrc.js index 829b601eb..ef13dee0e 100644 --- a/packages/email-mock-receiver/test/.eslintrc.js +++ b/packages/email-mock-receiver/test/.eslintrc.js @@ -2,5 +2,8 @@ module.exports = { plugins: ['ghost'], extends: [ 'plugin:ghost/test' - ] + ], + globals: { + beforeAll: 'readonly' + } }; diff --git a/packages/errors/vitest.config.ts b/packages/errors/vitest.config.ts new file mode 100644 index 000000000..c9f4cb1f1 --- /dev/null +++ b/packages/errors/vitest.config.ts @@ -0,0 +1,21 @@ +import {defineConfig} from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + include: ['test/**/*.test.{js,ts}'], + coverage: { + provider: 'v8', + all: true, + include: ['src/**'], + reporter: ['text', 'cobertura'], + thresholds: { + lines: 90, + functions: 90, + branches: 90, + statements: 90 + } + } + } +}); diff --git a/packages/express-test/test/.eslintrc.js b/packages/express-test/test/.eslintrc.js index 829b601eb..ef13dee0e 100644 --- a/packages/express-test/test/.eslintrc.js +++ b/packages/express-test/test/.eslintrc.js @@ -2,5 +2,8 @@ module.exports = { plugins: ['ghost'], extends: [ 'plugin:ghost/test' - ] + ], + globals: { + beforeAll: 'readonly' + } }; diff --git a/packages/express-test/test/utils/overrides.js b/packages/express-test/test/utils/overrides.js index f256fa5ff..77dcb5dd7 100644 --- a/packages/express-test/test/utils/overrides.js +++ b/packages/express-test/test/utils/overrides.js @@ -1,2 +1,22 @@ -const {mochaHooks} = require('@tryghost/jest-snapshot'); -exports.mochaHooks = mochaHooks; +const {snapshotManager} = require('@tryghost/jest-snapshot'); + +/* eslint-disable ghost/mocha/no-mocha-arrows, ghost/mocha/no-top-level-hooks, ghost/mocha/handle-done-callback */ +beforeAll(() => { // eslint-disable-line no-undef + snapshotManager.resetRegistry(); +}); + +beforeEach((context) => { // eslint-disable-line no-undef + // Reconstruct full title similar to mocha's fullTitle() + const parts = []; + let suite = context.task.suite; + while (suite && suite.name) { + parts.unshift(suite.name); + suite = suite.suite; + } + parts.push(context.task.name); + + snapshotManager.setCurrentTest({ + testPath: context.task.file.filepath, + testTitle: parts.join(' ') + }); +}); diff --git a/packages/express-test/vitest.config.ts b/packages/express-test/vitest.config.ts new file mode 100644 index 000000000..2257032e7 --- /dev/null +++ b/packages/express-test/vitest.config.ts @@ -0,0 +1,21 @@ +import {defineConfig} from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + include: ['test/**/*.test.{js,ts}'], + setupFiles: ['./test/utils/overrides.js'], + coverage: { + provider: 'v8', + all: true, + reporter: ['text', 'cobertura'], + thresholds: { + lines: 0, + functions: 0, + branches: 0, + statements: 0 + } + } + } +}); diff --git a/packages/http-cache-utils/vitest.config.ts b/packages/http-cache-utils/vitest.config.ts new file mode 100644 index 000000000..f1b98db11 --- /dev/null +++ b/packages/http-cache-utils/vitest.config.ts @@ -0,0 +1,20 @@ +import {defineConfig} from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + include: ['test/**/*.test.{js,ts}'], + coverage: { + provider: 'v8', + all: true, + reporter: ['text', 'cobertura'], + thresholds: { + lines: 0, + functions: 0, + branches: 0, + statements: 0 + } + } + } +}); diff --git a/packages/job-manager/vitest.config.ts b/packages/job-manager/vitest.config.ts new file mode 100644 index 000000000..e11870530 --- /dev/null +++ b/packages/job-manager/vitest.config.ts @@ -0,0 +1,24 @@ +import {defineConfig} from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + include: ['test/**/*.test.{js,ts}'], + // Job manager tests spawn background workers that emit unhandled + // rejections after the test completes (e.g. bree job cleanup). + // These are expected and were silently ignored by Mocha. + dangerouslyIgnoreUnhandledErrors: true, + coverage: { + provider: 'v8', + all: true, + reporter: ['text', 'cobertura'], + thresholds: { + lines: 90, + functions: 90, + branches: 90, + statements: 90 + } + } + } +}); diff --git a/packages/prometheus-metrics/test/.eslintrc.js b/packages/prometheus-metrics/test/.eslintrc.js index 6fe6dc150..023956a15 100644 --- a/packages/prometheus-metrics/test/.eslintrc.js +++ b/packages/prometheus-metrics/test/.eslintrc.js @@ -3,5 +3,8 @@ module.exports = { plugins: ['ghost'], extends: [ 'plugin:ghost/test' - ] + ], + globals: { + afterAll: 'readonly' + } }; diff --git a/packages/prometheus-metrics/vitest.config.ts b/packages/prometheus-metrics/vitest.config.ts new file mode 100644 index 000000000..c9f4cb1f1 --- /dev/null +++ b/packages/prometheus-metrics/vitest.config.ts @@ -0,0 +1,21 @@ +import {defineConfig} from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + include: ['test/**/*.test.{js,ts}'], + coverage: { + provider: 'v8', + all: true, + include: ['src/**'], + reporter: ['text', 'cobertura'], + thresholds: { + lines: 90, + functions: 90, + branches: 90, + statements: 90 + } + } + } +}); diff --git a/packages/webhook-mock-receiver/test/.eslintrc.js b/packages/webhook-mock-receiver/test/.eslintrc.js index 829b601eb..ef13dee0e 100644 --- a/packages/webhook-mock-receiver/test/.eslintrc.js +++ b/packages/webhook-mock-receiver/test/.eslintrc.js @@ -2,5 +2,8 @@ module.exports = { plugins: ['ghost'], extends: [ 'plugin:ghost/test' - ] + ], + globals: { + beforeAll: 'readonly' + } }; diff --git a/packages/webhook-mock-receiver/vitest.config.ts b/packages/webhook-mock-receiver/vitest.config.ts new file mode 100644 index 000000000..f1b98db11 --- /dev/null +++ b/packages/webhook-mock-receiver/vitest.config.ts @@ -0,0 +1,20 @@ +import {defineConfig} from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + include: ['test/**/*.test.{js,ts}'], + coverage: { + provider: 'v8', + all: true, + reporter: ['text', 'cobertura'], + thresholds: { + lines: 0, + functions: 0, + branches: 0, + statements: 0 + } + } + } +}); diff --git a/packages/zip/test/.eslintrc.js b/packages/zip/test/.eslintrc.js index 829b601eb..c0a7056a1 100644 --- a/packages/zip/test/.eslintrc.js +++ b/packages/zip/test/.eslintrc.js @@ -2,5 +2,9 @@ module.exports = { plugins: ['ghost'], extends: [ 'plugin:ghost/test' - ] + ], + globals: { + beforeAll: 'readonly', + afterAll: 'readonly' + } }; From 68fb0624a12d8fca4b27a1f6c6eb2d8096e85fbf Mon Sep 17 00:00:00 2001 From: Steve Larson <9larsons@gmail.com> Date: Mon, 2 Mar 2026 14:54:35 -0600 Subject: [PATCH 3/8] Convert test files from Mocha patterns to Vitest - Convert 87 done() callback patterns to async/await with Promises - Replace before/after with beforeAll/afterAll (5 files) - Fix runner-specific test assumptions (root-utils, jest-snapshot, metrics) --- packages/api-framework/test/http.test.js | 210 ++-- .../test/EmailMockReceiver.test.js | 2 +- .../express-test/test/ExpectRequest.test.js | 240 ++--- packages/express-test/test/Request.test.js | 42 +- .../express-test/test/example-app.test.js | 10 +- packages/logging/test/logging.test.js | 510 +++++----- packages/metrics/test/metrics.test.js | 57 +- .../test/mw-error-handler.test.js | 448 +++++---- .../pretty-stream/test/PrettyStream.test.js | 944 ++++++++---------- .../test/metrics-server.test.ts | 2 +- packages/server/test/server.test.js | 412 ++++---- .../test/WebhookMockReceiver.test.js | 2 +- packages/zip/test/zip.test.js | 49 +- 13 files changed, 1463 insertions(+), 1465 deletions(-) diff --git a/packages/api-framework/test/http.test.js b/packages/api-framework/test/http.test.js index 3e41407ce..93b8ec6fd 100644 --- a/packages/api-framework/test/http.test.js +++ b/packages/api-framework/test/http.test.js @@ -65,129 +65,143 @@ describe('HTTP', function () { }); }); - it('api response is fn', function (done) { - const response = sinon.stub().callsFake(function (_req, _res, _next) { - assert.ok(_req); - assert.ok(_res); - assert.ok(_next); - assert.equal(apiImpl.calledOnce, true); - assert.equal(_res.json.called, false); - done(); - }); + it('api response is fn', async function () { + await new Promise((resolve) => { + const response = sinon.stub().callsFake(function (_req, _res, _next) { + assert.ok(_req); + assert.ok(_res); + assert.ok(_next); + assert.equal(apiImpl.calledOnce, true); + assert.equal(_res.json.called, false); + resolve(); + }); - const apiImpl = sinon.stub().resolves(response); - shared.http(apiImpl)(req, res, next); + const apiImpl = sinon.stub().resolves(response); + shared.http(apiImpl)(req, res, next); + }); }); - it('api response is fn (data)', function (done) { - const apiImpl = sinon.stub().resolves('data'); + it('api response is fn (data)', async function () { + await new Promise((resolve) => { + const apiImpl = sinon.stub().resolves('data'); - next.callsFake(done); + next.callsFake(resolve); - res.json.callsFake(function () { - assert.equal(shared.headers.get.calledOnce, true); - assert.equal(res.status.calledOnce, true); - assert.equal(res.send.called, false); - done(); - }); + res.json.callsFake(function () { + assert.equal(shared.headers.get.calledOnce, true); + assert.equal(res.status.calledOnce, true); + assert.equal(res.send.called, false); + resolve(); + }); - shared.http(apiImpl)(req, res, next); + shared.http(apiImpl)(req, res, next); + }); }); - it('handles api key, user and plain text response', function (done) { - req.vhost = null; - req.user = {id: 'user-id'}; - req.api_key = { - get(key) { - return { - id: 'api-key-id', - type: 'admin', - integration_id: 'integration-id' - }[key]; - } - }; - - const apiImpl = sinon.stub().resolves('plain body'); - apiImpl.response = {format: 'plain'}; - apiImpl.statusCode = 201; - - res.send.callsFake(() => { - assert.equal(res.status.calledOnceWithExactly(201), true); - assert.equal(res.headers.constructor, Object); - assert.equal(res.json.called, false); + it('handles api key, user and plain text response', async function () { + await new Promise((resolve) => { + req.vhost = null; + req.user = {id: 'user-id'}; + req.api_key = { + get(key) { + return { + id: 'api-key-id', + type: 'admin', + integration_id: 'integration-id' + }[key]; + } + }; + + const apiImpl = sinon.stub().resolves('plain body'); + apiImpl.response = {format: 'plain'}; + apiImpl.statusCode = 201; + + res.send.callsFake(() => { + assert.equal(res.status.calledOnceWithExactly(201), true); + assert.equal(res.headers.constructor, Object); + assert.equal(res.json.called, false); + + const frame = apiImpl.args[0][0]; + assert.equal(frame.options.context.api_key.id, 'api-key-id'); + assert.equal(frame.options.context.integration.id, 'integration-id'); + assert.equal(frame.options.context.user, 'user-id'); + resolve(); + }); - const frame = apiImpl.args[0][0]; - assert.equal(frame.options.context.api_key.id, 'api-key-id'); - assert.equal(frame.options.context.integration.id, 'integration-id'); - assert.equal(frame.options.context.user, 'user-id'); - done(); + shared.http(apiImpl)(req, res, next); }); - - shared.http(apiImpl)(req, res, next); }); - it('supports async response format and statusCode function', function (done) { - const apiImpl = sinon.stub().resolves({ok: true}); - apiImpl.statusCode = sinon.stub().returns(204); - apiImpl.response = { - format() { - return Promise.resolve('plain'); - } - }; + it('supports async response format and statusCode function', async function () { + await new Promise((resolve) => { + const apiImpl = sinon.stub().resolves({ok: true}); + apiImpl.statusCode = sinon.stub().returns(204); + apiImpl.response = { + format() { + return Promise.resolve('plain'); + } + }; + + res.send.callsFake(() => { + assert.equal(apiImpl.statusCode.calledOnce, true); + assert.equal(res.status.calledOnceWithExactly(204), true); + resolve(); + }); - res.send.callsFake(() => { - assert.equal(apiImpl.statusCode.calledOnce, true); - assert.equal(res.status.calledOnceWithExactly(204), true); - done(); + shared.http(apiImpl)(req, res, next); }); - - shared.http(apiImpl)(req, res, next); }); - it('supports sync response format function', function (done) { - const apiImpl = sinon.stub().resolves('plain body'); - apiImpl.response = { - format() { - return 'plain'; - } - }; + it('supports sync response format function', async function () { + await new Promise((resolve) => { + const apiImpl = sinon.stub().resolves('plain body'); + apiImpl.response = { + format() { + return 'plain'; + } + }; + + res.send.callsFake(() => { + assert.equal(res.send.calledOnce, true); + assert.equal(res.json.called, false); + resolve(); + }); - res.send.callsFake(() => { - assert.equal(res.send.calledOnce, true); - assert.equal(res.json.called, false); - done(); + shared.http(apiImpl)(req, res, next); }); - - shared.http(apiImpl)(req, res, next); }); - it('passes errors to next with frame options', function (done) { - const error = new Error('failure'); - const apiImpl = sinon.stub().rejects(error); - - next.callsFake((err) => { - assert.equal(err, error); - assert.deepEqual(req.frameOptions, { - docName: null, - method: null + it('passes errors to next with frame options', async function () { + await new Promise((resolve) => { + const error = new Error('failure'); + const apiImpl = sinon.stub().rejects(error); + + next.callsFake((err) => { + assert.equal(err, error); + assert.deepEqual(req.frameOptions, { + docName: null, + method: null + }); + resolve(); }); - done(); - }); - shared.http(apiImpl)(req, res, next); + shared.http(apiImpl)(req, res, next); + }); }); - it('uses req.url pathname when originalUrl is missing', function (done) { - req.originalUrl = undefined; - req.url = '/ghost/api/content/posts/?include=authors'; + it('uses req.url pathname when originalUrl is missing', async function () { + await new Promise((resolve) => { + req.originalUrl = undefined; + req.url = '/ghost/api/content/posts/?include=authors'; - const apiImpl = sinon.stub().resolves({}); - res.json.callsFake(() => { - const frame = apiImpl.args[0][0]; - assert.equal(frame.original.url.pathname, '/ghost/api/content/posts/'); - done(); - }); + const apiImpl = sinon.stub().resolves({}); + res.json.callsFake(() => { + const frame = apiImpl.args[0][0]; + assert.equal(frame.original.url.pathname, '/ghost/api/content/posts/'); + resolve(); + }); - shared.http(apiImpl)(req, res, next); + shared.http(apiImpl)(req, res, next); + }); }); }); diff --git a/packages/email-mock-receiver/test/EmailMockReceiver.test.js b/packages/email-mock-receiver/test/EmailMockReceiver.test.js index e41c0895b..d3f51bbd3 100644 --- a/packages/email-mock-receiver/test/EmailMockReceiver.test.js +++ b/packages/email-mock-receiver/test/EmailMockReceiver.test.js @@ -7,7 +7,7 @@ describe('Email mock receiver', function () { let snapshotManager; let emailMockReceiver; - before(function () { + beforeAll(function () { snapshotManager = { assertSnapshot: sinon.spy() }; diff --git a/packages/express-test/test/ExpectRequest.test.js b/packages/express-test/test/ExpectRequest.test.js index 8cbea7cbc..ac75564ad 100644 --- a/packages/express-test/test/ExpectRequest.test.js +++ b/packages/express-test/test/ExpectRequest.test.js @@ -41,127 +41,137 @@ describe('ExpectRequest', function () { } }); - it('finalize with no assertions doesnt try to run assertions', async function (done) { - const fn = (req, res) => { - // This is how reqresnext works - res.emit('finish'); - }; - const jar = {}; - const opts = new RequestOptions(); - const request = new ExpectRequest(fn, jar, opts); - - stubCookies(request); - - try { - const superStub = sinon.stub(Request.prototype, 'finalize').callsArg(0); - const assertSpy = sinon.stub(request, '_assertAll'); - - // I couldn't figure out how to stub the super.finalize call here - request.finalize((error) => { - if (error) { - done(error); - } - - sinon.assert.calledOnce(superStub); - assert.equal(error, null); - sinon.assert.notCalled(assertSpy); - done(); - }); - } catch (error) { - done(error); - } + it('finalize with no assertions doesnt try to run assertions', async function () { + await new Promise((resolve, reject) => { + const fn = (req, res) => { + // This is how reqresnext works + res.emit('finish'); + }; + const jar = {}; + const opts = new RequestOptions(); + const request = new ExpectRequest(fn, jar, opts); + + stubCookies(request); + + try { + const superStub = sinon.stub(Request.prototype, 'finalize').callsArg(0); + const assertSpy = sinon.stub(request, '_assertAll'); + + // I couldn't figure out how to stub the super.finalize call here + request.finalize((error) => { + if (error) { + reject(error); + return; + } + + sinon.assert.calledOnce(superStub); + assert.equal(error, null); + sinon.assert.notCalled(assertSpy); + resolve(); + }); + } catch (error) { + reject(error); + } + }); }); - it('finalize with assertions runs assertions', async function (done) { - const fn = (req, res) => { - // This is how reqresnext works - res.emit('finish'); - }; - const jar = {}; - const opts = new RequestOptions(); - const request = new ExpectRequest(fn, jar, opts); - - request.assertions = [{}]; - - stubCookies(request); - - try { - const superStub = sinon.stub(Request.prototype, 'finalize').callsArg(0); - const assertSpy = sinon.stub(request, '_assertAll'); - - request.finalize((error) => { - if (error) { - done(error); - } - - sinon.assert.calledOnce(superStub); - assert.equal(error, null); - sinon.assert.calledOnce(assertSpy); - done(); - }); - } catch (error) { - done(error); - } + it('finalize with assertions runs assertions', async function () { + await new Promise((resolve, reject) => { + const fn = (req, res) => { + // This is how reqresnext works + res.emit('finish'); + }; + const jar = {}; + const opts = new RequestOptions(); + const request = new ExpectRequest(fn, jar, opts); + + request.assertions = [{}]; + + stubCookies(request); + + try { + const superStub = sinon.stub(Request.prototype, 'finalize').callsArg(0); + const assertSpy = sinon.stub(request, '_assertAll'); + + request.finalize((error) => { + if (error) { + reject(error); + return; + } + + sinon.assert.calledOnce(superStub); + assert.equal(error, null); + sinon.assert.calledOnce(assertSpy); + resolve(); + }); + } catch (error) { + reject(error); + } + }); }); - it('finalize errors correctly when super.finalize is erroring', async function (done) { - const fn = (req, res) => { - // This is how reqresnext works - res.emit('finish'); - }; - const jar = {}; - const opts = new RequestOptions(); - const request = new ExpectRequest(fn, jar, opts); - - request.assertions = []; - - stubCookies(request); - - const theError = new Error(); - - try { - const superStub = sinon.stub(Request.prototype, 'finalize').callsArgWith(0, theError); - const assertSpy = sinon.stub(request, '_assertAll'); - - request.finalize((error) => { - sinon.assert.calledOnce(superStub); - assert.equal(error, theError); - sinon.assert.notCalled(assertSpy); - done(); - }); - } catch (error) { - done(error); - } + it('finalize errors correctly when super.finalize is erroring', async function () { + await new Promise((resolve, reject) => { + const fn = (req, res) => { + // This is how reqresnext works + res.emit('finish'); + }; + const jar = {}; + const opts = new RequestOptions(); + const request = new ExpectRequest(fn, jar, opts); + + request.assertions = []; + + stubCookies(request); + + const theError = new Error(); + + try { + const superStub = sinon.stub(Request.prototype, 'finalize').callsArgWith(0, theError); + const assertSpy = sinon.stub(request, '_assertAll'); + + request.finalize((error) => { + sinon.assert.calledOnce(superStub); + assert.equal(error, theError); + sinon.assert.notCalled(assertSpy); + resolve(); + }); + } catch (error) { + reject(error); + } + }); }); - it('finalize errors correctly when assertions are erroring', async function (done) { - const fn = (req, res) => { - // This is how reqresnext works - res.emit('finish'); - }; - const jar = {}; - const opts = new RequestOptions(); - const request = new ExpectRequest(fn, jar, opts); - - request.assertions = [{}]; - - stubCookies(request); - - const theError = new Error(); - - try { - const superStub = sinon.stub(Request.prototype, 'finalize').callsArg(0); - const assertSpy = sinon.stub(request, '_assertAll').throws(theError); - - request.finalize((error) => { - sinon.assert.calledOnce(superStub); - sinon.assert.calledOnce(assertSpy); - assert.equal(error, theError); - done(); - }); - } catch (error) { - done(error); - } + it('finalize errors correctly when assertions are erroring', async function () { + await new Promise((resolve, reject) => { + const fn = (req, res) => { + // This is how reqresnext works + res.emit('finish'); + }; + const jar = {}; + const opts = new RequestOptions(); + const request = new ExpectRequest(fn, jar, opts); + + request.assertions = [{}]; + + stubCookies(request); + + const theError = new Error(); + + try { + const superStub = sinon.stub(Request.prototype, 'finalize').callsArg(0); + const assertSpy = sinon.stub(request, '_assertAll').throws(theError); + + request.finalize((error) => { + sinon.assert.calledOnce(superStub); + sinon.assert.calledOnce(assertSpy); + assert.equal(error, theError); + resolve(); + }); + } catch (error) { + reject(error); + } + }); }); it('_addAssertion adds an assertion', function () { diff --git a/packages/express-test/test/Request.test.js b/packages/express-test/test/Request.test.js index 1b957e6fc..908d3ed52 100644 --- a/packages/express-test/test/Request.test.js +++ b/packages/express-test/test/Request.test.js @@ -235,26 +235,32 @@ describe('Request', function () { sinon.assert.calledOnceWithMatch(jar.setCookies, 'xyz'); }); - it('_doRequest', async function (done) { - const fn = (req, res) => { - // This is how reqresnext works - res.emit('finish'); - }; - const jar = {}; - const opts = new RequestOptions(); - const request = new Request(fn, jar, opts); - - // Stub cookies, we'll test this behaviour later - const {saveCookiesStub, restoreCookiesStub} = stubCookies(request); - - request._doRequest((error, response) => { - assert.equal(error, null); - assert.equal(response.statusCode, 200); + it('_doRequest', async function () { + await new Promise((resolve, reject) => { + const fn = (req, res) => { + // This is how reqresnext works + res.emit('finish'); + }; + const jar = {}; + const opts = new RequestOptions(); + const request = new Request(fn, jar, opts); + + // Stub cookies, we'll test this behaviour later + const {saveCookiesStub, restoreCookiesStub} = stubCookies(request); + + request._doRequest((error, response) => { + if (error) { + reject(error); + return; + } + assert.equal(error, null); + assert.equal(response.statusCode, 200); - sinon.assert.calledOnce(saveCookiesStub); - sinon.assert.calledOnce(restoreCookiesStub); + sinon.assert.calledOnce(saveCookiesStub); + sinon.assert.calledOnce(restoreCookiesStub); - done(); + resolve(); + }); }); }); diff --git a/packages/express-test/test/example-app.test.js b/packages/express-test/test/example-app.test.js index eee811cbb..140b124d3 100644 --- a/packages/express-test/test/example-app.test.js +++ b/packages/express-test/test/example-app.test.js @@ -40,7 +40,7 @@ async function getExtendedAPIAgent() { } describe('Example App', function () { - before(async function () { + beforeAll(async function () { agent = await getAgent(); }); @@ -65,7 +65,7 @@ describe('Example App', function () { }); describe('API Agent with authentication in two steps', function () { - before(async function () { + beforeAll(async function () { agent = await getAPIAgent(); }); @@ -114,7 +114,7 @@ describe('Example App', function () { }); describe('API Agent with login function', function () { - before(async function () { + beforeAll(async function () { agent = await getExtendedAPIAgent(); await agent.login(); }); @@ -130,7 +130,7 @@ describe('Example App', function () { }); describe('Cookie clearing functionality', function () { - before(async function () { + beforeAll(async function () { agent = await getAPIAgent(); }); @@ -179,7 +179,7 @@ describe('Example App', function () { }); describe('Set & Expect', function () { - before(async function () { + beforeAll(async function () { agent = await getAgent(); }); diff --git a/packages/logging/test/logging.test.js b/packages/logging/test/logging.test.js index 0cddfbe57..0c997e40d 100644 --- a/packages/logging/test/logging.test.js +++ b/packages/logging/test/logging.test.js @@ -69,122 +69,136 @@ describe('Logging', function () { // in Bunyan 1.8.3 they have changed this behaviour // they are trying to find the err.message attribute and forward this as msg property // our PrettyStream implementation can't handle this case - it('ensure stdout write properties', function (done) { - sandbox.stub(PrettyStream.prototype, 'write').callsFake(function (data) { - assert.notEqual(data.req, null); - assert.notEqual(data.req.headers, null); - assert.equal(data.req.body, undefined); - assert.notEqual(data.res, null); - assert.notEqual(data.err, null); - assert.equal(data.name, 'testLogging'); - assert.equal(data.msg, 'message'); - done(); - }); + it('ensure stdout write properties', async function () { + await new Promise((resolve) => { + sandbox.stub(PrettyStream.prototype, 'write').callsFake(function (data) { + assert.notEqual(data.req, null); + assert.notEqual(data.req.headers, null); + assert.equal(data.req.body, undefined); + assert.notEqual(data.res, null); + assert.notEqual(data.err, null); + assert.equal(data.name, 'testLogging'); + assert.equal(data.msg, 'message'); + resolve(); + }); - var ghostLogger = new GhostLogger({name: 'testLogging'}); - ghostLogger.info({err: new Error('message'), req: {body: {}, headers: {}}, res: {getHeaders: () => ({})}}); + var ghostLogger = new GhostLogger({name: 'testLogging'}); + ghostLogger.info({err: new Error('message'), req: {body: {}, headers: {}}, res: {getHeaders: () => ({})}}); + }); }); - it('ensure stdout write properties with custom message', function (done) { - sandbox.stub(PrettyStream.prototype, 'write').callsFake(function (data) { - assert.notEqual(data, null); - assert.equal(data.name, 'Log'); - assert.equal(data.msg, 'A handled error! Original message'); - done(); - }); + it('ensure stdout write properties with custom message', async function () { + await new Promise((resolve) => { + sandbox.stub(PrettyStream.prototype, 'write').callsFake(function (data) { + assert.notEqual(data, null); + assert.equal(data.name, 'Log'); + assert.equal(data.msg, 'A handled error! Original message'); + resolve(); + }); - var ghostLogger = new GhostLogger(); - ghostLogger.warn('A handled error!', new Error('Original message')); + var ghostLogger = new GhostLogger(); + ghostLogger.warn('A handled error!', new Error('Original message')); + }); }); - it('ensure stdout write properties with object', function (done) { - sandbox.stub(PrettyStream.prototype, 'write').callsFake(function (data) { - assert.notEqual(data.err, null); - assert.equal(data.test, 2); - assert.equal(data.name, 'Log'); - assert.equal(data.msg, 'Got an error from 3rd party service X! Resource could not be found.'); - done(); - }); + it('ensure stdout write properties with object', async function () { + await new Promise((resolve) => { + sandbox.stub(PrettyStream.prototype, 'write').callsFake(function (data) { + assert.notEqual(data.err, null); + assert.equal(data.test, 2); + assert.equal(data.name, 'Log'); + assert.equal(data.msg, 'Got an error from 3rd party service X! Resource could not be found.'); + resolve(); + }); - var ghostLogger = new GhostLogger(); - ghostLogger.error({err: new errors.NotFoundError(), test: 2}, 'Got an error from 3rd party service X!'); + var ghostLogger = new GhostLogger(); + ghostLogger.error({err: new errors.NotFoundError(), test: 2}, 'Got an error from 3rd party service X!'); + }); }); - it('ensure stdout write metadata properties', function (done) { - sandbox.stub(PrettyStream.prototype, 'write').callsFake(function (data) { - assert.equal(data.version, 2); - assert.equal(data.msg, 'Message to be logged!'); - done(); - }); + it('ensure stdout write metadata properties', async function () { + await new Promise((resolve) => { + sandbox.stub(PrettyStream.prototype, 'write').callsFake(function (data) { + assert.equal(data.version, 2); + assert.equal(data.msg, 'Message to be logged!'); + resolve(); + }); - var ghostLogger = new GhostLogger({metadata: {version: 2}}); - ghostLogger.info('Message to be logged!'); + var ghostLogger = new GhostLogger({metadata: {version: 2}}); + ghostLogger.info('Message to be logged!'); + }); }); - it('ensure stdout write properties with util.format', function (done) { - sandbox.stub(PrettyStream.prototype, 'write').callsFake(function (data) { - assert.notEqual(data, null); - assert.equal(data.name, 'Log'); - assert.equal(data.msg, 'Message with format'); - done(); - }); + it('ensure stdout write properties with util.format', async function () { + await new Promise((resolve) => { + sandbox.stub(PrettyStream.prototype, 'write').callsFake(function (data) { + assert.notEqual(data, null); + assert.equal(data.name, 'Log'); + assert.equal(data.msg, 'Message with format'); + resolve(); + }); - var ghostLogger = new GhostLogger(); - var thing = 'format'; - ghostLogger.info('Message with %s', thing); + var ghostLogger = new GhostLogger(); + var thing = 'format'; + ghostLogger.info('Message with %s', thing); + }); }); - it('redact sensitive data with request body', function (done) { - sandbox.stub(PrettyStream.prototype, 'write').callsFake(function (data) { - assert.notEqual(data.req.body.password, null); - assert.equal(data.req.body.password, '**REDACTED**'); - assert.notEqual(data.req.body.data.attributes.pin, null); - assert.equal(data.req.body.data.attributes.pin, '**REDACTED**'); - assert.notEqual(data.req.body.data.attributes.test, null); - assert.notEqual(data.err, null); - assert.notEqual(data.err.errorDetails, null); - done(); - }); + it('redact sensitive data with request body', async function () { + await new Promise((resolve) => { + sandbox.stub(PrettyStream.prototype, 'write').callsFake(function (data) { + assert.notEqual(data.req.body.password, null); + assert.equal(data.req.body.password, '**REDACTED**'); + assert.notEqual(data.req.body.data.attributes.pin, null); + assert.equal(data.req.body.data.attributes.pin, '**REDACTED**'); + assert.notEqual(data.req.body.data.attributes.test, null); + assert.notEqual(data.err, null); + assert.notEqual(data.err.errorDetails, null); + resolve(); + }); - var ghostLogger = new GhostLogger({logBody: true}); + var ghostLogger = new GhostLogger({logBody: true}); - ghostLogger.error({ - err: new errors.IncorrectUsageError({message: 'Hallo', errorDetails: []}), - req: { - body: { - password: '12345678', - data: { - attributes: { - pin: '1234', - test: 'ja' + ghostLogger.error({ + err: new errors.IncorrectUsageError({message: 'Hallo', errorDetails: []}), + req: { + body: { + password: '12345678', + data: { + attributes: { + pin: '1234', + test: 'ja' + } } + }, + headers: { + authorization: 'secret', + Connection: 'keep-alive' } }, - headers: { - authorization: 'secret', - Connection: 'keep-alive' - } - }, - res: {getHeaders: () => ({})} + res: {getHeaders: () => ({})} + }); }); }); - it('gelf writes a log message', function (done) { - sandbox.stub(GelfStream.prototype, '_write').callsFake(function (data) { - assert.notEqual(data.err, null); - done(); - }); + it('gelf writes a log message', async function () { + await new Promise((resolve) => { + sandbox.stub(GelfStream.prototype, '_write').callsFake(function (data) { + assert.notEqual(data.err, null); + resolve(); + }); - var ghostLogger = new GhostLogger({ - transports: ['gelf'], - gelf: { - host: 'localhost', - port: 12201 - } - }); + var ghostLogger = new GhostLogger({ + transports: ['gelf'], + gelf: { + host: 'localhost', + port: 12201 + } + }); - ghostLogger.error(new errors.NotFoundError()); - assert.equal(GelfStream.prototype._write.called, true); + ghostLogger.error(new errors.NotFoundError()); + assert.equal(GelfStream.prototype._write.called, true); + }); }); it('gelf uses default host and port when not provided', function () { @@ -212,23 +226,25 @@ describe('Logging', function () { assert.equal(GelfStream.prototype._write.called, false); }); - it('loggly does only stream certain errors', function (done) { - sandbox.stub(Bunyan2Loggly.prototype, 'write').callsFake(function (data) { - assert.notEqual(data.err, null); - done(); - }); + it('loggly does only stream certain errors', async function () { + await new Promise((resolve) => { + sandbox.stub(Bunyan2Loggly.prototype, 'write').callsFake(function (data) { + assert.notEqual(data.err, null); + resolve(); + }); - var ghostLogger = new GhostLogger({ - transports: ['loggly'], - loggly: { - token: 'invalid', - subdomain: 'invalid', - match: 'level:critical' - } - }); + var ghostLogger = new GhostLogger({ + transports: ['loggly'], + loggly: { + token: 'invalid', + subdomain: 'invalid', + match: 'level:critical' + } + }); - ghostLogger.error(new errors.InternalServerError()); - assert.equal(Bunyan2Loggly.prototype.write.called, true); + ghostLogger.error(new errors.InternalServerError()); + assert.equal(Bunyan2Loggly.prototype.write.called, true); + }); }); it('loggly does not stream non-critical errors when matching critical', function () { @@ -295,23 +311,25 @@ describe('Logging', function () { assert.equal(Bunyan2Loggly.prototype.write.called, true); }); - it('loggly does stream errors that match normal level', function (done) { - sandbox.stub(Bunyan2Loggly.prototype, 'write').callsFake(function (data) { - assert.notEqual(data.err, null); - done(); - }); + it('loggly does stream errors that match normal level', async function () { + await new Promise((resolve) => { + sandbox.stub(Bunyan2Loggly.prototype, 'write').callsFake(function (data) { + assert.notEqual(data.err, null); + resolve(); + }); - var ghostLogger = new GhostLogger({ - transports: ['loggly'], - loggly: { - token: 'invalid', - subdomain: 'invalid', - match: 'level:normal' - } - }); + var ghostLogger = new GhostLogger({ + transports: ['loggly'], + loggly: { + token: 'invalid', + subdomain: 'invalid', + match: 'level:normal' + } + }); - ghostLogger.error(new errors.NotFoundError()); - assert.equal(Bunyan2Loggly.prototype.write.called, true); + ghostLogger.error(new errors.NotFoundError()); + assert.equal(Bunyan2Loggly.prototype.write.called, true); + }); }); it('loggly match can evaluate with null err payload', function () { @@ -330,66 +348,72 @@ describe('Logging', function () { assert.equal(Bunyan2Loggly.prototype.write.called, true); }); - it('loggly does stream errors that match an one of multiple match statements', function (done) { - sandbox.stub(Bunyan2Loggly.prototype, 'write').callsFake(function (data) { - assert.notEqual(data.err, null); - done(); - }); + it('loggly does stream errors that match an one of multiple match statements', async function () { + await new Promise((resolve) => { + sandbox.stub(Bunyan2Loggly.prototype, 'write').callsFake(function (data) { + assert.notEqual(data.err, null); + resolve(); + }); - var ghostLogger = new GhostLogger({ - transports: ['loggly'], - loggly: { - token: 'invalid', - subdomain: 'invalid', - match: 'level:critical|statusCode:404' - } - }); + var ghostLogger = new GhostLogger({ + transports: ['loggly'], + loggly: { + token: 'invalid', + subdomain: 'invalid', + match: 'level:critical|statusCode:404' + } + }); - ghostLogger.error(new errors.NotFoundError()); - assert.equal(Bunyan2Loggly.prototype.write.called, true); + ghostLogger.error(new errors.NotFoundError()); + assert.equal(Bunyan2Loggly.prototype.write.called, true); + }); }); - it('loggly does stream errors that match status code: full example', function (done) { - sandbox.stub(Bunyan2Loggly.prototype, 'write').callsFake(function (data) { - assert.notEqual(data.err, null); - assert.notEqual(data.req, null); - assert.notEqual(data.res, null); - done(); - }); + it('loggly does stream errors that match status code: full example', async function () { + await new Promise((resolve) => { + sandbox.stub(Bunyan2Loggly.prototype, 'write').callsFake(function (data) { + assert.notEqual(data.err, null); + assert.notEqual(data.req, null); + assert.notEqual(data.res, null); + resolve(); + }); - var ghostLogger = new GhostLogger({ - transports: ['loggly'], - loggly: { - token: 'invalid', - subdomain: 'invalid', - match: 'statusCode:404' - } - }); + var ghostLogger = new GhostLogger({ + transports: ['loggly'], + loggly: { + token: 'invalid', + subdomain: 'invalid', + match: 'statusCode:404' + } + }); - ghostLogger.error({ - err: new errors.NotFoundError(), - req: {body: {password: '12345678', data: {attributes: {pin: '1234', test: 'ja'}}}}, - res: {getHeaders: () => ({})} + ghostLogger.error({ + err: new errors.NotFoundError(), + req: {body: {password: '12345678', data: {attributes: {pin: '1234', test: 'ja'}}}}, + res: {getHeaders: () => ({})} + }); + assert.equal(Bunyan2Loggly.prototype.write.called, true); }); - assert.equal(Bunyan2Loggly.prototype.write.called, true); }); - it('loggly does only stream certain errors: match is not defined -> log everything', function (done) { - sandbox.stub(Bunyan2Loggly.prototype, 'write').callsFake(function (data) { - assert.notEqual(data.err, null); - done(); - }); + it('loggly does only stream certain errors: match is not defined -> log everything', async function () { + await new Promise((resolve) => { + sandbox.stub(Bunyan2Loggly.prototype, 'write').callsFake(function (data) { + assert.notEqual(data.err, null); + resolve(); + }); - var ghostLogger = new GhostLogger({ - transports: ['loggly'], - loggly: { - token: 'invalid', - subdomain: 'invalid' - } - }); + var ghostLogger = new GhostLogger({ + transports: ['loggly'], + loggly: { + token: 'invalid', + subdomain: 'invalid' + } + }); - ghostLogger.error(new errors.NotFoundError()); - assert.equal(Bunyan2Loggly.prototype.write.called, true); + ghostLogger.error(new errors.NotFoundError()); + assert.equal(Bunyan2Loggly.prototype.write.called, true); + }); }); it('elasticsearch should make a stream', function () { @@ -430,21 +454,23 @@ describe('Logging', function () { }, 'hello', 1); }); - it('http writes a log message', function (done) { - sandbox.stub(HttpStream.prototype, 'write').callsFake(function (data) { - assert.notEqual(data.err, null); - done(); - }); + it('http writes a log message', async function () { + await new Promise((resolve) => { + sandbox.stub(HttpStream.prototype, 'write').callsFake(function (data) { + assert.notEqual(data.err, null); + resolve(); + }); - var ghostLogger = new GhostLogger({ - transports: ['http'], - http: { - host: 'http://localhost' - } - }); + var ghostLogger = new GhostLogger({ + transports: ['http'], + http: { + host: 'http://localhost' + } + }); - ghostLogger.error(new errors.NotFoundError()); - assert.equal(HttpStream.prototype.write.called, true); + ghostLogger.error(new errors.NotFoundError()); + assert.equal(HttpStream.prototype.write.called, true); + }); }); it('http does not write an info log in error mode', function () { @@ -691,71 +717,75 @@ describe('Logging', function () { }); describe('serialization', function () { - it('serializes error into correct object', function (done) { - const err = new errors.NotFoundError(); + it('serializes error into correct object', async function () { + await new Promise((resolve) => { + const err = new errors.NotFoundError(); + + sandbox.stub(Bunyan2Loggly.prototype, 'write').callsFake(function (data) { + assert.notEqual(data.err, null); + assert.equal(data.err.id, err.id); + assert.equal(data.err.domain, 'localhost'); + assert.equal(data.err.code, null); + assert.equal(data.err.name, err.errorType); + assert.equal(data.err.statusCode, err.statusCode); + assert.equal(data.err.level, err.level); + assert.equal(data.err.message, err.message); + assert.equal(data.err.context, undefined); + assert.equal(data.err.help, undefined); + assert.notEqual(data.err.stack, null); + assert.equal(data.err.hideStack, undefined); + assert.equal(data.err.errorDetails, undefined); + resolve(); + }); - sandbox.stub(Bunyan2Loggly.prototype, 'write').callsFake(function (data) { - assert.notEqual(data.err, null); - assert.equal(data.err.id, err.id); - assert.equal(data.err.domain, 'localhost'); - assert.equal(data.err.code, null); - assert.equal(data.err.name, err.errorType); - assert.equal(data.err.statusCode, err.statusCode); - assert.equal(data.err.level, err.level); - assert.equal(data.err.message, err.message); - assert.equal(data.err.context, undefined); - assert.equal(data.err.help, undefined); - assert.notEqual(data.err.stack, null); - assert.equal(data.err.hideStack, undefined); - assert.equal(data.err.errorDetails, undefined); - done(); - }); + const ghostLogger = new GhostLogger({ + transports: ['loggly'], + loggly: { + token: 'invalid', + subdomain: 'invalid' + } + }); + ghostLogger.error({ + err + }); - const ghostLogger = new GhostLogger({ - transports: ['loggly'], - loggly: { - token: 'invalid', - subdomain: 'invalid' - } + assert.equal(Bunyan2Loggly.prototype.write.called, true); }); - ghostLogger.error({ - err - }); - - assert.equal(Bunyan2Loggly.prototype.write.called, true); }); - it('stringifies meta properties', function (done) { - sandbox.stub(Bunyan2Loggly.prototype, 'write').callsFake(function (data) { - assert.notEqual(data.err, null); - assert.equal(data.err.context, '{"a":"b"}'); - assert.equal(data.err.errorDetails, '{"c":"d"}'); - assert.equal(data.err.help, '{"b":"a"}'); - done(); - }); + it('stringifies meta properties', async function () { + await new Promise((resolve) => { + sandbox.stub(Bunyan2Loggly.prototype, 'write').callsFake(function (data) { + assert.notEqual(data.err, null); + assert.equal(data.err.context, '{"a":"b"}'); + assert.equal(data.err.errorDetails, '{"c":"d"}'); + assert.equal(data.err.help, '{"b":"a"}'); + resolve(); + }); - const ghostLogger = new GhostLogger({ - transports: ['loggly'], - loggly: { - token: 'invalid', - subdomain: 'invalid' - } - }); - ghostLogger.error({ - err: new errors.NotFoundError({ - context: { - a: 'b' - }, - errorDetails: { - c: 'd' - }, - help: { - b: 'a' + const ghostLogger = new GhostLogger({ + transports: ['loggly'], + loggly: { + token: 'invalid', + subdomain: 'invalid' } - }) - }); + }); + ghostLogger.error({ + err: new errors.NotFoundError({ + context: { + a: 'b' + }, + errorDetails: { + c: 'd' + }, + help: { + b: 'a' + } + }) + }); - assert.equal(Bunyan2Loggly.prototype.write.called, true); + assert.equal(Bunyan2Loggly.prototype.write.called, true); + }); }); it('serializes req extra and queueDepth fields when present', function () { diff --git a/packages/metrics/test/metrics.test.js b/packages/metrics/test/metrics.test.js index 3495c35bf..69b3e3488 100644 --- a/packages/metrics/test/metrics.test.js +++ b/packages/metrics/test/metrics.test.js @@ -8,6 +8,17 @@ const {getProcessRoot} = require('@tryghost/root-utils'); const GhostMetrics = require('../lib/GhostMetrics'); const sandbox = sinon.createSandbox(); +// Vitest sets process.env.MODE to 'test' which interferes with GhostMetrics mode detection +const originalMode = process.env.MODE; +beforeEach(function () { + delete process.env.MODE; +}); +afterEach(function () { + if (originalMode !== undefined) { + process.env.MODE = originalMode; + } +}); + const loggingConfigPath = path.join(getProcessRoot(), 'loggingrc'); describe('Metrics config', function () { @@ -51,25 +62,27 @@ describe('Logging', function () { sandbox.restore(); }); - it('stdout transport works', function (done) { + it('stdout transport works', async function () { const name = 'test-metric'; const value = 101; - sandbox.stub(PrettyStream.prototype, 'write').callsFake(function (data) { - assert.notEqual(data.msg, undefined); - assert.equal(data.msg, `Metric ${name}: ${JSON.stringify(value)}`); - done(); - }); + await new Promise((resolve) => { + sandbox.stub(PrettyStream.prototype, 'write').callsFake(function (data) { + assert.notEqual(data.msg, undefined); + assert.equal(data.msg, `Metric ${name}: ${JSON.stringify(value)}`); + resolve(); + }); - const ghostMetrics = new GhostMetrics({ - metrics: { - transports: ['stdout'] - } + const ghostMetrics = new GhostMetrics({ + metrics: { + transports: ['stdout'] + } + }); + ghostMetrics.metric(name, value); }); - ghostMetrics.metric(name, value); }); - it('elasticsearch transport works', function (done) { + it('elasticsearch transport works', async function () { const name = 'test-metric'; const value = 101; @@ -88,17 +101,19 @@ describe('Logging', function () { } }); - sandbox.stub(ElasticSearch.prototype, 'index').callsFake(function (data, index) { - assert.notEqual(data.metadata, undefined); - assert.equal(data.metadata.id, ghostMetrics.metadata.id); - assert.equal(data.value, value); + await new Promise((resolve) => { + sandbox.stub(ElasticSearch.prototype, 'index').callsFake(function (data, index) { + assert.notEqual(data.metadata, undefined); + assert.equal(data.metadata.id, ghostMetrics.metadata.id); + assert.equal(data.value, value); - // ElasticSearch shipper prefixes metric names to avoid polluting index namespace - assert.equal(index, 'metrics-' + name); - done(); - }); + // ElasticSearch shipper prefixes metric names to avoid polluting index namespace + assert.equal(index, 'metrics-' + name); + resolve(); + }); - ghostMetrics.metric(name, value); + ghostMetrics.metric(name, value); + }); }); it('throws for invalid transport', function () { diff --git a/packages/mw-error-handler/test/mw-error-handler.test.js b/packages/mw-error-handler/test/mw-error-handler.test.js index 0612f4d4d..009ab3b50 100644 --- a/packages/mw-error-handler/test/mw-error-handler.test.js +++ b/packages/mw-error-handler/test/mw-error-handler.test.js @@ -16,94 +16,106 @@ const { } = require('..'); describe('Prepare Error', function () { - it('Correctly prepares a non-Ghost error', function (done) { - prepareError(new Error('test!'), {}, { - set: () => {} - }, (err) => { - assert.equal(err.statusCode, 500); - assert.equal(err.name, 'InternalServerError'); - assert.equal(err.message, 'An unexpected error occurred, please try again.'); - assert.equal(err.context, 'test!'); - assert.equal(err.code, 'UNEXPECTED_ERROR'); - assert.ok(err.stack.startsWith('Error: test!')); - done(); + it('Correctly prepares a non-Ghost error', async function () { + await new Promise((resolve) => { + prepareError(new Error('test!'), {}, { + set: () => {} + }, (err) => { + assert.equal(err.statusCode, 500); + assert.equal(err.name, 'InternalServerError'); + assert.equal(err.message, 'An unexpected error occurred, please try again.'); + assert.equal(err.context, 'test!'); + assert.equal(err.code, 'UNEXPECTED_ERROR'); + assert.ok(err.stack.startsWith('Error: test!')); + resolve(); + }); }); }); - it('Correctly prepares a Ghost error', function (done) { - prepareError(new InternalServerError({message: 'Handled Error', context: 'Details'}), {}, { - set: () => {} - }, (err) => { - assert.equal(err.statusCode, 500); - assert.equal(err.name, 'InternalServerError'); - assert.equal(err.message, 'Handled Error'); - assert.equal(err.context, 'Details'); - assert.ok(err.stack.startsWith('InternalServerError: Handled Error')); - done(); + it('Correctly prepares a Ghost error', async function () { + await new Promise((resolve) => { + prepareError(new InternalServerError({message: 'Handled Error', context: 'Details'}), {}, { + set: () => {} + }, (err) => { + assert.equal(err.statusCode, 500); + assert.equal(err.name, 'InternalServerError'); + assert.equal(err.message, 'Handled Error'); + assert.equal(err.context, 'Details'); + assert.ok(err.stack.startsWith('InternalServerError: Handled Error')); + resolve(); + }); }); }); - it('Correctly prepares a 404 error', function (done) { + it('Correctly prepares a 404 error', async function () { let error = {message: 'Oh dear', statusCode: 404}; - prepareError(error, {}, { - set: () => {} - }, (err) => { - assert.equal(err.statusCode, 404); - assert.equal(err.name, 'NotFoundError'); - assert.ok(err.stack.startsWith('NotFoundError: Resource could not be found')); - assert.equal(err.hideStack, true); - done(); + await new Promise((resolve) => { + prepareError(error, {}, { + set: () => {} + }, (err) => { + assert.equal(err.statusCode, 404); + assert.equal(err.name, 'NotFoundError'); + assert.ok(err.stack.startsWith('NotFoundError: Resource could not be found')); + assert.equal(err.hideStack, true); + resolve(); + }); }); }); - it('Correctly prepares an error array', function (done) { - prepareError([new Error('test!')], {}, { - set: () => {} - }, (err) => { - assert.equal(err.statusCode, 500); - assert.equal(err.name, 'InternalServerError'); - assert.ok(err.stack.startsWith('Error: test!')); - done(); + it('Correctly prepares an error array', async function () { + await new Promise((resolve) => { + prepareError([new Error('test!')], {}, { + set: () => {} + }, (err) => { + assert.equal(err.statusCode, 500); + assert.equal(err.name, 'InternalServerError'); + assert.ok(err.stack.startsWith('Error: test!')); + resolve(); + }); }); }); - it('Correctly prepares a handlebars error', function (done) { + it('Correctly prepares a handlebars error', async function () { let error = new Error('obscure handlebars message!'); error.stack += '\n'; error.stack += path.join('node_modules', 'handlebars', 'something'); - prepareError(error, {}, { - set: () => {} - }, (err) => { - assert.equal(err.statusCode, 400); - assert.equal(err.name, 'IncorrectUsageError'); - // TODO: consider if the message should be trusted here - assert.equal(err.message, 'obscure handlebars message!'); - assert.ok(err.stack.startsWith('Error: obscure handlebars message!')); - done(); + await new Promise((resolve) => { + prepareError(error, {}, { + set: () => {} + }, (err) => { + assert.equal(err.statusCode, 400); + assert.equal(err.name, 'IncorrectUsageError'); + // TODO: consider if the message should be trusted here + assert.equal(err.message, 'obscure handlebars message!'); + assert.ok(err.stack.startsWith('Error: obscure handlebars message!')); + resolve(); + }); }); }); - it('Correctly prepares an express-hbs error', function (done) { + it('Correctly prepares an express-hbs error', async function () { let error = new Error('obscure express-hbs message!'); error.stack += '\n'; error.stack += path.join('node_modules', 'express-hbs', 'lib'); - prepareError(error, {}, { - set: () => {} - }, (err) => { - assert.equal(err.statusCode, 400); - assert.equal(err.name, 'IncorrectUsageError'); - assert.equal(err.message, 'obscure express-hbs message!'); - assert.ok(err.stack.startsWith('Error: obscure express-hbs message!')); - done(); + await new Promise((resolve) => { + prepareError(error, {}, { + set: () => {} + }, (err) => { + assert.equal(err.statusCode, 400); + assert.equal(err.name, 'IncorrectUsageError'); + assert.equal(err.message, 'obscure express-hbs message!'); + assert.ok(err.stack.startsWith('Error: obscure express-hbs message!')); + resolve(); + }); }); }); - it('Correctly prepares a known ER_WRONG_VALUE mysql2 error', function (done) { + it('Correctly prepares a known ER_WRONG_VALUE mysql2 error', async function () { let error = new Error('select anything from anywhere where something = anything;'); error.stack += '\n'; @@ -112,21 +124,23 @@ describe('Prepare Error', function () { error.sql = 'select anything from anywhere where something = anything;'; error.sqlMessage = 'Incorrect DATETIME value: 3234234234'; - prepareError(error, {}, { - set: () => {} - }, (err) => { - assert.equal(err.statusCode, 422); - assert.equal(err.name, 'ValidationError'); - assert.equal(err.message, 'Invalid value'); - assert.equal(err.code, 'ER_WRONG_VALUE'); - assert.equal(err.sqlErrorCode, 'ER_WRONG_VALUE'); - assert.equal(err.sql, 'select anything from anywhere where something = anything;'); - assert.equal(err.sqlMessage, 'Incorrect DATETIME value: 3234234234'); - done(); + await new Promise((resolve) => { + prepareError(error, {}, { + set: () => {} + }, (err) => { + assert.equal(err.statusCode, 422); + assert.equal(err.name, 'ValidationError'); + assert.equal(err.message, 'Invalid value'); + assert.equal(err.code, 'ER_WRONG_VALUE'); + assert.equal(err.sqlErrorCode, 'ER_WRONG_VALUE'); + assert.equal(err.sql, 'select anything from anywhere where something = anything;'); + assert.equal(err.sqlMessage, 'Incorrect DATETIME value: 3234234234'); + resolve(); + }); }); }); - it('Correctly prepares an unknown mysql2 error', function (done) { + it('Correctly prepares an unknown mysql2 error', async function () { let error = new Error('select anything from anywhere where something = anything;'); error.stack += '\n'; @@ -135,46 +149,52 @@ describe('Prepare Error', function () { error.sql = 'select anything from anywhere where something = anything;'; error.sqlMessage = 'Incorrect value: erororoor'; - prepareError(error, {}, { - set: () => {} - }, (err) => { - assert.equal(err.statusCode, 500); - assert.equal(err.name, 'InternalServerError'); - assert.equal(err.message, 'An unexpected error occurred, please try again.'); - assert.equal(err.code, 'UNEXPECTED_ERROR'); - assert.equal(err.sqlErrorCode, 'ER_BAD_FIELD_ERROR'); - assert.equal(err.sql, 'select anything from anywhere where something = anything;'); - assert.equal(err.sqlMessage, 'Incorrect value: erororoor'); - done(); + await new Promise((resolve) => { + prepareError(error, {}, { + set: () => {} + }, (err) => { + assert.equal(err.statusCode, 500); + assert.equal(err.name, 'InternalServerError'); + assert.equal(err.message, 'An unexpected error occurred, please try again.'); + assert.equal(err.code, 'UNEXPECTED_ERROR'); + assert.equal(err.sqlErrorCode, 'ER_BAD_FIELD_ERROR'); + assert.equal(err.sql, 'select anything from anywhere where something = anything;'); + assert.equal(err.sqlMessage, 'Incorrect value: erororoor'); + resolve(); + }); }); }); }); describe('Prepare Stack', function () { - it('Correctly prepares the stack for an error', function (done) { - prepareStack(new Error('test!'), {}, {}, (err) => { - // Includes "Stack Trace" text prepending human readable trace - assert.ok(err.stack.startsWith('Error: test!\nStack Trace:')); - done(); + it('Correctly prepares the stack for an error', async function () { + await new Promise((resolve) => { + prepareStack(new Error('test!'), {}, {}, (err) => { + // Includes "Stack Trace" text prepending human readable trace + assert.ok(err.stack.startsWith('Error: test!\nStack Trace:')); + resolve(); + }); }); }); }); describe('Prepare Error Cache Control', function () { - it('Sets private cache control by default', function (done) { + it('Sets private cache control by default', async function () { const res = { set: sinon.spy() }; - prepareErrorCacheControl()(new Error('generic error'), {}, res, () => { - assert(res.set.calledOnce); - assert(res.set.calledWith({ - 'Cache-Control': cacheControlValues.private - })); - done(); + await new Promise((resolve) => { + prepareErrorCacheControl()(new Error('generic error'), {}, res, () => { + assert(res.set.calledOnce); + assert(res.set.calledWith({ + 'Cache-Control': cacheControlValues.private + })); + resolve(); + }); }); }); - it('Sets private cache-control header for user-specific 404 responses', function (done) { + it('Sets private cache-control header for user-specific 404 responses', async function () { const req = { method: 'GET', get: (header) => { @@ -186,16 +206,18 @@ describe('Prepare Error Cache Control', function () { const res = { set: sinon.spy() }; - prepareErrorCacheControl()(new NotFoundError(), req, res, () => { - assert(res.set.calledOnce); - assert(res.set.calledWith({ - 'Cache-Control': cacheControlValues.private - })); - done(); + await new Promise((resolve) => { + prepareErrorCacheControl()(new NotFoundError(), req, res, () => { + assert(res.set.calledOnce); + assert(res.set.calledWith({ + 'Cache-Control': cacheControlValues.private + })); + resolve(); + }); }); }); - it('Sets noCache cache-control header for non-user-specific 404 responses', function (done) { + it('Sets noCache cache-control header for non-user-specific 404 responses', async function () { const req = { method: 'GET', get: () => { @@ -208,78 +230,88 @@ describe('Prepare Error Cache Control', function () { return false; } }; - prepareErrorCacheControl()(new NotFoundError(), req, res, () => { - assert(res.set.calledOnce); - assert(res.set.calledWith({ - 'Cache-Control': cacheControlValues.noCacheDynamic - })); - done(); + await new Promise((resolve) => { + prepareErrorCacheControl()(new NotFoundError(), req, res, () => { + assert(res.set.calledOnce); + assert(res.set.calledWith({ + 'Cache-Control': cacheControlValues.noCacheDynamic + })); + resolve(); + }); }); }); }); describe('Error renderers', function () { - it('Renders JSON', function (done) { - jsonErrorRenderer(new Error('test!'), {}, { - json: (data) => { - assert.equal(data.errors.length, 1); - assert.equal(data.errors[0].message, 'test!'); - done(); - } - }, () => {}); + it('Renders JSON', async function () { + await new Promise((resolve) => { + jsonErrorRenderer(new Error('test!'), {}, { + json: (data) => { + assert.equal(data.errors.length, 1); + assert.equal(data.errors[0].message, 'test!'); + resolve(); + } + }, () => {}); + }); }); - it('Handles unknown errors when preparing user message', function (done) { - jsonErrorRenderer(new RangeError('test!'), { - frameOptions: { - docName: 'oembed', - method: 'read' - } - }, { - json: (data) => { - assert.equal(data.errors.length, 1); - assert.equal(data.errors[0].message, 'Unknown error - RangeError, cannot read oembed.'); - assert.equal(data.errors[0].context, 'test!'); - done(); - } - }, () => {}); + it('Handles unknown errors when preparing user message', async function () { + await new Promise((resolve) => { + jsonErrorRenderer(new RangeError('test!'), { + frameOptions: { + docName: 'oembed', + method: 'read' + } + }, { + json: (data) => { + assert.equal(data.errors.length, 1); + assert.equal(data.errors[0].message, 'Unknown error - RangeError, cannot read oembed.'); + assert.equal(data.errors[0].context, 'test!'); + resolve(); + } + }, () => {}); + }); }); - it('Uses templates when required', function (done) { - jsonErrorRenderer(new InternalServerError({ - message: 'test!' - }), { - frameOptions: { - docName: 'blog', - method: 'browse' - } - }, { - json: (data) => { - assert.equal(data.errors.length, 1); - assert.equal(data.errors[0].message, 'Internal server error, cannot list blog.'); - assert.equal(data.errors[0].context, 'test!'); - done(); - } - }, () => {}); + it('Uses templates when required', async function () { + await new Promise((resolve) => { + jsonErrorRenderer(new InternalServerError({ + message: 'test!' + }), { + frameOptions: { + docName: 'blog', + method: 'browse' + } + }, { + json: (data) => { + assert.equal(data.errors.length, 1); + assert.equal(data.errors[0].message, 'Internal server error, cannot list blog.'); + assert.equal(data.errors[0].context, 'test!'); + resolve(); + } + }, () => {}); + }); }); - it('Uses defined message + context when available', function (done) { - jsonErrorRenderer(new InternalServerError({ - message: 'test!', - context: 'Image was too large.' - }), { - frameOptions: { - docName: 'images', - method: 'upload' - } - }, { - json: (data) => { - assert.equal(data.errors.length, 1); - assert.equal(data.errors[0].message, 'Internal server error, cannot upload image.'); - assert.equal(data.errors[0].context, 'test! Image was too large.'); - done(); - } - }, () => {}); + it('Uses defined message + context when available', async function () { + await new Promise((resolve) => { + jsonErrorRenderer(new InternalServerError({ + message: 'test!', + context: 'Image was too large.' + }), { + frameOptions: { + docName: 'images', + method: 'upload' + } + }, { + json: (data) => { + assert.equal(data.errors.length, 1); + assert.equal(data.errors[0].message, 'Internal server error, cannot upload image.'); + assert.equal(data.errors[0].context, 'test! Image was too large.'); + resolve(); + } + }, () => {}); + }); }); it('Exports the HTML renderer', function () { @@ -300,15 +332,17 @@ describe('Error renderers', function () { }); describe('Resource Not Found', function () { - it('Returns 404 Not Found Error for a generic case', function (done) { - resourceNotFound({}, {}, (error) => { - assert.equal(error.statusCode, 404); - assert.equal(error.message, 'Resource not found'); - done(); + it('Returns 404 Not Found Error for a generic case', async function () { + await new Promise((resolve) => { + resourceNotFound({}, {}, (error) => { + assert.equal(error.statusCode, 404); + assert.equal(error.message, 'Resource not found'); + resolve(); + }); }); }); - it('Returns 406 Request Not Acceptable Error for invalid version', function (done) { + it('Returns 406 Request Not Acceptable Error for invalid version', async function () { const req = { headers: { 'accept-version': 'foo' @@ -321,14 +355,16 @@ describe('Resource Not Found', function () { } }; - resourceNotFound(req, res, (error) => { - assert.equal(error.statusCode, 400); - assert.equal(error.message, 'Requested version is not supported.'); - done(); + await new Promise((resolve) => { + resourceNotFound(req, res, (error) => { + assert.equal(error.statusCode, 400); + assert.equal(error.message, 'Requested version is not supported.'); + resolve(); + }); }); }); - it('Returns 406 Request Not Acceptable Error for when requested version is behind current version', function (done) { + it('Returns 406 Request Not Acceptable Error for when requested version is behind current version', async function () { const req = { headers: { 'accept-version': 'v3.9' @@ -341,16 +377,18 @@ describe('Resource Not Found', function () { } }; - resourceNotFound(req, res, (error) => { - assert.equal(error.statusCode, 406); - assert.equal(error.message, 'Request could not be served, the endpoint was not found.'); - assert.equal(error.context, 'Provided client accept-version v3.9 is behind current Ghost version v4.3.'); - assert.equal(error.help, 'Try upgrading your Ghost API client.'); - done(); + await new Promise((resolve) => { + resourceNotFound(req, res, (error) => { + assert.equal(error.statusCode, 406); + assert.equal(error.message, 'Request could not be served, the endpoint was not found.'); + assert.equal(error.context, 'Provided client accept-version v3.9 is behind current Ghost version v4.3.'); + assert.equal(error.help, 'Try upgrading your Ghost API client.'); + resolve(); + }); }); }); - it('Returns 406 Request Not Acceptable Error for when requested version is ahead current version', function (done) { + it('Returns 406 Request Not Acceptable Error for when requested version is ahead current version', async function () { const req = { headers: { 'accept-version': 'v4.8' @@ -363,16 +401,18 @@ describe('Resource Not Found', function () { } }; - resourceNotFound(req, res, (error) => { - assert.equal(error.statusCode, 406); - assert.equal(error.message, 'Request could not be served, the endpoint was not found.'); - assert.equal(error.context, 'Provided client accept-version v4.8 is ahead of current Ghost version v4.3.'); - assert.equal(error.help, 'Try upgrading your Ghost install.'); - done(); + await new Promise((resolve) => { + resourceNotFound(req, res, (error) => { + assert.equal(error.statusCode, 406); + assert.equal(error.message, 'Request could not be served, the endpoint was not found.'); + assert.equal(error.context, 'Provided client accept-version v4.8 is ahead of current Ghost version v4.3.'); + assert.equal(error.help, 'Try upgrading your Ghost install.'); + resolve(); + }); }); }); - it('Returns 404 Not Found Error for when requested version is the same as current version', function (done) { + it('Returns 404 Not Found Error for when requested version is the same as current version', async function () { const req = { headers: { 'accept-version': 'v4.3' @@ -385,27 +425,33 @@ describe('Resource Not Found', function () { } }; - resourceNotFound(req, res, (error) => { - assert.equal(error.statusCode, 404); - assert.equal(error.message, 'Resource not found'); - done(); + await new Promise((resolve) => { + resourceNotFound(req, res, (error) => { + assert.equal(error.statusCode, 404); + assert.equal(error.message, 'Resource not found'); + resolve(); + }); }); }); describe('pageNotFound', function () { - it('returns 404 with special message when message not set', function (done) { - pageNotFound({}, {}, (error) => { - assert.equal(error.statusCode, 404); - assert.equal(error.message, 'Page not found'); - done(); + it('returns 404 with special message when message not set', async function () { + await new Promise((resolve) => { + pageNotFound({}, {}, (error) => { + assert.equal(error.statusCode, 404); + assert.equal(error.message, 'Page not found'); + resolve(); + }); }); }); - it('returns 404 with special message even if message is set', function (done) { - pageNotFound({message: 'uh oh'}, {}, (error) => { - assert.equal(error.statusCode, 404); - assert.equal(error.message, 'Page not found'); - done(); + it('returns 404 with special message even if message is set', async function () { + await new Promise((resolve) => { + pageNotFound({message: 'uh oh'}, {}, (error) => { + assert.equal(error.statusCode, 404); + assert.equal(error.message, 'Page not found'); + resolve(); + }); }); }); }); diff --git a/packages/pretty-stream/test/PrettyStream.test.js b/packages/pretty-stream/test/PrettyStream.test.js index e485d9c90..113ea3177 100644 --- a/packages/pretty-stream/test/PrettyStream.test.js +++ b/packages/pretty-stream/test/PrettyStream.test.js @@ -4,595 +4,467 @@ const Writable = require('stream').Writable; const sinon = require('sinon'); describe('PrettyStream', function () { + afterEach(function () { + sinon.restore(); + }); + describe('short mode', function () { - it('data.msg', function (done) { - var ghostPrettyStream = new PrettyStream({mode: 'short'}); - var writeStream = new Writable(); - - writeStream._write = function (data) { - data = data.toString(); - assert.equal(data, '[2016-07-01 00:00:00] \u001b[36mINFO\u001b[39m Ghost starts now.\n'); - done(); - }; - - ghostPrettyStream.pipe(writeStream); - - ghostPrettyStream.write(JSON.stringify({ - time: '2016-07-01 00:00:00', - level: 30, - msg: 'Ghost starts now.' - })); + it('data.msg', async function () { + await new Promise((resolve) => { + var ghostPrettyStream = new PrettyStream({mode: 'short'}); + var writeStream = new Writable(); + + writeStream._write = function (data) { + data = data.toString(); + assert.equal(data, '[2016-07-01 00:00:00] \u001b[36mINFO\u001b[39m Ghost starts now.\n'); + resolve(); + }; + + ghostPrettyStream.pipe(writeStream); + + ghostPrettyStream.write(JSON.stringify({ + time: '2016-07-01 00:00:00', + level: 30, + msg: 'Ghost starts now.' + })); + }); }); - it('data.err', function (done) { - var ghostPrettyStream = new PrettyStream({mode: 'short'}); - var writeStream = new Writable(); - - writeStream._write = function (data) { - data = data.toString(); - assert.equal(data, '[2016-07-01 00:00:00] \u001b[31mERROR\u001b[39m message\n\u001b[31m\n\u001b[31mHey Jude!\u001b[39m\n\n\u001b[1m\u001b[37mError Code: \u001b[39m\u001b[22m\n \u001b[90mHEY_JUDE\u001b[39m\n\n\u001b[90m----------------------------------------\u001b[39m\n\n\u001b[90mstack\u001b[39m\n\u001b[39m\n'); - done(); - }; - - ghostPrettyStream.pipe(writeStream); - - ghostPrettyStream.write(JSON.stringify({ - time: '2016-07-01 00:00:00', - level: 50, - msg: 'message', - err: { - message: 'Hey Jude!', - stack: 'stack', - code: 'HEY_JUDE' - } - })); + it('data.err', async function () { + await new Promise((resolve) => { + var ghostPrettyStream = new PrettyStream({mode: 'short'}); + var writeStream = new Writable(); + + writeStream._write = function (data) { + data = data.toString(); + assert.equal(data, '[2016-07-01 00:00:00] \u001b[31mERROR\u001b[39m message\n\u001b[31m\n\u001b[31mHey Jude!\u001b[39m\n\n\u001b[1m\u001b[37mError Code: \u001b[39m\u001b[22m\n \u001b[90mHEY_JUDE\u001b[39m\n\n\u001b[90m----------------------------------------\u001b[39m\n\n\u001b[90mstack\u001b[39m\n\u001b[39m\n'); + resolve(); + }; + + ghostPrettyStream.pipe(writeStream); + + ghostPrettyStream.write(JSON.stringify({ + time: '2016-07-01 00:00:00', + level: 50, + msg: 'message', + err: { + message: 'Hey Jude!', + stack: 'stack', + code: 'HEY_JUDE' + } + })); + }); }); - it('data.req && data.res', function (done) { - var ghostPrettyStream = new PrettyStream({mode: 'short'}); - var writeStream = new Writable(); - - writeStream._write = function (data) { - data = data.toString(); - assert.equal(data, '[2016-07-01 00:00:00] \u001b[36mINFO\u001b[39m "GET /test" \u001b[32m200\u001b[39m 39ms\n'); - done(); - }; - - ghostPrettyStream.pipe(writeStream); - - ghostPrettyStream.write(JSON.stringify({ - time: '2016-07-01 00:00:00', - level: 30, - req: { - originalUrl: '/test', - method: 'GET', - body: { - a: 'b' + it('data.req && data.res', async function () { + await new Promise((resolve) => { + var ghostPrettyStream = new PrettyStream({mode: 'short'}); + var writeStream = new Writable(); + + writeStream._write = function (data) { + data = data.toString(); + assert.equal(data, '[2016-07-01 00:00:00] \u001b[36mINFO\u001b[39m "GET /test" \u001b[32m200\u001b[39m 39ms\n'); + resolve(); + }; + + ghostPrettyStream.pipe(writeStream); + + ghostPrettyStream.write(JSON.stringify({ + time: '2016-07-01 00:00:00', + level: 30, + req: { + originalUrl: '/test', + method: 'GET', + body: { + a: 'b' + } + }, + res: { + statusCode: 200, + responseTime: '39ms' } - }, - res: { - statusCode: 200, - responseTime: '39ms' - } - })); + })); + }); }); - it('data.req && data.res && data.err', function (done) { - var ghostPrettyStream = new PrettyStream({mode: 'short'}); - var writeStream = new Writable(); - - writeStream._write = function (data) { - data = data.toString(); - assert.equal(data, '[2016-07-01 00:00:00] \u001b[31mERROR\u001b[39m "GET /test" \u001b[33m400\u001b[39m 39ms\n\u001b[31m\n\u001b[31mmessage\u001b[39m\n\n\u001b[90m----------------------------------------\u001b[39m\n\n\u001b[90mstack\u001b[39m\n\u001b[39m\n'); - done(); - }; - - ghostPrettyStream.pipe(writeStream); - - ghostPrettyStream.write(JSON.stringify({ - time: '2016-07-01 00:00:00', - level: 50, - req: { - originalUrl: '/test', - method: 'GET', - body: { - a: 'b' + it('data.req && data.res && data.err', async function () { + await new Promise((resolve) => { + var ghostPrettyStream = new PrettyStream({mode: 'short'}); + var writeStream = new Writable(); + + writeStream._write = function (data) { + data = data.toString(); + assert.equal(data, '[2016-07-01 00:00:00] \u001b[31mERROR\u001b[39m "GET /test" \u001b[33m400\u001b[39m 39ms\n\u001b[31m\n\u001b[31mmessage\u001b[39m\n\n\u001b[90m----------------------------------------\u001b[39m\n\n\u001b[90mstack\u001b[39m\n\u001b[39m\n'); + resolve(); + }; + + ghostPrettyStream.pipe(writeStream); + + ghostPrettyStream.write(JSON.stringify({ + time: '2016-07-01 00:00:00', + level: 50, + req: { + originalUrl: '/test', + method: 'GET', + body: { + a: 'b' + } + }, + res: { + statusCode: 400, + responseTime: '39ms' + }, + err: { + message: 'message', + stack: 'stack' } - }, - res: { - statusCode: 400, - responseTime: '39ms' - }, - err: { - message: 'message', - stack: 'stack' - } - })); + })); + }); }); }); describe('long mode', function () { - it('data.msg', function (done) { - var ghostPrettyStream = new PrettyStream({mode: 'long'}); - var writeStream = new Writable(); - - writeStream._write = function (data) { - data = data.toString(); - assert.equal(data, '[2016-07-01 00:00:00] \u001b[36mINFO\u001b[39m Ghost starts now.\n'); - done(); - }; - - ghostPrettyStream.pipe(writeStream); - - ghostPrettyStream.write(JSON.stringify({ - time: '2016-07-01 00:00:00', - level: 30, - msg: 'Ghost starts now.' - })); - }); - - it('data.err', function (done) { - var ghostPrettyStream = new PrettyStream({mode: 'long'}); - var writeStream = new Writable(); - - writeStream._write = function (data) { - data = data.toString(); - assert.equal(data, '[2016-07-01 00:00:00] \u001b[31mERROR\u001b[39m\n\u001b[31m\n\u001b[31mHey Jude!\u001b[39m\n\n\u001b[90m----------------------------------------\u001b[39m\n\n\u001b[90mstack\u001b[39m\n\u001b[39m\n\u001b[90m\u001b[39m\n'); - done(); - }; - - ghostPrettyStream.pipe(writeStream); - - ghostPrettyStream.write(JSON.stringify({ - time: '2016-07-01 00:00:00', - level: 50, - err: { - message: 'Hey Jude!', - stack: 'stack' - } - })); + it('data.msg', async function () { + await new Promise((resolve) => { + var ghostPrettyStream = new PrettyStream({mode: 'long'}); + var writeStream = new Writable(); + + writeStream._write = function (data) { + data = data.toString(); + assert.equal(data, '[2016-07-01 00:00:00] \u001b[36mINFO\u001b[39m Ghost starts now.\n'); + resolve(); + }; + + ghostPrettyStream.pipe(writeStream); + + ghostPrettyStream.write(JSON.stringify({ + time: '2016-07-01 00:00:00', + level: 30, + msg: 'Ghost starts now.' + })); + }); }); - it('data.req && data.res', function (done) { - var ghostPrettyStream = new PrettyStream({mode: 'long'}); - var writeStream = new Writable(); - - writeStream._write = function (data) { - data = data.toString(); - assert.equal(data, '[2016-07-01 00:00:00] \u001b[36mINFO\u001b[39m "GET /test" \u001b[32m200\u001b[39m 39ms\n\u001b[90m\n\u001b[33mREQ\u001b[39m\n\u001b[32mip: \u001b[39m 127.0.01\n\u001b[32moriginalUrl: \u001b[39m/test\n\u001b[32mmethod: \u001b[39m GET\n\u001b[32mbody: \u001b[39m\n \u001b[32ma: \u001b[39mb\n\n\u001b[33mRES\u001b[39m\n\u001b[32mresponseTime: \u001b[39m39ms\n\u001b[39m\n'); - done(); - }; - - ghostPrettyStream.pipe(writeStream); - - ghostPrettyStream.write(JSON.stringify({ - time: '2016-07-01 00:00:00', - level: 30, - req: { - ip: '127.0.01', - originalUrl: '/test', - method: 'GET', - body: { - a: 'b' + it('data.err', async function () { + await new Promise((resolve) => { + var ghostPrettyStream = new PrettyStream({mode: 'long'}); + var writeStream = new Writable(); + + writeStream._write = function (data) { + data = data.toString(); + assert.equal(data, '[2016-07-01 00:00:00] \u001b[31mERROR\u001b[39m\n\u001b[31m\n\u001b[31mHey Jude!\u001b[39m\n\n\u001b[90m----------------------------------------\u001b[39m\n\n\u001b[90mstack\u001b[39m\n\u001b[39m\n\u001b[90m\u001b[39m\n'); + resolve(); + }; + + ghostPrettyStream.pipe(writeStream); + + ghostPrettyStream.write(JSON.stringify({ + time: '2016-07-01 00:00:00', + level: 50, + err: { + message: 'Hey Jude!', + stack: 'stack' } - }, - res: { - statusCode: 200, - responseTime: '39ms' - } - })); + })); + }); }); - it('data.req && data.res && data.err', function (done) { - var ghostPrettyStream = new PrettyStream({mode: 'long'}); - var writeStream = new Writable(); - - writeStream._write = function (data) { - data = data.toString(); - assert.equal(data, '[2016-07-01 00:00:00] \u001b[31mERROR\u001b[39m "GET /test" \u001b[33m400\u001b[39m 39ms\n\u001b[31m\n\u001b[31mHey Jude!\u001b[39m\n\n\u001b[90m----------------------------------------\u001b[39m\n\n\u001b[90mstack\u001b[39m\n\u001b[39m\n\u001b[90m\n\u001b[33mREQ\u001b[39m\n\u001b[32moriginalUrl: \u001b[39m/test\n\u001b[32mmethod: \u001b[39m GET\n\u001b[32mbody: \u001b[39m\n \u001b[32ma: \u001b[39mb\n\n\u001b[33mRES\u001b[39m\n\u001b[32mresponseTime: \u001b[39m39ms\n\u001b[39m\n'); - done(); - }; - - ghostPrettyStream.pipe(writeStream); - - ghostPrettyStream.write(JSON.stringify({ - time: '2016-07-01 00:00:00', - level: 50, - req: { - originalUrl: '/test', - method: 'GET', - body: { - a: 'b' + it('data.req && data.res', async function () { + await new Promise((resolve) => { + var ghostPrettyStream = new PrettyStream({mode: 'long'}); + var writeStream = new Writable(); + + writeStream._write = function (data) { + data = data.toString(); + assert.equal(data, '[2016-07-01 00:00:00] \u001b[36mINFO\u001b[39m "GET /test" \u001b[32m200\u001b[39m 39ms\n\u001b[90m\n\u001b[33mREQ\u001b[39m\n\u001b[32mip: \u001b[39m 127.0.01\n\u001b[32moriginalUrl: \u001b[39m/test\n\u001b[32mmethod: \u001b[39m GET\n\u001b[32mbody: \u001b[39m\n \u001b[32ma: \u001b[39mb\n\n\u001b[33mRES\u001b[39m\n\u001b[32mresponseTime: \u001b[39m39ms\n\u001b[39m\n'); + resolve(); + }; + + ghostPrettyStream.pipe(writeStream); + + ghostPrettyStream.write(JSON.stringify({ + time: '2016-07-01 00:00:00', + level: 30, + req: { + ip: '127.0.01', + originalUrl: '/test', + method: 'GET', + body: { + a: 'b' + } + }, + res: { + statusCode: 200, + responseTime: '39ms' } - }, - res: { - statusCode: 400, - responseTime: '39ms' - }, - err: { - message: 'Hey Jude!', - stack: 'stack' - } - })); - }); - - it('data.err contains error details && meta fields', function (done) { - var ghostPrettyStream = new PrettyStream({mode: 'long'}); - var writeStream = new Writable(); - - writeStream._write = function (data) { - data = data.toString(); - assert.equal(data, '[2016-07-01 00:00:00] \u001b[31mERROR\u001b[39m\n\u001b[31m\n\u001b[31mType: ValidationError\u001b[39m\n\u001b[31mHey Jude!\u001b[39m\n\n\u001b[37m{"a":"b"}\u001b[39m\n\u001b[33mCheck documentation at https://docs.ghost.org/\u001b[39m\n\n\u001b[1m\u001b[37mError ID:\u001b[39m\u001b[22m\n \u001b[90me8546680-401f-11e9-99a7-ed7d6251b35c\u001b[39m\n\n\u001b[1m\u001b[37mDetails:\u001b[39m\u001b[22m\n\u001b[90m level: error\n rule: Templates must contain valid Handlebars.\n failures: \n - \n ref: default.hbs\n message: Missing helper: "image"\n code: GS005-TPL-ERR\u001b[39m\n\n\u001b[90m----------------------------------------\u001b[39m\n\n\u001b[90mstack\u001b[39m\n\u001b[39m\n\u001b[90m\u001b[39m\n'); - done(); - }; - - ghostPrettyStream.pipe(writeStream); - - ghostPrettyStream.write(JSON.stringify({ - time: '2016-07-01 00:00:00', - level: 50, - err: { - message: 'Hey Jude!', - stack: 'stack', - errorType: 'ValidationError', - id: 'e8546680-401f-11e9-99a7-ed7d6251b35c', - context: JSON.stringify({a: 'b'}), - help: 'Check documentation at https://docs.ghost.org/', - errorDetails: JSON.stringify([{ - level: 'error', - rule: 'Templates must contain valid Handlebars.', - failures: [{ref: 'default.hbs', message: 'Missing helper: "image"'}], - code: 'GS005-TPL-ERR' - }]) - } - })); - }); - - it('data with no time field', function (done) { - var ghostPrettyStream = new PrettyStream({mode: 'short'}); - var writeStream = new Writable(); - - // Hardcode the datetime so we don't have flaky tests - sinon.useFakeTimers(new Date('2024-12-15T13:17:00.000')); - - writeStream._write = function (data) { - data = data.toString(); - assert.equal(data, `[2024-12-15 13:17:00] \u001b[36mINFO\u001b[39m Ghost starts now.\n`); - done(); - }; - - ghostPrettyStream.pipe(writeStream); - - // Write the body with no time field - ghostPrettyStream.write(JSON.stringify({ - level: 30, - msg: 'Ghost starts now.' - })); - }); - }); - - describe('timezone handling', function () { - it('should display provided timestamps consistently', function (done) { - var ghostPrettyStream = new PrettyStream({mode: 'short'}); - var writeStream = new Writable(); - - writeStream._write = function (data) { - data = data.toString(); - // The timestamp should be formatted consistently - assert.equal(data.includes('[2016-07-01 00:00:00]'), true); - assert.equal(data.includes('INFO'), true); - assert.equal(data.includes('Test message'), true); - done(); - }; - - ghostPrettyStream.pipe(writeStream); - - // Write with an explicit timestamp - ghostPrettyStream.write(JSON.stringify({ - time: '2016-07-01 00:00:00', - level: 30, - msg: 'Test message' - })); + })); + }); }); - it('should handle ISO 8601 timestamps and convert to local time', function (done) { - var ghostPrettyStream = new PrettyStream({mode: 'short'}); - var writeStream = new Writable(); - - writeStream._write = function (data) { - data = data.toString(); - // ISO timestamp should be parsed and converted to local time - // Extract the timestamp to verify format - const timestampMatch = data.match(/^\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\]/); - assert.notEqual(timestampMatch, null); - - // Verify the timestamp represents the correct moment - // 2016-07-01T00:00:00.000Z in local time - const parsedTime = new Date(timestampMatch[1]); - const expectedTime = new Date('2016-07-01T00:00:00.000Z'); - - // The displayed local time should represent the same moment as the UTC time - // Allow for some tolerance due to date parsing - assert.equal(Math.abs(parsedTime.getTime() - expectedTime.getTime()) < 24 * 60 * 60 * 1000, true); - - assert.equal(data.includes('INFO'), true); - assert.equal(data.includes('ISO timestamp test'), true); - done(); - }; - - ghostPrettyStream.pipe(writeStream); - - // Write with an ISO 8601 timestamp - ghostPrettyStream.write(JSON.stringify({ - time: '2016-07-01T00:00:00.000Z', - level: 30, - msg: 'ISO timestamp test' - })); + it('data.req && data.res && data.err', async function () { + await new Promise((resolve) => { + var ghostPrettyStream = new PrettyStream({mode: 'long'}); + var writeStream = new Writable(); + + writeStream._write = function (data) { + data = data.toString(); + assert.equal(data, '[2016-07-01 00:00:00] \u001b[31mERROR\u001b[39m "GET /test" \u001b[33m400\u001b[39m 39ms\n\u001b[31m\n\u001b[31mHey Jude!\u001b[39m\n\n\u001b[90m----------------------------------------\u001b[39m\n\n\u001b[90mstack\u001b[39m\n\u001b[39m\n\u001b[90m\n\u001b[33mREQ\u001b[39m\n\u001b[32moriginalUrl: \u001b[39m/test\n\u001b[32mmethod: \u001b[39m GET\n\u001b[32mbody: \u001b[39m\n \u001b[32ma: \u001b[39mb\n\n\u001b[33mRES\u001b[39m\n\u001b[32mresponseTime: \u001b[39m39ms\n\u001b[39m\n'); + resolve(); + }; + + ghostPrettyStream.pipe(writeStream); + + ghostPrettyStream.write(JSON.stringify({ + time: '2016-07-01 00:00:00', + level: 50, + req: { + originalUrl: '/test', + method: 'GET', + body: { + a: 'b' + } + }, + res: { + statusCode: 400, + responseTime: '39ms' + }, + err: { + message: 'Hey Jude!', + stack: 'stack' + } + })); + }); }); - it('should handle timestamps with timezone offsets', function (done) { - var ghostPrettyStream = new PrettyStream({mode: 'short'}); - var writeStream = new Writable(); - - writeStream._write = function (data) { - data = data.toString(); - // Timestamp with timezone offset should be converted to local time for display - const timestampMatch = data.match(/^\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\]/); - assert.notEqual(timestampMatch, null); - - assert.equal(data.includes('INFO'), true); - assert.equal(data.includes('Timezone offset test'), true); - done(); - }; - - ghostPrettyStream.pipe(writeStream); - - // Write with a timestamp that includes timezone offset - ghostPrettyStream.write(JSON.stringify({ - time: '2016-07-01T00:00:00+02:00', - level: 30, - msg: 'Timezone offset test' - })); + it('data.err contains error details && meta fields', async function () { + await new Promise((resolve) => { + var ghostPrettyStream = new PrettyStream({mode: 'long'}); + var writeStream = new Writable(); + + writeStream._write = function (data) { + data = data.toString(); + assert.equal(data, '[2016-07-01 00:00:00] \u001b[31mERROR\u001b[39m\n\u001b[31m\n\u001b[31mType: ValidationError\u001b[39m\n\u001b[31mHey Jude!\u001b[39m\n\n\u001b[37m{"a":"b"}\u001b[39m\n\u001b[33mCheck documentation at https://docs.ghost.org/\u001b[39m\n\n\u001b[1m\u001b[37mError ID:\u001b[39m\u001b[22m\n \u001b[90me8546680-401f-11e9-99a7-ed7d6251b35c\u001b[39m\n\n\u001b[1m\u001b[37mDetails:\u001b[39m\u001b[22m\n\u001b[90m level: error\n rule: Templates must contain valid Handlebars.\n failures: \n - \n ref: default.hbs\n message: Missing helper: "image"\n code: GS005-TPL-ERR\u001b[39m\n\n\u001b[90m----------------------------------------\u001b[39m\n\n\u001b[90mstack\u001b[39m\n\u001b[39m\n\u001b[90m\u001b[39m\n'); + resolve(); + }; + + ghostPrettyStream.pipe(writeStream); + + ghostPrettyStream.write(JSON.stringify({ + time: '2016-07-01 00:00:00', + level: 50, + err: { + message: 'Hey Jude!', + stack: 'stack', + errorType: 'ValidationError', + id: 'e8546680-401f-11e9-99a7-ed7d6251b35c', + context: JSON.stringify({a: 'b'}), + help: 'Check documentation at https://docs.ghost.org/', + errorDetails: JSON.stringify([{ + level: 'error', + rule: 'Templates must contain valid Handlebars.', + failures: [{ref: 'default.hbs', message: 'Missing helper: "image"'}], + code: 'GS005-TPL-ERR' + }]) + } + })); + }); }); - it('should use current local time when no timestamp is provided', function (done) { - var ghostPrettyStream = new PrettyStream({mode: 'short'}); - var writeStream = new Writable(); + it('data with no time field', async function () { + await new Promise((resolve) => { + var ghostPrettyStream = new PrettyStream({mode: 'short'}); + var writeStream = new Writable(); - // Capture the time before the test - const beforeTime = new Date(); + // Hardcode the datetime so we don't have flaky tests + sinon.useFakeTimers(new Date('2024-12-15T13:17:00.000')); - writeStream._write = function (data) { - data = data.toString(); + writeStream._write = function (data) { + data = data.toString(); + assert.equal(data, `[2024-12-15 13:17:00] \u001b[36mINFO\u001b[39m Ghost starts now.\n`); + resolve(); + }; - // Extract the timestamp from the output - const timestampMatch = data.match(/^\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\]/); - assert.notEqual(timestampMatch, null); + ghostPrettyStream.pipe(writeStream); - const loggedTime = new Date(timestampMatch[1]); - const afterTime = new Date(); - - // The logged time should be between beforeTime and afterTime - assert.equal(loggedTime.getTime() >= beforeTime.getTime(), true); - assert.equal(loggedTime.getTime() <= afterTime.getTime(), true); - - done(); - }; - - ghostPrettyStream.pipe(writeStream); - - // Write without a timestamp - ghostPrettyStream.write(JSON.stringify({ - level: 30, - msg: 'No timestamp test' - })); - }); - - it('should work correctly in different timezones', function (done) { - // This test verifies that string timestamps are displayed as-is - var ghostPrettyStream = new PrettyStream({mode: 'short'}); - var writeStream = new Writable(); - - writeStream._write = function (data) { - data = data.toString(); - // String timestamp should be displayed exactly as provided - assert.equal(data.includes('[2016-07-01 00:00:00]'), true); - assert.equal(data.includes('String timestamp'), true); - done(); - }; - - ghostPrettyStream.pipe(writeStream); - - // Test with string timestamp - should be displayed as-is - ghostPrettyStream.write(JSON.stringify({ - time: '2016-07-01 00:00:00', - level: 30, - msg: 'String timestamp' - })); - }); - - it('regression test: string timestamps should not be affected by timezone offset', function (done) { - // This test ensures the bug from commit be5ddf2 doesn't resurface - // String timestamps like '2016-07-01 00:00:00' should be displayed exactly as provided - // regardless of the system timezone - var ghostPrettyStream = new PrettyStream({mode: 'short'}); - var writeStream = new Writable(); - - writeStream._write = function (data) { - data = data.toString(); - // The exact string '2016-07-01 00:00:00' should appear in the output - // It should NOT be shifted by timezone offset (e.g., NOT '2016-06-30 23:00:00') - assert.match(data, /^\[2016-07-01 00:00:00\]/); - assert.equal(data.includes('Regression test'), true); - done(); - }; - - ghostPrettyStream.pipe(writeStream); - - // This timestamp format was causing issues in non-UTC timezones - ghostPrettyStream.write(JSON.stringify({ - time: '2016-07-01 00:00:00', - level: 30, - msg: 'Regression test' - })); + // Write the body with no time field + ghostPrettyStream.write(JSON.stringify({ + level: 30, + msg: 'Ghost starts now.' + })); + }); }); }); - describe('edge paths', function () { it('defaults to short mode when no options are provided', function () { var ghostPrettyStream = new PrettyStream(); assert.equal(ghostPrettyStream.mode, 'short'); }); - it('accepts plain object writes and stringifies internally', function (done) { - var ghostPrettyStream = new PrettyStream({mode: 'short'}); - var writeStream = new Writable(); - - writeStream._write = function (data) { - data = data.toString(); - assert.equal(data.includes('Object input'), true); - done(); - }; - - ghostPrettyStream.pipe(writeStream); - ghostPrettyStream.write({ - time: '2016-07-01 00:00:00', - level: 30, - msg: 'Object input' + it('accepts plain object writes and stringifies internally', async function () { + await new Promise((resolve) => { + var ghostPrettyStream = new PrettyStream({mode: 'short'}); + var writeStream = new Writable(); + + writeStream._write = function (data) { + data = data.toString(); + assert.equal(data.includes('Object input'), true); + resolve(); + }; + + ghostPrettyStream.pipe(writeStream); + ghostPrettyStream.write({ + time: '2016-07-01 00:00:00', + level: 30, + msg: 'Object input' + }); }); }); - it('handles invalid JSON input in _transform', function (done) { - var ghostPrettyStream = new PrettyStream({mode: 'short'}); - ghostPrettyStream._transform(Buffer.from('{not-json'), null, (err) => { - assert.notEqual(err, null); - done(); + it('handles invalid JSON input in _transform', async function () { + await new Promise((resolve) => { + var ghostPrettyStream = new PrettyStream({mode: 'short'}); + ghostPrettyStream._transform(Buffer.from('{not-json'), null, (err) => { + assert.notEqual(err, null); + resolve(); + }); }); }); - it('renders raw errorDetails when parsing details fails', function (done) { - var ghostPrettyStream = new PrettyStream({mode: 'long'}); - var writeStream = new Writable(); - - writeStream._write = function (data) { - data = data.toString(); - assert.equal(data.includes('Details:'), true); - assert.equal(data.includes('not-json-details'), true); - done(); - }; - - ghostPrettyStream.pipe(writeStream); - ghostPrettyStream.write(JSON.stringify({ - time: '2016-07-01 00:00:00', - level: 50, - err: { - message: 'oops', - stack: 'stack', - errorDetails: 'not-json-details' - } - })); + it('renders raw errorDetails when parsing details fails', async function () { + await new Promise((resolve) => { + var ghostPrettyStream = new PrettyStream({mode: 'long'}); + var writeStream = new Writable(); + + writeStream._write = function (data) { + data = data.toString(); + assert.equal(data.includes('Details:'), true); + assert.equal(data.includes('not-json-details'), true); + resolve(); + }; + + ghostPrettyStream.pipe(writeStream); + ghostPrettyStream.write(JSON.stringify({ + time: '2016-07-01 00:00:00', + level: 50, + err: { + message: 'oops', + stack: 'stack', + errorDetails: 'not-json-details' + } + })); + }); }); - it('renders parsed object errorDetails payloads', function (done) { - var ghostPrettyStream = new PrettyStream({mode: 'long'}); - var writeStream = new Writable(); - - writeStream._write = function (data) { - data = data.toString(); - assert.equal(data.includes('Details:'), true); - assert.equal(data.includes('CODE1'), true); - done(); - }; - - ghostPrettyStream.pipe(writeStream); - ghostPrettyStream.write(JSON.stringify({ - time: '2016-07-01 00:00:00', - level: 50, - err: { - message: 'oops', - stack: 'stack', - errorDetails: JSON.stringify({code: 'CODE1'}) - } - })); + it('renders parsed object errorDetails payloads', async function () { + await new Promise((resolve) => { + var ghostPrettyStream = new PrettyStream({mode: 'long'}); + var writeStream = new Writable(); + + writeStream._write = function (data) { + data = data.toString(); + assert.equal(data.includes('Details:'), true); + assert.equal(data.includes('CODE1'), true); + resolve(); + }; + + ghostPrettyStream.pipe(writeStream); + ghostPrettyStream.write(JSON.stringify({ + time: '2016-07-01 00:00:00', + level: 50, + err: { + message: 'oops', + stack: 'stack', + errorDetails: JSON.stringify({code: 'CODE1'}) + } + })); + }); }); - it('renders non-object additional fields', function (done) { - var ghostPrettyStream = new PrettyStream({mode: 'long'}); - var writeStream = new Writable(); - - writeStream._write = function (data) { - data = data.toString(); - assert.equal(data.includes('plain-extra-value'), true); - done(); - }; - - ghostPrettyStream.pipe(writeStream); - ghostPrettyStream.write(JSON.stringify({ - time: '2016-07-01 00:00:00', - level: 30, - msg: 'hello', - extra: 'plain-extra-value' - })); + it('renders non-object additional fields', async function () { + await new Promise((resolve) => { + var ghostPrettyStream = new PrettyStream({mode: 'long'}); + var writeStream = new Writable(); + + writeStream._write = function (data) { + data = data.toString(); + assert.equal(data.includes('plain-extra-value'), true); + resolve(); + }; + + ghostPrettyStream.pipe(writeStream); + ghostPrettyStream.write(JSON.stringify({ + time: '2016-07-01 00:00:00', + level: 30, + msg: 'hello', + extra: 'plain-extra-value' + })); + }); }); - it('colors 500 status code as red', function (done) { - var ghostPrettyStream = new PrettyStream({mode: 'short'}); - var writeStream = new Writable(); - - writeStream._write = function (data) { - data = data.toString(); - assert.equal(data.includes('\u001b[31m500\u001b[39m'), true); - done(); - }; - - ghostPrettyStream.pipe(writeStream); - - ghostPrettyStream.write(JSON.stringify({ - time: '2016-07-01 00:00:00', - level: 30, - req: {originalUrl: '/a', method: 'GET'}, - res: {statusCode: 500, responseTime: '1ms'} - })); + it('colors 500 status code as red', async function () { + await new Promise((resolve) => { + var ghostPrettyStream = new PrettyStream({mode: 'short'}); + var writeStream = new Writable(); + + writeStream._write = function (data) { + data = data.toString(); + assert.equal(data.includes('\u001b[31m500\u001b[39m'), true); + resolve(); + }; + + ghostPrettyStream.pipe(writeStream); + + ghostPrettyStream.write(JSON.stringify({ + time: '2016-07-01 00:00:00', + level: 30, + req: {originalUrl: '/a', method: 'GET'}, + res: {statusCode: 500, responseTime: '1ms'} + })); + }); }); - it('colors 301 status code as cyan', function (done) { - var ghostPrettyStream = new PrettyStream({mode: 'short'}); - var writeStream = new Writable(); - - writeStream._write = function (data) { - data = data.toString(); - assert.equal(data.includes('\u001b[36m301\u001b[39m'), true); - done(); - }; - - ghostPrettyStream.pipe(writeStream); - - ghostPrettyStream.write(JSON.stringify({ - time: '2016-07-01 00:00:00', - level: 30, - req: {originalUrl: '/b', method: 'GET'}, - res: {statusCode: 301, responseTime: '1ms'} - })); + it('colors 301 status code as cyan', async function () { + await new Promise((resolve) => { + var ghostPrettyStream = new PrettyStream({mode: 'short'}); + var writeStream = new Writable(); + + writeStream._write = function (data) { + data = data.toString(); + assert.equal(data.includes('\u001b[36m301\u001b[39m'), true); + resolve(); + }; + + ghostPrettyStream.pipe(writeStream); + + ghostPrettyStream.write(JSON.stringify({ + time: '2016-07-01 00:00:00', + level: 30, + req: {originalUrl: '/b', method: 'GET'}, + res: {statusCode: 301, responseTime: '1ms'} + })); + }); }); - it('colors <200 status code with default color', function (done) { - var ghostPrettyStream = new PrettyStream({mode: 'short'}); - var writeStream = new Writable(); - - writeStream._write = function (data) { - data = data.toString(); - assert.equal(data.includes('\u001b[39m100\u001b[39m'), true); - done(); - }; - - ghostPrettyStream.pipe(writeStream); - - ghostPrettyStream.write(JSON.stringify({ - time: '2016-07-01 00:00:00', - level: 30, - req: {originalUrl: '/c', method: 'GET'}, - res: {statusCode: 100, responseTime: '1ms'} - })); + it('colors <200 status code with default color', async function () { + await new Promise((resolve) => { + var ghostPrettyStream = new PrettyStream({mode: 'short'}); + var writeStream = new Writable(); + + writeStream._write = function (data) { + data = data.toString(); + assert.equal(data.includes('\u001b[39m100\u001b[39m'), true); + resolve(); + }; + + ghostPrettyStream.pipe(writeStream); + + ghostPrettyStream.write(JSON.stringify({ + time: '2016-07-01 00:00:00', + level: 30, + req: {originalUrl: '/c', method: 'GET'}, + res: {statusCode: 100, responseTime: '1ms'} + })); + }); }); }); }); diff --git a/packages/prometheus-metrics/test/metrics-server.test.ts b/packages/prometheus-metrics/test/metrics-server.test.ts index c19e3d412..a9da243be 100644 --- a/packages/prometheus-metrics/test/metrics-server.test.ts +++ b/packages/prometheus-metrics/test/metrics-server.test.ts @@ -61,7 +61,7 @@ describe('Metrics Server', function () { await metricsServer.stop(); }); - after(async function () { + afterAll(async function () { await metricsServer.shutdown(); }); diff --git a/packages/server/test/server.test.js b/packages/server/test/server.test.js index 41b6779d2..e94baecd6 100644 --- a/packages/server/test/server.test.js +++ b/packages/server/test/server.test.js @@ -16,184 +16,198 @@ describe('Server Utils', function () { afterEach(function () { sandbox.restore(); }); - - it('Normalises port number correctly', function (done) { + + it('Normalises port number correctly', async function () { const testPort = 180; - sandbox.stub(http, 'createServer').callsFake(function () { - return { - listen: function (port) { - assert.equal(port, testPort); - done(); - } - }; + await new Promise((resolve) => { + sandbox.stub(http, 'createServer').callsFake(function () { + return { + listen: function (port) { + assert.equal(port, testPort); + resolve(); + } + }; + }); + + server.start({ + set: () => {} + }, testPort.toString()); }); - - server.start({ - set: () => {} - }, testPort.toString()); }); - it('Normalises named pipe correctly', function (done) { + it('Normalises named pipe correctly', async function () { const testPipe = 'hello'; - sandbox.stub(http, 'createServer').callsFake(function () { - return { - listen: function (port) { - assert.equal(port, testPipe); - done(); - } - }; + await new Promise((resolve) => { + sandbox.stub(http, 'createServer').callsFake(function () { + return { + listen: function (port) { + assert.equal(port, testPipe); + resolve(); + } + }; + }); + + server.start({ + set: () => {} + }, testPipe); }); - - server.start({ - set: () => {} - }, testPipe); }); - - it('Normalises negative port value correctly', function (done) { + + it('Normalises negative port value correctly', async function () { const testPort = -80; - sandbox.stub(http, 'createServer').callsFake(function () { - return { - listen: function (port) { - assert.equal(port, false); - done(); - } - }; + await new Promise((resolve) => { + sandbox.stub(http, 'createServer').callsFake(function () { + return { + listen: function (port) { + assert.equal(port, false); + resolve(); + } + }; + }); + + server.start({ + set: () => {} + }, testPort.toString()); }); - - server.start({ - set: () => {} - }, testPort.toString()); }); - it('Emits listening event', function (done) { + it('Emits listening event', async function () { const testAddress = 'hello'; - sandbox.stub(logging, 'info').callsFake(function (message) { - assert.equal(message.startsWith(`Listening on pipe ${testAddress}`), true); - done(); - }); - - sandbox.stub(http, 'createServer').callsFake(function () { - class Server extends EventEmitter { - constructor() { - super(); + await new Promise((resolve) => { + sandbox.stub(logging, 'info').callsFake(function (message) { + assert.equal(message.startsWith(`Listening on pipe ${testAddress}`), true); + resolve(); + }); + + sandbox.stub(http, 'createServer').callsFake(function () { + class Server extends EventEmitter { + constructor() { + super(); + } + listen() { + setTimeout(() => { + this.emit('listening'); + }, 0); + } + address() { + return testAddress; + } } - listen() { - setTimeout(() => { - this.emit('listening'); - }, 0); - } - address() { - return testAddress; - } - } - return new Server(); - }); + return new Server(); + }); - server.start({ - set: () => {} - }, 180); + server.start({ + set: () => {} + }, 180); + }); }); - it('Emits nice error for EACCES', function (done) { + it('Emits nice error for EACCES', async function () { const testPort = 180; - sandbox.stub(process, 'exit').callsFake(function () { - }); - - sandbox.stub(logging, 'error').callsFake(function (message) { - assert.equal(message.startsWith(`Port ${testPort} requires elevated privileges`), true); - done(); - }); - - sandbox.stub(http, 'createServer').callsFake(function () { - class Server extends EventEmitter { - constructor() { - super(); - } - listen() { - setTimeout(() => { - this.emit('error', { - code: 'EACCES', - syscall: 'listen' - }); - }, 0); + await new Promise((resolve) => { + sandbox.stub(process, 'exit').callsFake(function () { + }); + + sandbox.stub(logging, 'error').callsFake(function (message) { + assert.equal(message.startsWith(`Port ${testPort} requires elevated privileges`), true); + resolve(); + }); + + sandbox.stub(http, 'createServer').callsFake(function () { + class Server extends EventEmitter { + constructor() { + super(); + } + listen() { + setTimeout(() => { + this.emit('error', { + code: 'EACCES', + syscall: 'listen' + }); + }, 0); + } } - } - - return new Server(); - }); - server.start({ - set: () => {} - }, testPort); - }); - - it('Emits nice error for EADDRINUSE', function (done) { - const testPort = 180; + return new Server(); + }); - sandbox.stub(process, 'exit').callsFake(function () { + server.start({ + set: () => {} + }, testPort); }); + }); - sandbox.stub(logging, 'error').callsFake(function (message) { - assert.equal(message.startsWith(`Port ${testPort} is already in use`), true); - done(); - }); + it('Emits nice error for EADDRINUSE', async function () { + const testPort = 180; - sandbox.stub(http, 'createServer').callsFake(function () { - class Server extends EventEmitter { - constructor() { - super(); - } - listen() { - setTimeout(() => { - this.emit('error', { - code: 'EADDRINUSE', - syscall: 'listen' - }); - }, 0); + await new Promise((resolve) => { + sandbox.stub(process, 'exit').callsFake(function () { + }); + + sandbox.stub(logging, 'error').callsFake(function (message) { + assert.equal(message.startsWith(`Port ${testPort} is already in use`), true); + resolve(); + }); + + sandbox.stub(http, 'createServer').callsFake(function () { + class Server extends EventEmitter { + constructor() { + super(); + } + listen() { + setTimeout(() => { + this.emit('error', { + code: 'EADDRINUSE', + syscall: 'listen' + }); + }, 0); + } } - } - return new Server(); - }); + return new Server(); + }); - server.start({ - set: () => {} - }, testPort); + server.start({ + set: () => {} + }, testPort); + }); }); - it('Emits pipe-specific error message when bound to a named pipe', function (done) { + it('Emits pipe-specific error message when bound to a named pipe', async function () { const testPipe = 'server-pipe'; - sandbox.stub(process, 'exit').callsFake(function () {}); - sandbox.stub(logging, 'error').callsFake(function (message) { - assert.equal(message.startsWith(`Pipe ${testPipe} requires elevated privileges`), true); - done(); - }); - - sandbox.stub(http, 'createServer').callsFake(function () { - class Server extends EventEmitter { - listen() { - setTimeout(() => { - this.emit('error', { - code: 'EACCES', - syscall: 'listen' - }); - }, 0); + await new Promise((resolve) => { + sandbox.stub(process, 'exit').callsFake(function () {}); + sandbox.stub(logging, 'error').callsFake(function (message) { + assert.equal(message.startsWith(`Pipe ${testPipe} requires elevated privileges`), true); + resolve(); + }); + + sandbox.stub(http, 'createServer').callsFake(function () { + class Server extends EventEmitter { + listen() { + setTimeout(() => { + this.emit('error', { + code: 'EACCES', + syscall: 'listen' + }); + }, 0); + } } - } - return new Server(); - }); + return new Server(); + }); - server.start({ - set: () => {} - }, testPipe); + server.start({ + set: () => {} + }, testPipe); + }); }); it('Stops server without throwing', function () { @@ -218,79 +232,85 @@ describe('Server Utils', function () { assert.doesNotThrow(server.stop); }); - it('Emits listening event with numeric port', function (done) { + it('Emits listening event with numeric port', async function () { const testPort = 191; - sandbox.stub(logging, 'info').callsFake(function (message) { - assert.equal(message.startsWith(`Listening on port ${testPort}`), true); - done(); - }); - - sandbox.stub(http, 'createServer').callsFake(function () { - class Server extends EventEmitter { - listen() { - setTimeout(() => { - this.emit('listening'); - }, 0); + await new Promise((resolve) => { + sandbox.stub(logging, 'info').callsFake(function (message) { + assert.equal(message.startsWith(`Listening on port ${testPort}`), true); + resolve(); + }); + + sandbox.stub(http, 'createServer').callsFake(function () { + class Server extends EventEmitter { + listen() { + setTimeout(() => { + this.emit('listening'); + }, 0); + } + address() { + return {port: testPort}; + } } - address() { - return {port: testPort}; - } - } - return new Server(); - }); + return new Server(); + }); - server.start({ - set: () => {} - }, testPort); + server.start({ + set: () => {} + }, testPort); + }); }); - it('Throws unknown listen errors', function (done) { - sandbox.stub(http, 'createServer').callsFake(function () { - class Server extends EventEmitter { - listen() { - setTimeout(() => { - assert.throws(() => { - this.emit('error', { - code: 'EOTHER', - syscall: 'listen' + it('Throws unknown listen errors', async function () { + await new Promise((resolve) => { + sandbox.stub(http, 'createServer').callsFake(function () { + class Server extends EventEmitter { + listen() { + setTimeout(() => { + assert.throws(() => { + this.emit('error', { + code: 'EOTHER', + syscall: 'listen' + }); }); - }); - done(); - }, 0); + resolve(); + }, 0); + } } - } - return new Server(); - }); + return new Server(); + }); - server.start({ - set: () => {} - }, 180); + server.start({ + set: () => {} + }, 180); + }); }); - it('Throws errors not originating from listen syscall', function (done) { - sandbox.stub(http, 'createServer').callsFake(function () { - class Server extends EventEmitter { - listen() { - setTimeout(() => { - assert.throws(() => { - this.emit('error', { - code: 'EACCES', - syscall: 'not-listen' + it('Throws errors not originating from listen syscall', async function () { + await new Promise((resolve) => { + sandbox.stub(http, 'createServer').callsFake(function () { + class Server extends EventEmitter { + listen() { + setTimeout(() => { + assert.throws(() => { + this.emit('error', { + code: 'EACCES', + syscall: 'not-listen' + }); }); - }); - done(); - }, 0); + resolve(); + }, 0); + } } - } - return new Server(); - }); + return new Server(); + }); - server.start({ - set: () => {} - }, 180); + server.start({ + set: () => {} + }, 180); + }); }); }); diff --git a/packages/webhook-mock-receiver/test/WebhookMockReceiver.test.js b/packages/webhook-mock-receiver/test/WebhookMockReceiver.test.js index 0372a5036..5ee66d62f 100644 --- a/packages/webhook-mock-receiver/test/WebhookMockReceiver.test.js +++ b/packages/webhook-mock-receiver/test/WebhookMockReceiver.test.js @@ -9,7 +9,7 @@ describe('Webhook Mock Receiver', function () { let got; const webhookURL = 'https://test-webhook-receiver.com/webhook'; - before(async function () { + beforeAll(async function () { got = (await import('got')).default; snapshotManager = { assertSnapshot: sinon.spy() diff --git a/packages/zip/test/zip.test.js b/packages/zip/test/zip.test.js index 1e3e34c9a..e43b09eb2 100644 --- a/packages/zip/test/zip.test.js +++ b/packages/zip/test/zip.test.js @@ -18,7 +18,7 @@ describe('Compress and Extract should be opposite functions', function () { fs.rmSync(unzipDestination, {recursive: true, force: true}); }; - before(function () { + beforeAll(function () { symlinkPath = path.join(__dirname, 'fixtures', 'theme-symlink'); themeFolder = path.join(__dirname, 'fixtures', 'test-theme'); zipDestination = path.join(__dirname, 'fixtures', 'test-theme.zip'); @@ -27,41 +27,26 @@ describe('Compress and Extract should be opposite functions', function () { cleanUp(); }); - after(function () { + afterAll(function () { cleanUp(); }); - it('ensure symlinks work', function (done) { + it('ensure symlinks work', async function () { fs.symlinkSync(themeFolder, symlinkPath); - let originalHash; - - hashElement(symlinkPath) - .then((_originalHash) => { - originalHash = _originalHash; - return compress(symlinkPath, zipDestination); - }) - .then((res) => { - assert.equal(typeof res, 'object'); - assert.equal(res.path, zipDestination); - assert.equal(res.size < 619618, true); - - return extract(zipDestination, unzipDestination); - }) - .then((res) => { - assert.equal(typeof res, 'object'); - assert.equal(res.path, unzipDestination); - - return hashElement(unzipDestination); - }) - .then((extractedHash) => { - assert.equal(originalHash.children.toString(), extractedHash.children.toString()); - - done(); - }) - .catch((err) => { - return done(err); - }); + const originalHash = await hashElement(symlinkPath); + + const compressRes = await compress(symlinkPath, zipDestination); + assert.equal(typeof compressRes, 'object'); + assert.equal(compressRes.path, zipDestination); + assert.equal(compressRes.size < 619618, true); + + const extractRes = await extract(zipDestination, unzipDestination); + assert.equal(typeof extractRes, 'object'); + assert.equal(extractRes.path, unzipDestination); + + const extractedHash = await hashElement(unzipDestination); + assert.equal(originalHash.children.toString(), extractedHash.children.toString()); }); it('rejects when archiver emits an async error event', async function () { @@ -98,7 +83,7 @@ describe('Compress and Extract should be opposite functions', function () { describe('Extract zip', function () { let themeFolder, zipDestination, unzipDestination, symLinkPath, longFilePath; - before(function () { + beforeAll(function () { themeFolder = path.join(__dirname, 'fixtures', 'test-theme'); zipDestination = path.join(__dirname, 'fixtures', 'test-theme.zip'); unzipDestination = path.join(__dirname, 'fixtures', 'test-theme-unzipped'); From 4bca262fa257270725c8d7112dc185ee6168973e Mon Sep 17 00:00:00 2001 From: Steve Larson <9larsons@gmail.com> Date: Mon, 2 Mar 2026 14:54:44 -0600 Subject: [PATCH 4/8] Fix runner-specific test issues and update migration docs - root-utils: mock caller in test (vitest call stack differs from mocha) - jest-snapshot: use __filename instead of mocha this.test.file - job-manager: use assert.rejects for async job validation - Update MIGRATION.md to document completed vitest migration --- MIGRATION.md | 251 ++++++++++++++++++ .../__snapshots__/example-app.test.js.snap | 2 +- .../test/SnapshotManager.test.js | 5 +- packages/job-manager/test/job-manager.test.js | 28 +- packages/root-utils/test/root-utils.test.js | 13 +- 5 files changed, 274 insertions(+), 25 deletions(-) create mode 100644 MIGRATION.md diff --git a/MIGRATION.md b/MIGRATION.md new file mode 100644 index 000000000..297d3f88d --- /dev/null +++ b/MIGRATION.md @@ -0,0 +1,251 @@ +# Framework Migration Plan + +## Overview + +Three major migrations for the TryGhost framework monorepo (44 packages): + +1. **Vitest Migration** — Replace Mocha with Vitest +2. **TypeScript Migration** — Convert JS packages to TypeScript +3. **Lodash Removal** — Replace Lodash with native JS + +**Recommended order:** Vitest → TypeScript → Lodash (Lodash can run in parallel) + +--- + +## 1. Vitest Migration — COMPLETE + +All 42 packages migrated from Mocha 11.7.5 + c8 to Vitest 3.1.1 + @vitest/coverage-v8. + +### Configuration + +**Root config** (`vitest.config.ts`): Shared defaults — globals enabled, node environment, v8 coverage at 90% thresholds. Standard packages reference this via `--config ../../vitest.config.ts`. + +**6 packages have local `vitest.config.ts` overrides:** + +| Package | Override | Reason | +|---------|----------|--------| +| **express-test** | `setupFiles: ['./test/utils/overrides.js']`, 0% thresholds | Setup file initializes snapshot test registry before each test. Coverage was never enforced under mocha. | +| **http-cache-utils** | 0% thresholds | Coverage was never enforced under mocha. | +| **webhook-mock-receiver** | 0% thresholds | Coverage was never enforced under mocha. | +| **errors** | `coverage.include: ['src/**']` | TypeScript package with source in `src/` not `lib/`. | +| **prometheus-metrics** | `coverage.include: ['src/**']` | TypeScript package with source in `src/` not `lib/`. | +| **job-manager** | `dangerouslyIgnoreUnhandledErrors: true` | Bree spawns background workers that emit unhandled rejections during cleanup after tests complete. These are expected and were silently ignored by Mocha. | + +### Key migration notes + +- Mocha's `before`/`after` become Vitest's `beforeAll`/`afterAll`. ESLint `plugin:ghost/test` uses `env: { mocha: true }` which doesn't include these globals, so 5 test `.eslintrc.js` files add them explicitly. +- 87 `done()` callback patterns across 9 test files were converted to `async`/`await` with `new Promise`. +- `express-test/test/utils/overrides.js` uses arrow functions and top-level hooks (vitest setup file pattern) with ESLint disable comments for the mocha lint rules. +- Vitest sets `process.env.MODE = 'test'` which conflicted with `@tryghost/metrics` — cleaned up in beforeEach/afterEach. + +--- + +## 2. TypeScript Migration + +### Current State + +| Metric | Value | +|--------|-------| +| TypeScript packages | 2 (errors, prometheus-metrics) | +| JavaScript packages | 42 | +| Shared tsconfig | packages/tsconfig.json (ES2022, commonjs, strict) | +| Build tools | esbuild + tsc (errors), tsc only (prometheus-metrics) | + +### Existing TypeScript Package Patterns + +**@tryghost/errors (esbuild + tsc):** +- Source: `/src/*.ts` +- Output: `/cjs/` (CommonJS via esbuild), `/es/` (ESM via esbuild), `/types/` (declarations via tsc) +- Dual CJS/ESM output + +**@tryghost/prometheus-metrics (tsc only):** +- Source: `/src/*.ts` +- Output: `/build/` +- Single CJS output with declarations + +### Decisions Needed + +- [ ] **Build strategy:** Standardize on esbuild+tsc (fast builds, dual output) or tsc-only (simpler)? +- [ ] **Strictness:** Start with `strict: true` (match existing tsconfig) or `strict: false` + `allowJs: true` for gradual migration? +- [ ] **Module format:** Keep commonjs or move to ESM? (Current tsconfig targets commonjs) + +### Migration Steps + +#### Step 1: Standardize Build Infrastructure +- [ ] Decide on build strategy +- [ ] Create a shared tsconfig base that all packages extend +- [ ] Create a package template/script for conversion + +#### Step 2: Wave 1 — Leaf Packages (simple, few/no dependents) +- [ ] tpl (1 source file) +- [ ] limit (2 source files) +- [ ] color-utils (1 source file) +- [ ] root-utils (1 source file) +- [ ] timezone-data (1 source file) +- [ ] string (1 source file) +- [ ] debug (1 source file) +- [ ] parse-email-address + +#### Step 3: Wave 2 — Small Utility Packages +- [ ] validator (2 source files) +- [ ] pretty-stream (1 source file) +- [ ] config-url-helpers (2 source files) +- [ ] extract-api-key (1 source file) +- [ ] http-cache-utils (1 source file) +- [ ] minifier (1 source file) +- [ ] nql (1 source file) + +#### Step 4: Wave 3 — Medium Packages +- [ ] logging (2 source files) +- [ ] request (1 source file) +- [ ] security (6 source files) +- [ ] express-test +- [ ] custom-redirects +- [ ] members-csv +- [ ] zip +- [ ] http-stream + +#### Step 5: Wave 4 — Bookshelf Plugins (7 packages) +- [ ] bookshelf-collision +- [ ] bookshelf-eager-load +- [ ] bookshelf-filter +- [ ] bookshelf-has-posts +- [ ] bookshelf-include-count +- [ ] bookshelf-order +- [ ] bookshelf-pagination +- [ ] bookshelf-plugins + +#### Step 6: Wave 5 — Core/Complex Packages +- [ ] api-framework (16 source files — largest package) +- [ ] domain-events +- [ ] job-manager +- [ ] magic-link +- [ ] session-service +- [ ] staff-service +- [ ] mw-error-handler +- [ ] mw-vhost +- [ ] mw-session-from-token +- [ ] mw-api-version-mismatch +- [ ] mw-cache-control +- [ ] adapter-manager +- [ ] dynamic-routing-events +- [ ] email-analytics-provider-mailgun +- [ ] email-analytics-service +- [ ] mailgun-client +- [ ] member-events +- [ ] members-importer +- [ ] metrics-server +- [ ] moleculer-service-from-class +- [ ] verification-trigger +- [ ] version-notifications-data-service + +#### Per-Package Conversion Checklist +For each package: +- [ ] Create/extend `tsconfig.json` from shared base +- [ ] Rename `lib/*.js` → `src/*.ts` (or `lib/*.ts` — decide on convention) +- [ ] Add type annotations +- [ ] Add `build` script to package.json +- [ ] Update `main`, `types`, and `exports` in package.json +- [ ] Convert test files from `.test.js` → `.test.ts` +- [ ] Update `.gitignore` for build output +- [ ] Verify tests pass with 100% coverage +- [ ] Update any packages that import from this one + +--- + +## 3. Lodash Removal + +### Current State + +| Metric | Value | +|--------|-------| +| Packages using lodash | 13 | +| Files using lodash | ~22 | +| Unique functions used | ~35 | +| lodash version | 4.17.23 | + +### Function Inventory by Replacement Difficulty + +#### Easy — Direct Native Replacements + +| Lodash Function | Native Replacement | Usage Count | +|----------------|-------------------|-------------| +| `_.isString(x)` | `typeof x === 'string'` | 1 | +| `_.isArray(x)` | `Array.isArray(x)` | 3 | +| `_.isObject(x)` | `typeof x === 'object' && x !== null` | 1 | +| `_.isNumber(x)` | `typeof x === 'number'` | 1 | +| `_.isBoolean(x)` | `typeof x === 'boolean'` | 1 | +| `_.isNil(x)` | `x === null \|\| x === undefined` | 1 | +| `_.isEmpty(x)` | `Object.keys(x).length === 0` / `x.length === 0` | 7 | +| `_.each(arr, fn)` | `arr.forEach(fn)` / `for...of` | 6 | +| `_.map(arr, fn)` | `arr.map(fn)` | 1 | +| `_.filter(arr, fn)` | `arr.filter(fn)` | 1 | +| `_.find(arr, fn)` | `arr.find(fn)` | 1 | +| `_.includes(arr, v)` | `arr.includes(v)` | 1 | +| `_.toString(x)` | `String(x)` | 1 | +| `_.defaults(obj, def)` | `{ ...defaults, ...obj }` | 1 | +| `_.extend(a, b)` | `Object.assign(a, b)` | 1 | + +#### Medium — Small Utility Needed + +| Lodash Function | Native Replacement | Usage Count | +|----------------|-------------------|-------------| +| `_.pick(obj, keys)` | `Object.fromEntries(Object.entries(obj).filter(([k]) => keys.includes(k)))` | 6 | +| `_.omit(obj, keys)` | `Object.fromEntries(Object.entries(obj).filter(([k]) => !keys.includes(k)))` | 2 | +| `_.cloneDeep(x)` | `structuredClone(x)` | 4 | +| `_.merge(a, b)` | `Object.assign()` for shallow, custom for deep | 2 | +| `_.result(obj, path)` | `typeof obj[path] === 'function' ? obj[path]() : obj[path]` | 6 | +| `_.forOwn(obj, fn)` | `Object.entries(obj).forEach(([k,v]) => fn(v,k))` | 2 | +| `_.has(obj, key)` | `Object.hasOwn(obj, key)` | 2 | +| `_.uniq(arr)` | `[...new Set(arr)]` | 1 | +| `_.intersection(a, b)` | `a.filter(x => b.includes(x))` | 1 | + +#### Hard — Consider Keeping + +| Lodash Function | Notes | Usage Count | +|----------------|-------|-------------| +| `_.get(obj, path, def)` | Optional chaining + `??` works for most cases, but deep string paths need a helper | 4 | +| `lodash.template` | Used in `tpl` package — core templating. Would need a replacement library or custom impl | 2 | + +### Migration Steps by Package + +#### Phase 1: Easy Wins (type checks and array methods) +- [ ] **bookshelf-collision** — uses: `_.each` +- [ ] **bookshelf-eager-load** — uses: `_.each` +- [ ] **bookshelf-has-posts** — uses: `_.each` +- [ ] **bookshelf-include-count** — uses: `_.each`, `_.intersection` +- [ ] **bookshelf-order** — uses: `_.each` + +#### Phase 2: Medium Complexity +- [ ] **bookshelf-pagination** — uses: `_.pick`, `_.defaults`, `_.omit` +- [ ] **pretty-stream** — uses: `_.isObject`, `_.isNumber`, `_.isBoolean`, `_.isNil`, `_.isEmpty`, `_.isString`, `_.toString`, `_.each`, `_.has` +- [ ] **request** — uses: `_.extend` +- [ ] **mw-error-handler** — uses: `_.merge` + +#### Phase 3: Heavier Usage +- [ ] **api-framework** (5 files) — uses: `_.pick`, `_.omit`, `_.result`, `_.isEmpty`, `_.isArray`, `_.cloneDeep`, `_.each`, `_.filter`, `_.find`, `_.map`, `_.includes`, `_.forOwn` +- [ ] **validator** (2 files) — uses: `_.isEmpty`, `_.isString`, `_.isArray`, `_.each`, `_.has` +- [ ] **logging** (2 files) — uses: `_.cloneDeep`, `_.isEmpty`, `_.each` + +#### Phase 4: Special Cases +- [ ] **tpl** — uses `lodash.template` (separate npm package). Options: + - Keep `lodash.template` as standalone dependency (it's small) + - Replace with a lightweight template library + - Write a simple template function if usage is basic +- [ ] **errors** — lodash is only in devDependencies (+ @types/lodash). Check if it's actually used in tests + +#### Per-Package Checklist +For each package: +- [ ] Replace lodash calls with native equivalents +- [ ] Run tests to verify behavior matches +- [ ] Remove `lodash` from `dependencies` in package.json +- [ ] Remove `@types/lodash` from devDependencies if present +- [ ] Verify 100% coverage still passes + +--- + +## Progress Tracking + +### Vitest Migration: COMPLETE (42/42 packages) +### TypeScript Migration: 2/44 packages +### Lodash Removal: 0/13 packages diff --git a/packages/express-test/test/__snapshots__/example-app.test.js.snap b/packages/express-test/test/__snapshots__/example-app.test.js.snap index b1d833e84..555f0f088 100644 --- a/packages/express-test/test/__snapshots__/example-app.test.js.snap +++ b/packages/express-test/test/__snapshots__/example-app.test.js.snap @@ -1,4 +1,4 @@ -// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`Example App Set & Expect check body using snapshot matching errors correctly for random data 1: [body] 1`] = ` Object { diff --git a/packages/jest-snapshot/test/SnapshotManager.test.js b/packages/jest-snapshot/test/SnapshotManager.test.js index bfc0079f0..255646fd3 100644 --- a/packages/jest-snapshot/test/SnapshotManager.test.js +++ b/packages/jest-snapshot/test/SnapshotManager.test.js @@ -99,9 +99,8 @@ describe('Snapshot Manager', function () { outputPath = snapshotManager._resolveSnapshotFilePath(inputPath); assert.equal(outputPath, '/full/path/to/tests/unit/__snapshots__/foo.js.snap'); - // Real example mocha context - const {test} = this; - inputPath = test.file; + // Real example using current test file path + inputPath = __filename; outputPath = snapshotManager._resolveSnapshotFilePath(inputPath); assert.match(outputPath, /\/packages\/jest-snapshot\/test\/__snapshots__\/SnapshotManager\.test\.js\.snap/); }); diff --git a/packages/job-manager/test/job-manager.test.js b/packages/job-manager/test/job-manager.test.js index 36e271b11..97aa70dcd 100644 --- a/packages/job-manager/test/job-manager.test.js +++ b/packages/job-manager/test/job-manager.test.js @@ -114,26 +114,18 @@ describe('Job Manager', function () { assert.equal(jobManager.bree.start.called, true); }); - it('fails to schedule for invalid scheduling expression', function () { - try { - jobManager.addJob({ - at: 'invalid expression', - name: 'jobName' - }); - } catch (err) { - assert.equal(err.message, 'Invalid schedule format'); - } + it('fails to schedule for invalid scheduling expression', async function () { + await assert.rejects(() => jobManager.addJob({ + at: 'invalid expression', + name: 'jobName' + }), {message: 'Invalid schedule format'}); }); - it('fails to schedule for no job name', function () { - try { - jobManager.addJob({ - at: 'invalid expression', - job: () => {} - }); - } catch (err) { - assert.equal(err.message, 'Name parameter should be present if job is a function'); - } + it('fails to schedule for no job name', async function () { + await assert.rejects(() => jobManager.addJob({ + at: 'invalid expression', + job: () => {} + }), {message: 'Name parameter should be present if job is a function'}); }); it('schedules a job using date format', async function () { diff --git a/packages/root-utils/test/root-utils.test.js b/packages/root-utils/test/root-utils.test.js index 79c4cda08..f790edef2 100644 --- a/packages/root-utils/test/root-utils.test.js +++ b/packages/root-utils/test/root-utils.test.js @@ -3,7 +3,7 @@ const path = require('path'); const fs = require('fs'); const os = require('os'); const Module = require('module'); -const {getCallerRoot, getProcessRoot} = require('../index'); +const {getProcessRoot} = require('../index'); const rootUtilsModulePath = require.resolve('../lib/root-utils'); function loadRootUtilsWithMocks(mocks) { @@ -25,8 +25,15 @@ function loadRootUtilsWithMocks(mocks) { describe('getCallerRoot', function () { it('Gets the root directory of the caller', function () { - // mocha calls the test function calls getCallerRoot - assert.equal(getCallerRoot().endsWith('mocha'), true); + const mockedModule = loadRootUtilsWithMocks({ + caller: () => __filename, + 'find-root': require('find-root') + }); + + const callerRoot = mockedModule.getCallerRoot(); + assert.ok(callerRoot, 'caller root should be defined'); + assert.ok(typeof callerRoot === 'string', 'caller root should be a string'); + assert.equal(callerRoot.endsWith('root-utils'), true); }); it('returns undefined when caller root cannot be resolved', function () { From dee4bba920b74565e8da3498d88fe4b5fc99040d Mon Sep 17 00:00:00 2001 From: Steve Larson <9larsons@gmail.com> Date: Mon, 2 Mar 2026 14:57:29 -0600 Subject: [PATCH 5/8] Remove MIGRATION.md and add comments to vitest config overrides Document the reason for each package-level vitest.config.ts override directly in the config file. --- MIGRATION.md | 251 ------------------ packages/errors/vitest.config.ts | 2 + packages/express-test/vitest.config.ts | 2 + packages/http-cache-utils/vitest.config.ts | 1 + packages/prometheus-metrics/vitest.config.ts | 2 + .../webhook-mock-receiver/vitest.config.ts | 1 + 6 files changed, 8 insertions(+), 251 deletions(-) delete mode 100644 MIGRATION.md diff --git a/MIGRATION.md b/MIGRATION.md deleted file mode 100644 index 297d3f88d..000000000 --- a/MIGRATION.md +++ /dev/null @@ -1,251 +0,0 @@ -# Framework Migration Plan - -## Overview - -Three major migrations for the TryGhost framework monorepo (44 packages): - -1. **Vitest Migration** — Replace Mocha with Vitest -2. **TypeScript Migration** — Convert JS packages to TypeScript -3. **Lodash Removal** — Replace Lodash with native JS - -**Recommended order:** Vitest → TypeScript → Lodash (Lodash can run in parallel) - ---- - -## 1. Vitest Migration — COMPLETE - -All 42 packages migrated from Mocha 11.7.5 + c8 to Vitest 3.1.1 + @vitest/coverage-v8. - -### Configuration - -**Root config** (`vitest.config.ts`): Shared defaults — globals enabled, node environment, v8 coverage at 90% thresholds. Standard packages reference this via `--config ../../vitest.config.ts`. - -**6 packages have local `vitest.config.ts` overrides:** - -| Package | Override | Reason | -|---------|----------|--------| -| **express-test** | `setupFiles: ['./test/utils/overrides.js']`, 0% thresholds | Setup file initializes snapshot test registry before each test. Coverage was never enforced under mocha. | -| **http-cache-utils** | 0% thresholds | Coverage was never enforced under mocha. | -| **webhook-mock-receiver** | 0% thresholds | Coverage was never enforced under mocha. | -| **errors** | `coverage.include: ['src/**']` | TypeScript package with source in `src/` not `lib/`. | -| **prometheus-metrics** | `coverage.include: ['src/**']` | TypeScript package with source in `src/` not `lib/`. | -| **job-manager** | `dangerouslyIgnoreUnhandledErrors: true` | Bree spawns background workers that emit unhandled rejections during cleanup after tests complete. These are expected and were silently ignored by Mocha. | - -### Key migration notes - -- Mocha's `before`/`after` become Vitest's `beforeAll`/`afterAll`. ESLint `plugin:ghost/test` uses `env: { mocha: true }` which doesn't include these globals, so 5 test `.eslintrc.js` files add them explicitly. -- 87 `done()` callback patterns across 9 test files were converted to `async`/`await` with `new Promise`. -- `express-test/test/utils/overrides.js` uses arrow functions and top-level hooks (vitest setup file pattern) with ESLint disable comments for the mocha lint rules. -- Vitest sets `process.env.MODE = 'test'` which conflicted with `@tryghost/metrics` — cleaned up in beforeEach/afterEach. - ---- - -## 2. TypeScript Migration - -### Current State - -| Metric | Value | -|--------|-------| -| TypeScript packages | 2 (errors, prometheus-metrics) | -| JavaScript packages | 42 | -| Shared tsconfig | packages/tsconfig.json (ES2022, commonjs, strict) | -| Build tools | esbuild + tsc (errors), tsc only (prometheus-metrics) | - -### Existing TypeScript Package Patterns - -**@tryghost/errors (esbuild + tsc):** -- Source: `/src/*.ts` -- Output: `/cjs/` (CommonJS via esbuild), `/es/` (ESM via esbuild), `/types/` (declarations via tsc) -- Dual CJS/ESM output - -**@tryghost/prometheus-metrics (tsc only):** -- Source: `/src/*.ts` -- Output: `/build/` -- Single CJS output with declarations - -### Decisions Needed - -- [ ] **Build strategy:** Standardize on esbuild+tsc (fast builds, dual output) or tsc-only (simpler)? -- [ ] **Strictness:** Start with `strict: true` (match existing tsconfig) or `strict: false` + `allowJs: true` for gradual migration? -- [ ] **Module format:** Keep commonjs or move to ESM? (Current tsconfig targets commonjs) - -### Migration Steps - -#### Step 1: Standardize Build Infrastructure -- [ ] Decide on build strategy -- [ ] Create a shared tsconfig base that all packages extend -- [ ] Create a package template/script for conversion - -#### Step 2: Wave 1 — Leaf Packages (simple, few/no dependents) -- [ ] tpl (1 source file) -- [ ] limit (2 source files) -- [ ] color-utils (1 source file) -- [ ] root-utils (1 source file) -- [ ] timezone-data (1 source file) -- [ ] string (1 source file) -- [ ] debug (1 source file) -- [ ] parse-email-address - -#### Step 3: Wave 2 — Small Utility Packages -- [ ] validator (2 source files) -- [ ] pretty-stream (1 source file) -- [ ] config-url-helpers (2 source files) -- [ ] extract-api-key (1 source file) -- [ ] http-cache-utils (1 source file) -- [ ] minifier (1 source file) -- [ ] nql (1 source file) - -#### Step 4: Wave 3 — Medium Packages -- [ ] logging (2 source files) -- [ ] request (1 source file) -- [ ] security (6 source files) -- [ ] express-test -- [ ] custom-redirects -- [ ] members-csv -- [ ] zip -- [ ] http-stream - -#### Step 5: Wave 4 — Bookshelf Plugins (7 packages) -- [ ] bookshelf-collision -- [ ] bookshelf-eager-load -- [ ] bookshelf-filter -- [ ] bookshelf-has-posts -- [ ] bookshelf-include-count -- [ ] bookshelf-order -- [ ] bookshelf-pagination -- [ ] bookshelf-plugins - -#### Step 6: Wave 5 — Core/Complex Packages -- [ ] api-framework (16 source files — largest package) -- [ ] domain-events -- [ ] job-manager -- [ ] magic-link -- [ ] session-service -- [ ] staff-service -- [ ] mw-error-handler -- [ ] mw-vhost -- [ ] mw-session-from-token -- [ ] mw-api-version-mismatch -- [ ] mw-cache-control -- [ ] adapter-manager -- [ ] dynamic-routing-events -- [ ] email-analytics-provider-mailgun -- [ ] email-analytics-service -- [ ] mailgun-client -- [ ] member-events -- [ ] members-importer -- [ ] metrics-server -- [ ] moleculer-service-from-class -- [ ] verification-trigger -- [ ] version-notifications-data-service - -#### Per-Package Conversion Checklist -For each package: -- [ ] Create/extend `tsconfig.json` from shared base -- [ ] Rename `lib/*.js` → `src/*.ts` (or `lib/*.ts` — decide on convention) -- [ ] Add type annotations -- [ ] Add `build` script to package.json -- [ ] Update `main`, `types`, and `exports` in package.json -- [ ] Convert test files from `.test.js` → `.test.ts` -- [ ] Update `.gitignore` for build output -- [ ] Verify tests pass with 100% coverage -- [ ] Update any packages that import from this one - ---- - -## 3. Lodash Removal - -### Current State - -| Metric | Value | -|--------|-------| -| Packages using lodash | 13 | -| Files using lodash | ~22 | -| Unique functions used | ~35 | -| lodash version | 4.17.23 | - -### Function Inventory by Replacement Difficulty - -#### Easy — Direct Native Replacements - -| Lodash Function | Native Replacement | Usage Count | -|----------------|-------------------|-------------| -| `_.isString(x)` | `typeof x === 'string'` | 1 | -| `_.isArray(x)` | `Array.isArray(x)` | 3 | -| `_.isObject(x)` | `typeof x === 'object' && x !== null` | 1 | -| `_.isNumber(x)` | `typeof x === 'number'` | 1 | -| `_.isBoolean(x)` | `typeof x === 'boolean'` | 1 | -| `_.isNil(x)` | `x === null \|\| x === undefined` | 1 | -| `_.isEmpty(x)` | `Object.keys(x).length === 0` / `x.length === 0` | 7 | -| `_.each(arr, fn)` | `arr.forEach(fn)` / `for...of` | 6 | -| `_.map(arr, fn)` | `arr.map(fn)` | 1 | -| `_.filter(arr, fn)` | `arr.filter(fn)` | 1 | -| `_.find(arr, fn)` | `arr.find(fn)` | 1 | -| `_.includes(arr, v)` | `arr.includes(v)` | 1 | -| `_.toString(x)` | `String(x)` | 1 | -| `_.defaults(obj, def)` | `{ ...defaults, ...obj }` | 1 | -| `_.extend(a, b)` | `Object.assign(a, b)` | 1 | - -#### Medium — Small Utility Needed - -| Lodash Function | Native Replacement | Usage Count | -|----------------|-------------------|-------------| -| `_.pick(obj, keys)` | `Object.fromEntries(Object.entries(obj).filter(([k]) => keys.includes(k)))` | 6 | -| `_.omit(obj, keys)` | `Object.fromEntries(Object.entries(obj).filter(([k]) => !keys.includes(k)))` | 2 | -| `_.cloneDeep(x)` | `structuredClone(x)` | 4 | -| `_.merge(a, b)` | `Object.assign()` for shallow, custom for deep | 2 | -| `_.result(obj, path)` | `typeof obj[path] === 'function' ? obj[path]() : obj[path]` | 6 | -| `_.forOwn(obj, fn)` | `Object.entries(obj).forEach(([k,v]) => fn(v,k))` | 2 | -| `_.has(obj, key)` | `Object.hasOwn(obj, key)` | 2 | -| `_.uniq(arr)` | `[...new Set(arr)]` | 1 | -| `_.intersection(a, b)` | `a.filter(x => b.includes(x))` | 1 | - -#### Hard — Consider Keeping - -| Lodash Function | Notes | Usage Count | -|----------------|-------|-------------| -| `_.get(obj, path, def)` | Optional chaining + `??` works for most cases, but deep string paths need a helper | 4 | -| `lodash.template` | Used in `tpl` package — core templating. Would need a replacement library or custom impl | 2 | - -### Migration Steps by Package - -#### Phase 1: Easy Wins (type checks and array methods) -- [ ] **bookshelf-collision** — uses: `_.each` -- [ ] **bookshelf-eager-load** — uses: `_.each` -- [ ] **bookshelf-has-posts** — uses: `_.each` -- [ ] **bookshelf-include-count** — uses: `_.each`, `_.intersection` -- [ ] **bookshelf-order** — uses: `_.each` - -#### Phase 2: Medium Complexity -- [ ] **bookshelf-pagination** — uses: `_.pick`, `_.defaults`, `_.omit` -- [ ] **pretty-stream** — uses: `_.isObject`, `_.isNumber`, `_.isBoolean`, `_.isNil`, `_.isEmpty`, `_.isString`, `_.toString`, `_.each`, `_.has` -- [ ] **request** — uses: `_.extend` -- [ ] **mw-error-handler** — uses: `_.merge` - -#### Phase 3: Heavier Usage -- [ ] **api-framework** (5 files) — uses: `_.pick`, `_.omit`, `_.result`, `_.isEmpty`, `_.isArray`, `_.cloneDeep`, `_.each`, `_.filter`, `_.find`, `_.map`, `_.includes`, `_.forOwn` -- [ ] **validator** (2 files) — uses: `_.isEmpty`, `_.isString`, `_.isArray`, `_.each`, `_.has` -- [ ] **logging** (2 files) — uses: `_.cloneDeep`, `_.isEmpty`, `_.each` - -#### Phase 4: Special Cases -- [ ] **tpl** — uses `lodash.template` (separate npm package). Options: - - Keep `lodash.template` as standalone dependency (it's small) - - Replace with a lightweight template library - - Write a simple template function if usage is basic -- [ ] **errors** — lodash is only in devDependencies (+ @types/lodash). Check if it's actually used in tests - -#### Per-Package Checklist -For each package: -- [ ] Replace lodash calls with native equivalents -- [ ] Run tests to verify behavior matches -- [ ] Remove `lodash` from `dependencies` in package.json -- [ ] Remove `@types/lodash` from devDependencies if present -- [ ] Verify 100% coverage still passes - ---- - -## Progress Tracking - -### Vitest Migration: COMPLETE (42/42 packages) -### TypeScript Migration: 2/44 packages -### Lodash Removal: 0/13 packages diff --git a/packages/errors/vitest.config.ts b/packages/errors/vitest.config.ts index c9f4cb1f1..d0e49fb91 100644 --- a/packages/errors/vitest.config.ts +++ b/packages/errors/vitest.config.ts @@ -1,5 +1,7 @@ import {defineConfig} from 'vitest/config'; +// Override: TypeScript package with source in src/, not lib/. +// Coverage must be scoped to src/ to measure the right files. export default defineConfig({ test: { globals: true, diff --git a/packages/express-test/vitest.config.ts b/packages/express-test/vitest.config.ts index 2257032e7..29754eabe 100644 --- a/packages/express-test/vitest.config.ts +++ b/packages/express-test/vitest.config.ts @@ -1,5 +1,7 @@ import {defineConfig} from 'vitest/config'; +// Override: setupFiles needed to initialize the snapshot test registry +// before each test. Coverage was never enforced under Mocha. export default defineConfig({ test: { globals: true, diff --git a/packages/http-cache-utils/vitest.config.ts b/packages/http-cache-utils/vitest.config.ts index f1b98db11..82d3fff60 100644 --- a/packages/http-cache-utils/vitest.config.ts +++ b/packages/http-cache-utils/vitest.config.ts @@ -1,5 +1,6 @@ import {defineConfig} from 'vitest/config'; +// Override: coverage was never enforced under Mocha. export default defineConfig({ test: { globals: true, diff --git a/packages/prometheus-metrics/vitest.config.ts b/packages/prometheus-metrics/vitest.config.ts index c9f4cb1f1..d0e49fb91 100644 --- a/packages/prometheus-metrics/vitest.config.ts +++ b/packages/prometheus-metrics/vitest.config.ts @@ -1,5 +1,7 @@ import {defineConfig} from 'vitest/config'; +// Override: TypeScript package with source in src/, not lib/. +// Coverage must be scoped to src/ to measure the right files. export default defineConfig({ test: { globals: true, diff --git a/packages/webhook-mock-receiver/vitest.config.ts b/packages/webhook-mock-receiver/vitest.config.ts index f1b98db11..82d3fff60 100644 --- a/packages/webhook-mock-receiver/vitest.config.ts +++ b/packages/webhook-mock-receiver/vitest.config.ts @@ -1,5 +1,6 @@ import {defineConfig} from 'vitest/config'; +// Override: coverage was never enforced under Mocha. export default defineConfig({ test: { globals: true, From 5d21679cf8ae526c739b0f8b0ec98fd5d726df57 Mon Sep 17 00:00:00 2001 From: Steve Larson <9larsons@gmail.com> Date: Mon, 2 Mar 2026 15:04:16 -0600 Subject: [PATCH 6/8] Simplify vitest config overrides to extend root config Use mergeConfig to extend the shared root vitest.config.ts instead of duplicating the entire configuration. Each override now only specifies what differs from the root. --- packages/errors/vitest.config.ts | 21 ++++---------- packages/express-test/vitest.config.ts | 13 +++------ packages/http-cache-utils/vitest.config.ts | 13 +++------ packages/job-manager/vitest.config.ts | 29 +++++-------------- packages/prometheus-metrics/vitest.config.ts | 21 ++++---------- .../webhook-mock-receiver/vitest.config.ts | 13 +++------ 6 files changed, 30 insertions(+), 80 deletions(-) diff --git a/packages/errors/vitest.config.ts b/packages/errors/vitest.config.ts index d0e49fb91..0a51d09c0 100644 --- a/packages/errors/vitest.config.ts +++ b/packages/errors/vitest.config.ts @@ -1,23 +1,12 @@ -import {defineConfig} from 'vitest/config'; +import {defineConfig, mergeConfig} from 'vitest/config'; +import rootConfig from '../../vitest.config'; // Override: TypeScript package with source in src/, not lib/. // Coverage must be scoped to src/ to measure the right files. -export default defineConfig({ +export default mergeConfig(rootConfig, defineConfig({ test: { - globals: true, - environment: 'node', - include: ['test/**/*.test.{js,ts}'], coverage: { - provider: 'v8', - all: true, - include: ['src/**'], - reporter: ['text', 'cobertura'], - thresholds: { - lines: 90, - functions: 90, - branches: 90, - statements: 90 - } + include: ['src/**'] } } -}); +})); diff --git a/packages/express-test/vitest.config.ts b/packages/express-test/vitest.config.ts index 29754eabe..8c3c01555 100644 --- a/packages/express-test/vitest.config.ts +++ b/packages/express-test/vitest.config.ts @@ -1,17 +1,12 @@ -import {defineConfig} from 'vitest/config'; +import {defineConfig, mergeConfig} from 'vitest/config'; +import rootConfig from '../../vitest.config'; // Override: setupFiles needed to initialize the snapshot test registry // before each test. Coverage was never enforced under Mocha. -export default defineConfig({ +export default mergeConfig(rootConfig, defineConfig({ test: { - globals: true, - environment: 'node', - include: ['test/**/*.test.{js,ts}'], setupFiles: ['./test/utils/overrides.js'], coverage: { - provider: 'v8', - all: true, - reporter: ['text', 'cobertura'], thresholds: { lines: 0, functions: 0, @@ -20,4 +15,4 @@ export default defineConfig({ } } } -}); +})); diff --git a/packages/http-cache-utils/vitest.config.ts b/packages/http-cache-utils/vitest.config.ts index 82d3fff60..c7bee7abe 100644 --- a/packages/http-cache-utils/vitest.config.ts +++ b/packages/http-cache-utils/vitest.config.ts @@ -1,15 +1,10 @@ -import {defineConfig} from 'vitest/config'; +import {defineConfig, mergeConfig} from 'vitest/config'; +import rootConfig from '../../vitest.config'; // Override: coverage was never enforced under Mocha. -export default defineConfig({ +export default mergeConfig(rootConfig, defineConfig({ test: { - globals: true, - environment: 'node', - include: ['test/**/*.test.{js,ts}'], coverage: { - provider: 'v8', - all: true, - reporter: ['text', 'cobertura'], thresholds: { lines: 0, functions: 0, @@ -18,4 +13,4 @@ export default defineConfig({ } } } -}); +})); diff --git a/packages/job-manager/vitest.config.ts b/packages/job-manager/vitest.config.ts index e11870530..601b1a6ad 100644 --- a/packages/job-manager/vitest.config.ts +++ b/packages/job-manager/vitest.config.ts @@ -1,24 +1,11 @@ -import {defineConfig} from 'vitest/config'; +import {defineConfig, mergeConfig} from 'vitest/config'; +import rootConfig from '../../vitest.config'; -export default defineConfig({ +// Override: Bree spawns background workers that emit unhandled rejections +// during cleanup after tests complete. These are expected and were silently +// ignored by Mocha. +export default mergeConfig(rootConfig, defineConfig({ test: { - globals: true, - environment: 'node', - include: ['test/**/*.test.{js,ts}'], - // Job manager tests spawn background workers that emit unhandled - // rejections after the test completes (e.g. bree job cleanup). - // These are expected and were silently ignored by Mocha. - dangerouslyIgnoreUnhandledErrors: true, - coverage: { - provider: 'v8', - all: true, - reporter: ['text', 'cobertura'], - thresholds: { - lines: 90, - functions: 90, - branches: 90, - statements: 90 - } - } + dangerouslyIgnoreUnhandledErrors: true } -}); +})); diff --git a/packages/prometheus-metrics/vitest.config.ts b/packages/prometheus-metrics/vitest.config.ts index d0e49fb91..0a51d09c0 100644 --- a/packages/prometheus-metrics/vitest.config.ts +++ b/packages/prometheus-metrics/vitest.config.ts @@ -1,23 +1,12 @@ -import {defineConfig} from 'vitest/config'; +import {defineConfig, mergeConfig} from 'vitest/config'; +import rootConfig from '../../vitest.config'; // Override: TypeScript package with source in src/, not lib/. // Coverage must be scoped to src/ to measure the right files. -export default defineConfig({ +export default mergeConfig(rootConfig, defineConfig({ test: { - globals: true, - environment: 'node', - include: ['test/**/*.test.{js,ts}'], coverage: { - provider: 'v8', - all: true, - include: ['src/**'], - reporter: ['text', 'cobertura'], - thresholds: { - lines: 90, - functions: 90, - branches: 90, - statements: 90 - } + include: ['src/**'] } } -}); +})); diff --git a/packages/webhook-mock-receiver/vitest.config.ts b/packages/webhook-mock-receiver/vitest.config.ts index 82d3fff60..c7bee7abe 100644 --- a/packages/webhook-mock-receiver/vitest.config.ts +++ b/packages/webhook-mock-receiver/vitest.config.ts @@ -1,15 +1,10 @@ -import {defineConfig} from 'vitest/config'; +import {defineConfig, mergeConfig} from 'vitest/config'; +import rootConfig from '../../vitest.config'; // Override: coverage was never enforced under Mocha. -export default defineConfig({ +export default mergeConfig(rootConfig, defineConfig({ test: { - globals: true, - environment: 'node', - include: ['test/**/*.test.{js,ts}'], coverage: { - provider: 'v8', - all: true, - reporter: ['text', 'cobertura'], thresholds: { lines: 0, functions: 0, @@ -18,4 +13,4 @@ export default defineConfig({ } } } -}); +})); From e15a83d6ead27c909e5fef372b5ee868a99928dc Mon Sep 17 00:00:00 2001 From: Steve Larson <9larsons@gmail.com> Date: Mon, 2 Mar 2026 15:08:35 -0600 Subject: [PATCH 7/8] Remove unnecessary vitest config overrides MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit http-cache-utils, webhook-mock-receiver, and express-test all have 100% coverage — no need for lowered thresholds. Remove standalone configs for the first two (use root config), and simplify express-test to only override setupFiles. --- packages/express-test/vitest.config.ts | 12 ++---------- packages/http-cache-utils/package.json | 2 +- packages/http-cache-utils/vitest.config.ts | 16 ---------------- packages/webhook-mock-receiver/package.json | 2 +- packages/webhook-mock-receiver/vitest.config.ts | 16 ---------------- 5 files changed, 4 insertions(+), 44 deletions(-) delete mode 100644 packages/http-cache-utils/vitest.config.ts delete mode 100644 packages/webhook-mock-receiver/vitest.config.ts diff --git a/packages/express-test/vitest.config.ts b/packages/express-test/vitest.config.ts index 8c3c01555..c69603f55 100644 --- a/packages/express-test/vitest.config.ts +++ b/packages/express-test/vitest.config.ts @@ -2,17 +2,9 @@ import {defineConfig, mergeConfig} from 'vitest/config'; import rootConfig from '../../vitest.config'; // Override: setupFiles needed to initialize the snapshot test registry -// before each test. Coverage was never enforced under Mocha. +// before each test (replaces Mocha's --require flag). export default mergeConfig(rootConfig, defineConfig({ test: { - setupFiles: ['./test/utils/overrides.js'], - coverage: { - thresholds: { - lines: 0, - functions: 0, - branches: 0, - statements: 0 - } - } + setupFiles: ['./test/utils/overrides.js'] } })); diff --git a/packages/http-cache-utils/package.json b/packages/http-cache-utils/package.json index 57082c849..17cb9eac6 100644 --- a/packages/http-cache-utils/package.json +++ b/packages/http-cache-utils/package.json @@ -11,7 +11,7 @@ "main": "index.js", "scripts": { "dev": "echo \"Implement me!\"", - "test:unit": "NODE_ENV=testing vitest run --coverage", + "test:unit": "NODE_ENV=testing vitest run --coverage --config ../../vitest.config.ts", "test": "yarn test:unit", "lint:code": "eslint *.js lib/ --ext .js --cache", "lint": "yarn lint:code && yarn lint:test", diff --git a/packages/http-cache-utils/vitest.config.ts b/packages/http-cache-utils/vitest.config.ts deleted file mode 100644 index c7bee7abe..000000000 --- a/packages/http-cache-utils/vitest.config.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {defineConfig, mergeConfig} from 'vitest/config'; -import rootConfig from '../../vitest.config'; - -// Override: coverage was never enforced under Mocha. -export default mergeConfig(rootConfig, defineConfig({ - test: { - coverage: { - thresholds: { - lines: 0, - functions: 0, - branches: 0, - statements: 0 - } - } - } -})); diff --git a/packages/webhook-mock-receiver/package.json b/packages/webhook-mock-receiver/package.json index 7012b514e..904732f30 100644 --- a/packages/webhook-mock-receiver/package.json +++ b/packages/webhook-mock-receiver/package.json @@ -11,7 +11,7 @@ "main": "index.js", "scripts": { "dev": "echo \"Implement me!\"", - "test": "NODE_ENV=testing vitest run --coverage", + "test": "NODE_ENV=testing vitest run --coverage --config ../../vitest.config.ts", "lint:code": "eslint *.js lib/ --ext .js --cache", "lint": "yarn lint:code && yarn lint:test", "lint:test": "eslint -c test/.eslintrc.js test/ --ext .js --cache", diff --git a/packages/webhook-mock-receiver/vitest.config.ts b/packages/webhook-mock-receiver/vitest.config.ts deleted file mode 100644 index c7bee7abe..000000000 --- a/packages/webhook-mock-receiver/vitest.config.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {defineConfig, mergeConfig} from 'vitest/config'; -import rootConfig from '../../vitest.config'; - -// Override: coverage was never enforced under Mocha. -export default mergeConfig(rootConfig, defineConfig({ - test: { - coverage: { - thresholds: { - lines: 0, - functions: 0, - branches: 0, - statements: 0 - } - } - } -})); From 3553734f83bfb825451db71a5b530232e8ee735a Mon Sep 17 00:00:00 2001 From: Steve Larson <9larsons@gmail.com> Date: Mon, 2 Mar 2026 15:16:25 -0600 Subject: [PATCH 8/8] Address review feedback on metrics and root-utils tests - metrics: restore process.env.MODE to undefined baseline in afterEach - root-utils: wrap Module._load mock in try/finally for safe cleanup --- packages/metrics/test/metrics.test.js | 2 ++ packages/root-utils/test/root-utils.test.js | 13 +++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/metrics/test/metrics.test.js b/packages/metrics/test/metrics.test.js index 69b3e3488..f8016b59a 100644 --- a/packages/metrics/test/metrics.test.js +++ b/packages/metrics/test/metrics.test.js @@ -16,6 +16,8 @@ beforeEach(function () { afterEach(function () { if (originalMode !== undefined) { process.env.MODE = originalMode; + } else { + delete process.env.MODE; } }); diff --git a/packages/root-utils/test/root-utils.test.js b/packages/root-utils/test/root-utils.test.js index f790edef2..5e247b822 100644 --- a/packages/root-utils/test/root-utils.test.js +++ b/packages/root-utils/test/root-utils.test.js @@ -15,12 +15,13 @@ function loadRootUtilsWithMocks(mocks) { return originalLoad(request, parent, isMain); }; - delete require.cache[rootUtilsModulePath]; - const loaded = require('../lib/root-utils'); - - Module._load = originalLoad; - delete require.cache[rootUtilsModulePath]; - return loaded; + try { + delete require.cache[rootUtilsModulePath]; + return require('../lib/root-utils'); + } finally { + Module._load = originalLoad; + delete require.cache[rootUtilsModulePath]; + } } describe('getCallerRoot', function () {