diff --git a/cli/init.js b/cli/init.js index db15d6cf0..412a54c0f 100755 --- a/cli/init.js +++ b/cli/init.js @@ -2,7 +2,7 @@ const fs = require('fs') const path = require('path') const chalk = require('chalk') const { until } = require('@open-draft/until') -const confirm = require('@inquirer/confirm') +const confirm = require('@inquirer/confirm').default const invariant = require('./invariant') const { SERVICE_WORKER_BUILD_PATH } = require('../config/constants') diff --git a/config/replaceCoreImports.js b/config/replaceCoreImports.js index 3087485cf..1033c1a72 100644 --- a/config/replaceCoreImports.js +++ b/config/replaceCoreImports.js @@ -1,10 +1,17 @@ -function replaceCoreImports(fileContents, isEsm) { - const importPattern = isEsm - ? /from ["'](~\/core(.*))["'](;)?$/gm - : /require\(["'](~\/core(.*))["']\)(;)?/gm +const CORE_ESM_IMPORT_PATTERN = /from ["'](~\/core(.*))["'](;)?/gm +const CORE_CJS_IMPORT_PATTERN = /require\(["'](~\/core(.*))["']\)(;)?/gm + +function getCoreImportPattern(isEsm) { + return isEsm ? CORE_ESM_IMPORT_PATTERN : CORE_CJS_IMPORT_PATTERN +} +function hasCoreImports(fileContents, isEsm) { + return getCoreImportPattern(isEsm).test(fileContents) +} + +function replaceCoreImports(fileContents, isEsm) { return fileContents.replace( - importPattern, + getCoreImportPattern(isEsm), (_, __, maybeSubmodulePath, maybeSemicolon) => { const submodulePath = maybeSubmodulePath || '/index' const semicolon = maybeSemicolon || '' @@ -17,5 +24,6 @@ function replaceCoreImports(fileContents, isEsm) { } module.exports = { + hasCoreImports, replaceCoreImports, } diff --git a/config/scripts/patch-ts.js b/config/scripts/patch-ts.js index fa25c7895..7f413f736 100644 --- a/config/scripts/patch-ts.js +++ b/config/scripts/patch-ts.js @@ -1,29 +1,106 @@ -const fs = require('fs') -const path = require('path') -const { replaceCoreImports } = require('../replaceCoreImports') +const fs = require('node:fs') +const { exec } = require('node:child_process') +const path = require('node:path') +const { promisify } = require('node:util') +const { invariant } = require('outvariant') +const glob = require('glob') +const { hasCoreImports, replaceCoreImports } = require('../replaceCoreImports') + +const execAsync = promisify(exec) + +const BUILD_DIR = path.resolve(__dirname, '../../lib') async function patchTypeDefs() { - const typeDefsPaths = [ - path.resolve(__dirname, '../..', 'lib/browser/index.d.ts'), - path.resolve(__dirname, '../..', 'lib/node/index.d.ts'), - path.resolve(__dirname, '../..', 'lib/native/index.d.ts'), - ] + const typeDefsPaths = glob.sync('**/*.d.{ts,mts}', { + cwd: BUILD_DIR, + absolute: true, + }) + const typeDefsWithCoreImports = typeDefsPaths + .map((modulePath) => { + const fileContents = fs.readFileSync(modulePath, 'utf8') + if (hasCoreImports(fileContents, true)) { + return [modulePath, fileContents] + } + }) + .filter(Boolean) - for (const typeDefsPath of typeDefsPaths) { - if (!fs.existsSync(typeDefsPath)) { - continue - } + if (typeDefsWithCoreImports.length === 0) { + console.log( + 'Found no .d.ts modules containing the "~/core" import, skipping...', + ) + return process.exit(0) + } - const fileContents = fs.readFileSync(typeDefsPath, 'utf8') + console.log( + 'Found %d module(s) with the "~/core" import, resolving...', + typeDefsWithCoreImports.length, + ) + for (const [typeDefsPath, fileContents] of typeDefsWithCoreImports) { // Treat ".d.ts" files as ESM to replace "import" statements. // Force no extension on the ".d.ts" imports. const nextFileContents = replaceCoreImports(fileContents, true) - fs.writeFileSync(typeDefsPath, nextFileContents, 'utf8') + console.log('Successfully patched "%s"!', typeDefsPath) + } + + console.log( + 'Imports resolved in %d file(s), verifying...', + typeDefsWithCoreImports.length, + ) + + // Next, validate that we left no "~/core" imports unresolved. + const result = await execAsync( + `grep "~/core" ./**/*.{ts,mts} -R -l || exit 0`, + { + cwd: BUILD_DIR, + shell: '/bin/bash', + }, + ) + + invariant( + result.stderr === '', + 'Failed to validate the .d.ts modules for the presence of the "~/core" import. See the original error below.', + result.stderr, + ) - console.log('Successfully patched at "%s"!', typeDefsPath) + if (result.stdout !== '') { + const modulesWithUnresolvedImports = result.stdout + .split('\n') + .filter(Boolean) + + console.error( + `Found .d.ts modules containing unresolved "~/core" import after the patching: + + ${modulesWithUnresolvedImports.map((path) => ` - ${path}`).join('\n')} + `, + ) + + return process.exit(1) + } + + // Ensure that the .d.ts files compile without errors after resolving the "~/core" imports. + console.log('Compiling the .d.ts modules with tsc...') + const tscCompilation = await execAsync( + `tsc --noEmit --skipLibCheck ${typeDefsPaths.join(' ')}`, + { + cwd: BUILD_DIR, + }, + ) + + if (tscCompilation.stderr !== '') { + console.error( + 'Failed to compile the .d.ts modules with tsc. See the original error below.', + tscCompilation.stderr, + ) + + return process.exit(1) } + + console.log( + 'The "~/core" imports resolved successfully in %d .d.ts modules! 🎉', + typeDefsWithCoreImports.length, + ) } patchTypeDefs() diff --git a/package.json b/package.json index 92637b4f6..c4d7ad127 100644 --- a/package.json +++ b/package.json @@ -93,10 +93,7 @@ "url": "https://github.com/kettanaito" }, "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mswjs" - }, + "funding": "https://github.com/sponsors/mswjs", "files": [ "config/constants.js", "config/scripts/postinstall.js", @@ -147,7 +144,7 @@ "@commitlint/config-conventional": "^18.4.4", "@fastify/websocket": "^8.3.1", "@open-draft/test-server": "^0.4.2", - "@ossjs/release": "^0.8.0", + "@ossjs/release": "^0.8.1", "@playwright/test": "^1.40.1", "@swc/core": "^1.3.102", "@types/express": "^4.17.21", @@ -156,8 +153,8 @@ "@types/json-bigint": "^1.0.4", "@types/node": "18.x", "@types/ws": "^8.5.10", - "@typescript-eslint/eslint-plugin": "^5.11.0", - "@typescript-eslint/parser": "^5.11.0", + "@typescript-eslint/eslint-plugin": "^7.2.0", + "@typescript-eslint/parser": "^7.2.0", "@web/dev-server": "^0.1.38", "axios": "^1.6.5", "babel-minify": "^0.5.1", @@ -167,7 +164,7 @@ "cz-conventional-changelog": "3.3.0", "esbuild": "^0.19.11", "esbuild-loader": "^4.0.2", - "eslint": "^8.56.0", + "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.1.3", "express": "^4.18.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 373ea46e2..0518856f2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,7 +11,7 @@ specifiers: '@mswjs/interceptors': ^0.26.8 '@open-draft/test-server': ^0.4.2 '@open-draft/until': ^2.1.0 - '@ossjs/release': ^0.8.0 + '@ossjs/release': ^0.8.1 '@playwright/test': ^1.40.1 '@swc/core': ^1.3.102 '@types/cookie': ^0.6.0 @@ -22,8 +22,8 @@ specifiers: '@types/node': 18.x '@types/statuses': ^2.0.4 '@types/ws': ^8.5.10 - '@typescript-eslint/eslint-plugin': ^5.11.0 - '@typescript-eslint/parser': ^5.11.0 + '@typescript-eslint/eslint-plugin': ^7.2.0 + '@typescript-eslint/parser': ^7.2.0 '@web/dev-server': ^0.1.38 axios: ^1.6.5 babel-minify: ^0.5.1 @@ -34,7 +34,7 @@ specifiers: cz-conventional-changelog: 3.3.0 esbuild: ^0.19.11 esbuild-loader: ^4.0.2 - eslint: ^8.56.0 + eslint: ^8.57.0 eslint-config-prettier: ^9.1.0 eslint-plugin-prettier: ^5.1.3 express: ^4.18.2 @@ -92,7 +92,7 @@ devDependencies: '@commitlint/config-conventional': 18.6.2 '@fastify/websocket': 8.3.1 '@open-draft/test-server': 0.4.2 - '@ossjs/release': 0.8.0 + '@ossjs/release': 0.8.1 '@playwright/test': 1.41.2 '@swc/core': 1.4.2 '@types/express': 4.17.21 @@ -101,8 +101,8 @@ devDependencies: '@types/json-bigint': 1.0.4 '@types/node': 18.19.17 '@types/ws': 8.5.10 - '@typescript-eslint/eslint-plugin': 5.62.0_jbhrznr3jcixjmlfip44232stu - '@typescript-eslint/parser': 5.62.0_j5xxi3skikxmgrjgngycki2pt4 + '@typescript-eslint/eslint-plugin': 7.2.0_sgabwklfavlej7ajy5bmtxbg6i + '@typescript-eslint/parser': 7.2.0_5iotri5aeibafrgj7thsggiclm '@web/dev-server': 0.1.38 axios: 1.6.7 babel-minify: 0.5.2 @@ -112,9 +112,9 @@ devDependencies: cz-conventional-changelog: 3.3.0_ldhiuk7svr7x2l7bfqwjwnkvgy esbuild: 0.19.12 esbuild-loader: 4.0.3_webpack@5.90.3 - eslint: 8.56.0 - eslint-config-prettier: 9.1.0_eslint@8.56.0 - eslint-plugin-prettier: 5.1.3_roii55ymmf2x2gcdisgexmmcgu + eslint: 8.57.0 + eslint-config-prettier: 9.1.0_eslint@8.57.0 + eslint-plugin-prettier: 5.1.3_udeglvt2rvqfn252kbszwwr7nu express: 4.18.2 fastify: 4.26.2 fs-extra: 11.2.0 @@ -133,8 +133,8 @@ devDependencies: typescript: 5.4.2 undici: 5.28.3 url-loader: 4.1.1_webpack@5.90.3 - vitest: 1.3.1_jz4daltgcxlsto6zys62wxwil4 - vitest-environment-miniflare: 2.14.2_vitest@1.3.1 + vitest: 1.4.0_jz4daltgcxlsto6zys62wxwil4 + vitest-environment-miniflare: 2.14.2_vitest@1.4.0 webpack: 5.90.3_hcs6zs6br3cvrjsek5ynrm4maa webpack-http-server: 0.5.0_hcs6zs6br3cvrjsek5ynrm4maa @@ -755,13 +755,13 @@ packages: dev: true optional: true - /@eslint-community/eslint-utils/4.4.0_eslint@8.56.0: + /@eslint-community/eslint-utils/4.4.0_eslint@8.57.0: resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 dependencies: - eslint: 8.56.0 + eslint: 8.57.0 eslint-visitor-keys: 3.4.3 dev: true @@ -787,8 +787,8 @@ packages: - supports-color dev: true - /@eslint/js/8.56.0: - resolution: {integrity: sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==} + /@eslint/js/8.57.0: + resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true @@ -1178,8 +1178,8 @@ packages: /@open-draft/until/2.1.0: resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} - /@ossjs/release/0.8.0: - resolution: {integrity: sha512-vzxhYvad/Ub3j8bWWCRfdwTvFzK3HtKjm8IM5J+7njnQcZZie5iouUXX+G65OI3F1YgQSWvsozrWqHyN1x7fjQ==} + /@ossjs/release/0.8.1: + resolution: {integrity: sha512-gApVH7M47Mkh9GNMpd/LJi72KlCUcl/t0lbTP082APlHIQUzyQObcMyfLOjEwRdxyyYJtKlbTMzoDf/+NNIIiQ==} hasBin: true dependencies: '@open-draft/deferred-promise': 2.2.0 @@ -1542,7 +1542,7 @@ packages: /@types/conventional-commits-parser/3.0.6: resolution: {integrity: sha512-z4crlplLzL9uA5kbE4ZghAf5RbrEr1UL/uNGGgxYbJjI0jRBjuYKuasbo13ANZsSapLTM2DLZk6LDcjemow4qQ==} dependencies: - '@types/node': 18.19.17 + '@types/node': 20.11.19 dev: true /@types/cookie/0.4.1: @@ -1710,7 +1710,7 @@ packages: /@types/node-fetch/2.6.11: resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==} dependencies: - '@types/node': 18.19.17 + '@types/node': 20.11.19 form-data: 4.0.0 dev: true @@ -1814,133 +1814,135 @@ packages: '@types/yargs-parser': 21.0.3 dev: true - /@typescript-eslint/eslint-plugin/5.62.0_jbhrznr3jcixjmlfip44232stu: - resolution: {integrity: sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /@typescript-eslint/eslint-plugin/7.2.0_sgabwklfavlej7ajy5bmtxbg6i: + resolution: {integrity: sha512-mdekAHOqS9UjlmyF/LSs6AIEvfceV749GFxoBAjwAv0nkevfKHWQFDMcBZWUiIC5ft6ePWivXoS36aKQ0Cy3sw==} + engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: - '@typescript-eslint/parser': ^5.0.0 - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + '@typescript-eslint/parser': ^7.0.0 + eslint: ^8.56.0 typescript: '*' peerDependenciesMeta: typescript: optional: true dependencies: '@eslint-community/regexpp': 4.10.0 - '@typescript-eslint/parser': 5.62.0_j5xxi3skikxmgrjgngycki2pt4 - '@typescript-eslint/scope-manager': 5.62.0 - '@typescript-eslint/type-utils': 5.62.0_j5xxi3skikxmgrjgngycki2pt4 - '@typescript-eslint/utils': 5.62.0_j5xxi3skikxmgrjgngycki2pt4 + '@typescript-eslint/parser': 7.2.0_5iotri5aeibafrgj7thsggiclm + '@typescript-eslint/scope-manager': 7.2.0 + '@typescript-eslint/type-utils': 7.2.0_5iotri5aeibafrgj7thsggiclm + '@typescript-eslint/utils': 7.2.0_5iotri5aeibafrgj7thsggiclm + '@typescript-eslint/visitor-keys': 7.2.0 debug: 4.3.4 - eslint: 8.56.0 + eslint: 8.57.0 graphemer: 1.4.0 ignore: 5.3.1 - natural-compare-lite: 1.4.0 + natural-compare: 1.4.0 semver: 7.6.0 - tsutils: 3.21.0_typescript@5.4.2 + ts-api-utils: 1.3.0_typescript@5.4.2 typescript: 5.4.2 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/parser/5.62.0_j5xxi3skikxmgrjgngycki2pt4: - resolution: {integrity: sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /@typescript-eslint/parser/7.2.0_5iotri5aeibafrgj7thsggiclm: + resolution: {integrity: sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==} + engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + eslint: ^8.56.0 typescript: '*' peerDependenciesMeta: typescript: optional: true dependencies: - '@typescript-eslint/scope-manager': 5.62.0 - '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/typescript-estree': 5.62.0_typescript@5.4.2 + '@typescript-eslint/scope-manager': 7.2.0 + '@typescript-eslint/types': 7.2.0 + '@typescript-eslint/typescript-estree': 7.2.0_typescript@5.4.2 + '@typescript-eslint/visitor-keys': 7.2.0 debug: 4.3.4 - eslint: 8.56.0 + eslint: 8.57.0 typescript: 5.4.2 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/scope-manager/5.62.0: - resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /@typescript-eslint/scope-manager/7.2.0: + resolution: {integrity: sha512-Qh976RbQM/fYtjx9hs4XkayYujB/aPwglw2choHmf3zBjB4qOywWSdt9+KLRdHubGcoSwBnXUH2sR3hkyaERRg==} + engines: {node: ^16.0.0 || >=18.0.0} dependencies: - '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/visitor-keys': 5.62.0 + '@typescript-eslint/types': 7.2.0 + '@typescript-eslint/visitor-keys': 7.2.0 dev: true - /@typescript-eslint/type-utils/5.62.0_j5xxi3skikxmgrjgngycki2pt4: - resolution: {integrity: sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /@typescript-eslint/type-utils/7.2.0_5iotri5aeibafrgj7thsggiclm: + resolution: {integrity: sha512-xHi51adBHo9O9330J8GQYQwrKBqbIPJGZZVQTHHmy200hvkLZFWJIFtAG/7IYTWUyun6DE6w5InDReePJYJlJA==} + engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: - eslint: '*' + eslint: ^8.56.0 typescript: '*' peerDependenciesMeta: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': 5.62.0_typescript@5.4.2 - '@typescript-eslint/utils': 5.62.0_j5xxi3skikxmgrjgngycki2pt4 + '@typescript-eslint/typescript-estree': 7.2.0_typescript@5.4.2 + '@typescript-eslint/utils': 7.2.0_5iotri5aeibafrgj7thsggiclm debug: 4.3.4 - eslint: 8.56.0 - tsutils: 3.21.0_typescript@5.4.2 + eslint: 8.57.0 + ts-api-utils: 1.3.0_typescript@5.4.2 typescript: 5.4.2 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/types/5.62.0: - resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /@typescript-eslint/types/7.2.0: + resolution: {integrity: sha512-XFtUHPI/abFhm4cbCDc5Ykc8npOKBSJePY3a3s+lwumt7XWJuzP5cZcfZ610MIPHjQjNsOLlYK8ASPaNG8UiyA==} + engines: {node: ^16.0.0 || >=18.0.0} dev: true - /@typescript-eslint/typescript-estree/5.62.0_typescript@5.4.2: - resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /@typescript-eslint/typescript-estree/7.2.0_typescript@5.4.2: + resolution: {integrity: sha512-cyxS5WQQCoBwSakpMrvMXuMDEbhOo9bNHHrNcEWis6XHx6KF518tkF1wBvKIn/tpq5ZpUYK7Bdklu8qY0MsFIA==} + engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: typescript: '*' peerDependenciesMeta: typescript: optional: true dependencies: - '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/visitor-keys': 5.62.0 + '@typescript-eslint/types': 7.2.0 + '@typescript-eslint/visitor-keys': 7.2.0 debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 + minimatch: 9.0.3 semver: 7.6.0 - tsutils: 3.21.0_typescript@5.4.2 + ts-api-utils: 1.3.0_typescript@5.4.2 typescript: 5.4.2 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/utils/5.62.0_j5xxi3skikxmgrjgngycki2pt4: - resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /@typescript-eslint/utils/7.2.0_5iotri5aeibafrgj7thsggiclm: + resolution: {integrity: sha512-YfHpnMAGb1Eekpm3XRK8hcMwGLGsnT6L+7b2XyRv6ouDuJU1tZir1GS2i0+VXRatMwSI1/UfcyPe53ADkU+IuA==} + engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + eslint: ^8.56.0 dependencies: - '@eslint-community/eslint-utils': 4.4.0_eslint@8.56.0 + '@eslint-community/eslint-utils': 4.4.0_eslint@8.57.0 '@types/json-schema': 7.0.15 '@types/semver': 7.5.7 - '@typescript-eslint/scope-manager': 5.62.0 - '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/typescript-estree': 5.62.0_typescript@5.4.2 - eslint: 8.56.0 - eslint-scope: 5.1.1 + '@typescript-eslint/scope-manager': 7.2.0 + '@typescript-eslint/types': 7.2.0 + '@typescript-eslint/typescript-estree': 7.2.0_typescript@5.4.2 + eslint: 8.57.0 semver: 7.6.0 transitivePeerDependencies: - supports-color - typescript dev: true - /@typescript-eslint/visitor-keys/5.62.0: - resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /@typescript-eslint/visitor-keys/7.2.0: + resolution: {integrity: sha512-c6EIQRHhcpl6+tO8EMR+kjkkV+ugUNXOmeASA1rlzkd8EPIriavpWoiEz1HR/VLhbVIdhqnV6E7JZm00cBDx2A==} + engines: {node: ^16.0.0 || >=18.0.0} dependencies: - '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/types': 7.2.0 eslint-visitor-keys: 3.4.3 dev: true @@ -1948,38 +1950,38 @@ packages: resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} dev: true - /@vitest/expect/1.3.1: - resolution: {integrity: sha512-xofQFwIzfdmLLlHa6ag0dPV8YsnKOCP1KdAeVVh34vSjN2dcUiXYCD9htu/9eM7t8Xln4v03U9HLxLpPlsXdZw==} + /@vitest/expect/1.4.0: + resolution: {integrity: sha512-Jths0sWCJZ8BxjKe+p+eKsoqev1/T8lYcrjavEaz8auEJ4jAVY0GwW3JKmdVU4mmNPLPHixh4GNXP7GFtAiDHA==} dependencies: - '@vitest/spy': 1.3.1 - '@vitest/utils': 1.3.1 + '@vitest/spy': 1.4.0 + '@vitest/utils': 1.4.0 chai: 4.4.1 dev: true - /@vitest/runner/1.3.1: - resolution: {integrity: sha512-5FzF9c3jG/z5bgCnjr8j9LNq/9OxV2uEBAITOXfoe3rdZJTdO7jzThth7FXv/6b+kdY65tpRQB7WaKhNZwX+Kg==} + /@vitest/runner/1.4.0: + resolution: {integrity: sha512-EDYVSmesqlQ4RD2VvWo3hQgTJ7ZrFQ2VSJdfiJiArkCerDAGeyF1i6dHkmySqk573jLp6d/cfqCN+7wUB5tLgg==} dependencies: - '@vitest/utils': 1.3.1 + '@vitest/utils': 1.4.0 p-limit: 5.0.0 pathe: 1.1.2 dev: true - /@vitest/snapshot/1.3.1: - resolution: {integrity: sha512-EF++BZbt6RZmOlE3SuTPu/NfwBF6q4ABS37HHXzs2LUVPBLx2QoY/K0fKpRChSo8eLiuxcbCVfqKgx/dplCDuQ==} + /@vitest/snapshot/1.4.0: + resolution: {integrity: sha512-saAFnt5pPIA5qDGxOHxJ/XxhMFKkUSBJmVt5VgDsAqPTX6JP326r5C/c9UuCMPoXNzuudTPsYDZCoJ5ilpqG2A==} dependencies: magic-string: 0.30.7 pathe: 1.1.2 pretty-format: 29.7.0 dev: true - /@vitest/spy/1.3.1: - resolution: {integrity: sha512-xAcW+S099ylC9VLU7eZfdT9myV67Nor9w9zhf0mGCYJSO+zM2839tOeROTdikOi/8Qeusffvxb/MyBSOja1Uig==} + /@vitest/spy/1.4.0: + resolution: {integrity: sha512-Ywau/Qs1DzM/8Uc+yA77CwSegizMlcgTJuYGAi0jujOteJOUf1ujunHThYo243KG9nAyWT3L9ifPYZ5+As/+6Q==} dependencies: tinyspy: 2.2.1 dev: true - /@vitest/utils/1.3.1: - resolution: {integrity: sha512-d3Waie/299qqRyHTm2DjADeTaNdNSVsnwHPWrs20JMpjh6eiVq7ggggweO8rc4arhf6rRkWuHKwvxGvejUXZZQ==} + /@vitest/utils/1.4.0: + resolution: {integrity: sha512-mx3Yd1/6e2Vt/PUC98DcqTirtfxUyAZ32uK82r8rZzbtBeBo+nqgnjx/LvqQdWsrvNtm14VmurNgcf4nqY5gJg==} dependencies: diff-sequences: 29.6.3 estree-walker: 3.0.3 @@ -3748,16 +3750,16 @@ packages: engines: {node: '>=10'} dev: true - /eslint-config-prettier/9.1.0_eslint@8.56.0: + /eslint-config-prettier/9.1.0_eslint@8.57.0: resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} hasBin: true peerDependencies: eslint: '>=7.0.0' dependencies: - eslint: 8.56.0 + eslint: 8.57.0 dev: true - /eslint-plugin-prettier/5.1.3_roii55ymmf2x2gcdisgexmmcgu: + /eslint-plugin-prettier/5.1.3_udeglvt2rvqfn252kbszwwr7nu: resolution: {integrity: sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: @@ -3771,8 +3773,8 @@ packages: eslint-config-prettier: optional: true dependencies: - eslint: 8.56.0 - eslint-config-prettier: 9.1.0_eslint@8.56.0 + eslint: 8.57.0 + eslint-config-prettier: 9.1.0_eslint@8.57.0 prettier: 3.2.5 prettier-linter-helpers: 1.0.0 synckit: 0.8.8 @@ -3799,15 +3801,15 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /eslint/8.56.0: - resolution: {integrity: sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==} + /eslint/8.57.0: + resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} hasBin: true dependencies: - '@eslint-community/eslint-utils': 4.4.0_eslint@8.56.0 + '@eslint-community/eslint-utils': 4.4.0_eslint@8.57.0 '@eslint-community/regexpp': 4.10.0 '@eslint/eslintrc': 2.1.4 - '@eslint/js': 8.56.0 + '@eslint/js': 8.57.0 '@humanwhocodes/config-array': 0.11.14 '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 @@ -5749,10 +5751,6 @@ packages: hasBin: true dev: true - /natural-compare-lite/1.4.0: - resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} - dev: true - /natural-compare/1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true @@ -7420,6 +7418,15 @@ packages: engines: {node: '>=8'} dev: true + /ts-api-utils/1.3.0_typescript@5.4.2: + resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' + dependencies: + typescript: 5.4.2 + dev: true + /ts-interface-checker/0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} dev: true @@ -7456,10 +7463,6 @@ packages: yn: 3.1.1 dev: true - /tslib/1.14.1: - resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} - dev: true - /tslib/2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} dev: true @@ -7509,16 +7512,6 @@ packages: - ts-node dev: true - /tsutils/3.21.0_typescript@5.4.2: - resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} - engines: {node: '>= 6'} - peerDependencies: - typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' - dependencies: - tslib: 1.14.1 - typescript: 5.4.2 - dev: true - /type-check/0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -7766,8 +7759,8 @@ packages: engines: {node: '>= 0.8'} dev: true - /vite-node/1.3.1_@types+node@18.19.17: - resolution: {integrity: sha512-azbRrqRxlWTJEVbzInZCTchx0X69M/XPTCz4H+TLvlTcR/xH/3hkRqhOakT41fMJCMzXTu4UvegkZiEoJAWvng==} + /vite-node/1.4.0_@types+node@18.19.17: + resolution: {integrity: sha512-VZDAseqjrHgNd4Kh8icYHWzTKSCZMhia7GyHfhtzLW33fZlG9SwsB6CEhgyVOWkJfJ2pFLrp/Gj1FSfAiqH9Lw==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true dependencies: @@ -7823,7 +7816,7 @@ packages: fsevents: 2.3.3 dev: true - /vitest-environment-miniflare/2.14.2_vitest@1.3.1: + /vitest-environment-miniflare/2.14.2_vitest@1.4.0: resolution: {integrity: sha512-9bVoJ59m8RtW+KxXoQBTm+2ljZDTuN/yxth3VlIJV4oOpjLo7ambK+exWJmoU+2lcSf0WAC/610a3gJuAJJDTg==} engines: {node: '>=16.13'} peerDependencies: @@ -7834,21 +7827,21 @@ packages: '@miniflare/shared': 2.14.2 '@miniflare/shared-test-environment': 2.14.2 undici: 5.28.2 - vitest: 1.3.1_jz4daltgcxlsto6zys62wxwil4 + vitest: 1.4.0_jz4daltgcxlsto6zys62wxwil4 transitivePeerDependencies: - bufferutil - utf-8-validate dev: true - /vitest/1.3.1_jz4daltgcxlsto6zys62wxwil4: - resolution: {integrity: sha512-/1QJqXs8YbCrfv/GPQ05wAZf2eakUPLPa18vkJAKE7RXOKfVHqMZZ1WlTjiwl6Gcn65M5vpNUB6EFLnEdRdEXQ==} + /vitest/1.4.0_jz4daltgcxlsto6zys62wxwil4: + resolution: {integrity: sha512-gujzn0g7fmwf83/WzrDTnncZt2UiXP41mHuFYFrdwaLRVQ6JYQEiME2IfEjU3vcFL3VKa75XhI3lFgn+hfVsQw==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': 1.3.1 - '@vitest/ui': 1.3.1 + '@vitest/browser': 1.4.0 + '@vitest/ui': 1.4.0 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -7866,11 +7859,11 @@ packages: optional: true dependencies: '@types/node': 18.19.17 - '@vitest/expect': 1.3.1 - '@vitest/runner': 1.3.1 - '@vitest/snapshot': 1.3.1 - '@vitest/spy': 1.3.1 - '@vitest/utils': 1.3.1 + '@vitest/expect': 1.4.0 + '@vitest/runner': 1.4.0 + '@vitest/snapshot': 1.4.0 + '@vitest/spy': 1.4.0 + '@vitest/utils': 1.4.0 acorn-walk: 8.3.2 chai: 4.4.1 debug: 4.3.4 @@ -7885,7 +7878,7 @@ packages: tinybench: 2.6.0 tinypool: 0.8.2 vite: 5.1.3_@types+node@18.19.17 - vite-node: 1.3.1_@types+node@18.19.17 + vite-node: 1.4.0_@types+node@18.19.17 why-is-node-running: 2.2.2 transitivePeerDependencies: - less diff --git a/src/browser/setupWorker/glossary.ts b/src/browser/setupWorker/glossary.ts index 9cf152e06..4fb053108 100644 --- a/src/browser/setupWorker/glossary.ts +++ b/src/browser/setupWorker/glossary.ts @@ -52,7 +52,10 @@ export type ServiceWorkerIncomingResponse = Pick< */ export interface ServiceWorkerIncomingEventsMap { MOCKING_ENABLED: boolean - INTEGRITY_CHECK_RESPONSE: string + INTEGRITY_CHECK_RESPONSE: { + packageVersion: string + checksum: string + } KEEPALIVE_RESPONSE: never REQUEST: ServiceWorkerIncomingRequest RESPONSE: ServiceWorkerIncomingResponse diff --git a/src/browser/setupWorker/start/createRequestListener.ts b/src/browser/setupWorker/start/createRequestListener.ts index e530e69b0..627617e49 100644 --- a/src/browser/setupWorker/start/createRequestListener.ts +++ b/src/browser/setupWorker/start/createRequestListener.ts @@ -48,7 +48,7 @@ export const createRequestListener = ( context.emitter, { onPassthroughResponse() { - messageChannel.postMessage('NOT_FOUND') + messageChannel.postMessage('PASSTHROUGH') }, async onMockedResponse(response, { handler, parsedResult }) { // Clone the mocked response so its body could be read diff --git a/src/browser/setupWorker/start/createResponseListener.ts b/src/browser/setupWorker/start/createResponseListener.ts index 222635d27..1425c8587 100644 --- a/src/browser/setupWorker/start/createResponseListener.ts +++ b/src/browser/setupWorker/start/createResponseListener.ts @@ -21,6 +21,8 @@ export function createResponseListener(context: SetupWorkerInternalContext) { const request = context.requests.get(requestId)! context.requests.delete(requestId) + console.log('RESPONSE LISTENER', responseJson, context.requests) + /** * CORS requests with `mode: "no-cors"` result in "opaque" responses. * That kind of responses cannot be manipulated in JavaScript due diff --git a/src/browser/setupWorker/start/createStartHandler.ts b/src/browser/setupWorker/start/createStartHandler.ts index 9634ced38..dd9c35ebb 100644 --- a/src/browser/setupWorker/start/createStartHandler.ts +++ b/src/browser/setupWorker/start/createStartHandler.ts @@ -1,10 +1,9 @@ -import { until } from '@open-draft/until' import { devUtils } from '~/core/utils/internal/devUtils' import { getWorkerInstance } from './utils/getWorkerInstance' import { enableMocking } from './utils/enableMocking' import { SetupWorkerInternalContext, StartHandler } from '../glossary' import { createRequestListener } from './createRequestListener' -import { requestIntegrityCheck } from '../../utils/requestIntegrityCheck' +import { checkWorkerIntegrity } from '../../utils/checkWorkerIntegrity' import { createResponseListener } from './createResponseListener' import { validateWorkerScope } from './utils/validateWorkerScope' @@ -74,23 +73,14 @@ Please consider using a custom "serviceWorker.url" option to point to the actual window.clearInterval(context.keepAliveInterval) }) - // Check if the active Service Worker is the latest published one - const integrityCheckResult = await until(() => - requestIntegrityCheck(context, worker), - ) - - if (integrityCheckResult.error) { - devUtils.error(`\ -Detected outdated Service Worker: ${integrityCheckResult.error.message} - -The mocking is still enabled, but it's highly recommended that you update your Service Worker by running: - -$ npx msw init - -This is necessary to ensure that the Service Worker is in sync with the library to guarantee its stability. -If this message still persists after updating, please report an issue: https://github.com/open-draft/msw/issues\ - `) - } + // Check if the active Service Worker has been generated + // by the currently installed version of MSW. + await checkWorkerIntegrity(context).catch((error) => { + devUtils.error( + 'Error while checking the worker script integrity. Please report this on GitHub (https://github.com/mswjs/msw/issues), including the original error below.', + ) + console.error(error) + }) context.keepAliveInterval = window.setInterval( () => context.workerChannel.send('KEEPALIVE_REQUEST'), diff --git a/src/browser/setupWorker/start/utils/createMessageChannel.ts b/src/browser/setupWorker/start/utils/createMessageChannel.ts index 2a2cd0d5e..f0638a6cd 100644 --- a/src/browser/setupWorker/start/utils/createMessageChannel.ts +++ b/src/browser/setupWorker/start/utils/createMessageChannel.ts @@ -16,7 +16,7 @@ interface WorkerChannelEventsMap { data: StringifiedResponse, transfer?: [ReadableStream], ] - NOT_FOUND: [] + PASSTHROUGH: [] } export class WorkerChannel { diff --git a/src/browser/utils/checkWorkerIntegrity.ts b/src/browser/utils/checkWorkerIntegrity.ts new file mode 100644 index 000000000..6a7a0073f --- /dev/null +++ b/src/browser/utils/checkWorkerIntegrity.ts @@ -0,0 +1,34 @@ +import { devUtils } from '~/core/utils/internal/devUtils' +import type { SetupWorkerInternalContext } from '../setupWorker/glossary' + +/** + * Check whether the registered Service Worker has been + * generated by the installed version of the library. + * Prints a warning message if the worker scripts mismatch. + */ +export async function checkWorkerIntegrity( + context: SetupWorkerInternalContext, +): Promise { + // Request the integrity checksum from the registered worker. + context.workerChannel.send('INTEGRITY_CHECK_REQUEST') + + const { payload } = await context.events.once('INTEGRITY_CHECK_RESPONSE') + + // Compare the response from the Service Worker and the + // global variable set during the build. + + // The integrity is validated based on the worker script's checksum + // that's derived from its minified content during the build. + // The "SERVICE_WORKER_CHECKSUM" global variable is injected by the build. + if (payload.checksum !== SERVICE_WORKER_CHECKSUM) { + devUtils.warn( + `The currently registered Service Worker has been generated by a different version of MSW (${payload.packageVersion}) and may not be fully compatible with the installed version. + +It's recommended you update your worker script by running this command: + + \u2022 npx msw init + +You can also automate this process and make the worker script update automatically upon the library installations. Read more: https://mswjs.io/docs/cli/init.`, + ) + } +} diff --git a/src/browser/utils/requestIntegrityCheck.ts b/src/browser/utils/requestIntegrityCheck.ts deleted file mode 100644 index 67e1b4144..000000000 --- a/src/browser/utils/requestIntegrityCheck.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { SetupWorkerInternalContext } from '../setupWorker/glossary' - -export async function requestIntegrityCheck( - context: SetupWorkerInternalContext, - serviceWorker: ServiceWorker, -): Promise { - // Signal Service Worker to report back its integrity - context.workerChannel.send('INTEGRITY_CHECK_REQUEST') - - const { payload: actualChecksum } = await context.events.once( - 'INTEGRITY_CHECK_RESPONSE', - ) - - // Compare the response from the Service Worker and the - // global variable set during the build. - if (actualChecksum !== SERVICE_WORKER_CHECKSUM) { - throw new Error( - `Currently active Service Worker (${actualChecksum}) is behind the latest published one (${SERVICE_WORKER_CHECKSUM}).`, - ) - } - - return serviceWorker -} diff --git a/src/core/bypass.test.ts b/src/core/bypass.test.ts index 385234d10..bef858bcb 100644 --- a/src/core/bypass.test.ts +++ b/src/core/bypass.test.ts @@ -45,3 +45,25 @@ it('returns bypassed request given request instance', async () => { 'x-msw-intention': 'bypass', }) }) + +it('allows modifying the bypassed request instance', async () => { + const original = new Request('http://localhost/resource', { + method: 'POST', + body: 'hello world', + }) + const request = bypass(original, { + method: 'PUT', + headers: { 'x-modified-header': 'yes' }, + }) + + expect(request.method).toBe('PUT') + expect(Object.fromEntries(request.headers.entries())).toEqual({ + 'x-msw-intention': 'bypass', + 'x-modified-header': 'yes', + }) + expect(original.bodyUsed).toBe(false) + expect(request.bodyUsed).toBe(false) + + expect(await request.text()).toBe('hello world') + expect(original.bodyUsed).toBe(false) +}) diff --git a/src/core/bypass.ts b/src/core/bypass.ts index e467cf30c..26d3afb2f 100644 --- a/src/core/bypass.ts +++ b/src/core/bypass.ts @@ -15,7 +15,15 @@ export type BypassRequestInput = string | URL | Request * @see {@link https://mswjs.io/docs/api/bypass `bypass()` API reference} */ export function bypass(input: BypassRequestInput, init?: RequestInit): Request { - const request = input instanceof Request ? input : new Request(input, init) + // Always create a new Request instance. + // This way, the "init" modifications will propagate + // to the bypass request instance automatically. + const request = new Request( + // If given a Request instance, clone it not to exhaust + // the original request's body. + input instanceof Request ? input.clone() : input, + init, + ) invariant( !request.bodyUsed, diff --git a/src/core/utils/HttpResponse/decorators.ts b/src/core/utils/HttpResponse/decorators.ts index 7b1bb97cc..6af4a72bb 100644 --- a/src/core/utils/HttpResponse/decorators.ts +++ b/src/core/utils/HttpResponse/decorators.ts @@ -1,5 +1,6 @@ import statuses from '@bundled-es-modules/statuses' import type { HttpResponseInit } from '../../HttpResponse' +import { Headers as HeadersPolyfill } from 'headers-polyfill' const { message } = statuses @@ -40,10 +41,13 @@ export function decorateResponse( // Cookie forwarding is only relevant in the browser. if (typeof document !== 'undefined') { // Write the mocked response cookies to the document. - // Note that Fetch API Headers will concatenate multiple "Set-Cookie" - // headers into a single comma-separated string, just as it does - // with any other multi-value headers. - const responseCookies = init.headers.get('Set-Cookie')?.split(',') || [] + // Use `headers-polyfill` to get the Set-Cookie header value correctly. + // This is an alternative until TypeScript 5.2 + // and Node.js v20 become the minimum supported version + // and getSetCookie in Headers can be used directly. + const responseCookies = HeadersPolyfill.prototype.getSetCookie.call( + init.headers, + ) for (const cookieString of responseCookies) { // No need to parse the cookie headers because it's defined diff --git a/src/core/utils/handleRequest.ts b/src/core/utils/handleRequest.ts index f821410ba..cea1a1e09 100644 --- a/src/core/utils/handleRequest.ts +++ b/src/core/utils/handleRequest.ts @@ -51,7 +51,7 @@ export async function handleRequest( ): Promise { emitter.emit('request:start', { request, requestId }) - // Perform bypassed requests (i.e. issued via "ctx.fetch") as-is. + // Perform bypassed requests (i.e. wrapped in "bypass()") as-is. if (request.headers.get('x-msw-intention') === 'bypass') { emitter.emit('request:end', { request, requestId }) handleRequestOptions?.onPassthroughResponse?.(request) diff --git a/src/mockServiceWorker.js b/src/mockServiceWorker.js index bece48c8d..04132a385 100644 --- a/src/mockServiceWorker.js +++ b/src/mockServiceWorker.js @@ -2,12 +2,13 @@ /* tslint:disable */ /** - * Mock Service Worker (). + * Mock Service Worker. * @see https://github.com/mswjs/msw * - Please do NOT modify this file. * - Please do NOT serve this file on production. */ +const PACKAGE_VERSION = '' const INTEGRITY_CHECKSUM = '' const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') const activeClientIds = new Set() @@ -48,7 +49,10 @@ self.addEventListener('message', async function (event) { case 'INTEGRITY_CHECK_REQUEST': { sendToClient(client, { type: 'INTEGRITY_CHECK_RESPONSE', - payload: INTEGRITY_CHECKSUM, + payload: { + packageVersion: PACKAGE_VERSION, + checksum: INTEGRITY_CHECKSUM, + }, }) break } @@ -202,13 +206,6 @@ async function getResponse(event, client, requestId) { return passthrough() } - // Bypass requests with the explicit bypass header. - // Such requests can be issued by "ctx.fetch()". - const mswIntention = request.headers.get('x-msw-intention') - if (['bypass', 'passthrough'].includes(mswIntention)) { - return passthrough() - } - // Notify the client that a request has been intercepted. const requestBuffer = await request.arrayBuffer() const clientMessage = await sendToClient( @@ -240,7 +237,7 @@ async function getResponse(event, client, requestId) { return respondWithMock(clientMessage.data) } - case 'MOCK_NOT_FOUND': { + case 'PASSTHROUGH': { return passthrough() } } diff --git a/test/browser/msw-api/integrity-check.test.ts b/test/browser/msw-api/integrity-check.test.ts index dba8420db..86ea3b2b2 100644 --- a/test/browser/msw-api/integrity-check.test.ts +++ b/test/browser/msw-api/integrity-check.test.ts @@ -2,6 +2,7 @@ import * as fs from 'fs' import * as path from 'path' import { test, expect } from '../playwright.extend' import copyServiceWorker from '../../../config/copyServiceWorker' +import { version } from '../../../package.json' const { SERVICE_WORKER_SOURCE_PATH } = require('../../../config/constants') @@ -65,9 +66,15 @@ test('errors when activating the worker with an outdated integrity', async ({ await waitFor(() => { // Produces a meaningful error in the browser's console. - expect(consoleSpy.get('error')).toEqual( + expect(consoleSpy.get('warning')).toEqual( expect.arrayContaining([ - expect.stringContaining('[MSW] Detected outdated Service Worker'), + `[MSW] The currently registered Service Worker has been generated by a different version of MSW (${version}) and may not be fully compatible with the installed version. + +It's recommended you update your worker script by running this command: + + \u2022 npx msw init + +You can also automate this process and make the worker script update automatically upon the library installations. Read more: https://mswjs.io/docs/cli/init.`, ]), ) }) diff --git a/test/browser/msw-api/setup-worker/life-cycle-events/on.mocks.ts b/test/browser/msw-api/setup-worker/life-cycle-events/on.mocks.ts index 5ca89c165..bcc2cdbd0 100644 --- a/test/browser/msw-api/setup-worker/life-cycle-events/on.mocks.ts +++ b/test/browser/msw-api/setup-worker/life-cycle-events/on.mocks.ts @@ -1,4 +1,10 @@ -import { HttpResponse, http, LifeCycleEventsMap } from 'msw' +import { + HttpResponse, + http, + LifeCycleEventsMap, + passthrough, + bypass, +} from 'msw' import { setupWorker } from 'msw/browser' const worker = setupWorker( @@ -8,6 +14,12 @@ const worker = setupWorker( http.post('*/no-response', () => { return }), + http.get('*/passthrough', () => { + return passthrough() + }), + http.get('*/bypass', async ({ request }) => { + return fetch(bypass(request, { method: 'POST' })) + }), http.get('*/unhandled-exception', () => { throw new Error('Unhandled resolver error') }), diff --git a/test/browser/msw-api/setup-worker/life-cycle-events/on.test.ts b/test/browser/msw-api/setup-worker/life-cycle-events/on.test.ts index a4c420c64..d01368db8 100644 --- a/test/browser/msw-api/setup-worker/life-cycle-events/on.test.ts +++ b/test/browser/msw-api/setup-worker/life-cycle-events/on.test.ts @@ -1,4 +1,4 @@ -import type { SetupWorker } from 'msw/lib/browser' +import type { SetupWorker } from 'msw/browser' import { HttpServer } from '@open-draft/test-server/http' import type { ConsoleMessages } from 'page-with' import { test, expect } from '../../../playwright.extend' @@ -11,7 +11,20 @@ declare namespace window { const ON_EXAMPLE = require.resolve('./on.mocks.ts') -let server: HttpServer +const server = new HttpServer((app) => { + app.post('/no-response', (_req, res) => { + res.send('original-response') + }) + app.get('/unknown-route', (_req, res) => { + res.send('majestic-unknown') + }) + app.get('/passthrough', (_req, res) => { + res.send('passthrough-response') + }) + app.post('/bypass', (_req, res) => { + res.send('bypassed-response') + }) +}) export function getRequestId(messages: ConsoleMessages) { const requestStartMessage = messages.get('warning')?.find((message) => { @@ -20,15 +33,12 @@ export function getRequestId(messages: ConsoleMessages) { return requestStartMessage?.split(' ')?.[3] } -test.beforeEach(async ({ createServer }) => { - server = await createServer((app) => { - app.post('/no-response', (req, res) => { - res.send('original-response') - }) - app.get('/unknown-route', (req, res) => { - res.send('majestic-unknown') - }) - }) +test.beforeAll(async () => { + await server.listen() +}) + +test.afterAll(async () => { + await server.close() }) test('emits events for a handled request and mocked response', async ({ @@ -111,6 +121,74 @@ test('emits events for an unhandled request', async ({ ]) }) +test('emits events for a passthrough request', async ({ + loadExample, + spyOnConsole, + fetch, + waitFor, +}) => { + const consoleSpy = spyOnConsole() + await loadExample(ON_EXAMPLE) + + // Explicit "passthrough()" request must go through the + // same request processing pipeline to contain both + // "request" and "response" in the life-cycle event listener. + const url = server.http.url('/passthrough') + await fetch(url) + const requestId = getRequestId(consoleSpy) + + await waitFor(() => { + expect(consoleSpy.get('warning')).toEqual([ + `[request:start] GET ${url} ${requestId}`, + `[request:end] GET ${url} ${requestId}`, + `[response:bypass] ${url} passthrough-response GET ${url} ${requestId}`, + ]) + }) +}) + +test('emits events for a bypassed request', async ({ + loadExample, + spyOnConsole, + fetch, + waitFor, + page, +}) => { + const consoleSpy = spyOnConsole() + await loadExample(ON_EXAMPLE) + + const pageErrors: Array = [] + page.on('pageerror', (error) => pageErrors.push(error)) + + const url = server.http.url('/bypass') + await fetch(url) + + await waitFor(() => { + // First, must print the events for the original (mocked) request. + expect(consoleSpy.get('warning')).toEqual( + expect.arrayContaining([ + expect.stringContaining(`[request:start] GET ${url}`), + expect.stringContaining(`[request:end] GET ${url}`), + expect.stringContaining( + `[response:mocked] ${url} bypassed-response GET ${url}`, + ), + ]), + ) + + // Then, must also print events for the bypassed request. + expect(consoleSpy.get('warning')).toEqual( + expect.arrayContaining([ + expect.stringContaining(`[request:start] POST ${url}`), + expect.stringContaining(`[request:end] POST ${url}`), + expect.stringContaining( + `[response:bypass] ${url} bypassed-response POST ${url}`, + ), + ]), + ) + + expect(pageErrors).toEqual([]) + }) +}) + test('emits unhandled exceptions in the request handler', async ({ loadExample, spyOnConsole, diff --git a/test/browser/rest-api/cookies.mocks.ts b/test/browser/rest-api/cookies.mocks.ts index 1c6a51f3f..7d5aa6746 100644 --- a/test/browser/rest-api/cookies.mocks.ts +++ b/test/browser/rest-api/cookies.mocks.ts @@ -23,6 +23,7 @@ const worker = setupWorker( headers: [ ['Set-Cookie', 'firstCookie=yes'], ['Set-Cookie', 'secondCookie=no; Max-Age=1000'], + ['Set-Cookie', 'thirdCookie=1,2,3'], ], }, ) diff --git a/test/browser/rest-api/cookies.test.ts b/test/browser/rest-api/cookies.test.ts index af3b95f47..d571c83e5 100644 --- a/test/browser/rest-api/cookies.test.ts +++ b/test/browser/rest-api/cookies.test.ts @@ -48,5 +48,6 @@ test('allows setting multiple response cookies', async ({ expect(allCookies).toEqual({ firstCookie: 'yes', secondCookie: 'no', + thirdCookie: '1,2,3', }) })