diff --git a/package-lock.json b/package-lock.json index acb55058..185b0e53 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,7 @@ "@diplodoc/openapi-extension": "^2.6.0", "@diplodoc/prettier-config": "^2.0.0", "@diplodoc/search-extension": "^1.1.5", - "@diplodoc/transform": "^4.38.2", + "@diplodoc/transform": "^4.40.1", "@diplodoc/tsconfig": "^1.0.2", "@gravity-ui/page-constructor": "^5.29.1", "@octokit/core": "4.2.4", @@ -43,7 +43,7 @@ "@types/shelljs": "0.8.15", "@types/tar-stream": "^2.2.2", "@types/yargs": "17.0.24", - "@vitest/coverage-v8": "^1.2.1", + "@vitest/coverage-v8": "^2.1.8", "ajv": "^8.11.0", "async": "^3.2.4", "axios": "^1.6.7", @@ -68,7 +68,7 @@ "ts-dedent": "^2.2.0", "typescript": "^5.4.5", "vite-tsconfig-paths": "^4.2.3", - "vitest": "^1.1.3", + "vitest": "^2.1.8", "vitest-when": "^0.5.0", "walk-sync": "^3.0.0" }, @@ -1673,15 +1673,27 @@ } }, "node_modules/@diplodoc/cut-extension": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@diplodoc/cut-extension/-/cut-extension-0.3.2.tgz", - "integrity": "sha512-55AgVEIiy3GHorZRht3dm1AcFWdgCMAn8yGV/8qp8sPQ4LssPPuCrVzCVFMn7o8d3KZbEDt5dDj6kI1KtVaWQg==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@diplodoc/cut-extension/-/cut-extension-0.5.0.tgz", + "integrity": "sha512-UvSZmjTVL9DSvKLYzde3Y9RJGjM44s1iTP1iSytuInXDqLIeyd4wenNsr9LHqmAl6EB3V5/OMR+B3X8FgXNypg==", "license": "MIT", + "dependencies": { + "@diplodoc/directive": "^0.3.0" + }, "peerDependencies": { "@diplodoc/utils": "1.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/@diplodoc/directive": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@diplodoc/directive/-/directive-0.3.0.tgz", + "integrity": "sha512-1UD7UHthRqO0rju/XNAaKQIxSy/pa1giEMlARBZ7U4ZPi1QeTw+QNyXy1TwvnBb5hc6jAChTjfOxDwpct1AEdg==", + "license": "MIT", + "dependencies": { + "markdown-it-directive": "2.0.4" + } + }, "node_modules/@diplodoc/eslint-config": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@diplodoc/eslint-config/-/eslint-config-2.0.0.tgz", @@ -1700,6 +1712,18 @@ "eslint-plugin-security": "1.7.1" } }, + "node_modules/@diplodoc/file-extension": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@diplodoc/file-extension/-/file-extension-0.2.1.tgz", + "integrity": "sha512-4m9ZcQwmeHw0t2t5vv5GGxKfUifOdpf4Idb9a/Rfkxl6pqGnk0rnZ0xwXrruQEwinkQIBL4TCl2YN3SKGaBrjQ==", + "license": "MIT", + "dependencies": { + "@diplodoc/directive": "^0.3.0" + }, + "peerDependencies": { + "markdown-it": "^13.0.0" + } + }, "node_modules/@diplodoc/latex-extension": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@diplodoc/latex-extension/-/latex-extension-1.3.2.tgz", @@ -2313,12 +2337,13 @@ } }, "node_modules/@diplodoc/transform": { - "version": "4.38.2", - "resolved": "https://registry.npmjs.org/@diplodoc/transform/-/transform-4.38.2.tgz", - "integrity": "sha512-CsKh9JWx37ldmsiqZmj+5pujHV3RFt9sViRutZ4rBRDODjpmLmLKruTj0uCdFg9gOZa8BiD22rU+4iayvd0k4w==", + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@diplodoc/transform/-/transform-4.40.1.tgz", + "integrity": "sha512-1s/iG/K8JfFR9x+BAu5wn1GeR/MWgjlOsFBh5Fjp7KqA/+gX+i3/VNUmiCY146A8VzX99/oFbiYSGcubytWumQ==", "license": "MIT", "dependencies": { - "@diplodoc/cut-extension": "^0.3.0", + "@diplodoc/cut-extension": "^0.5.0", + "@diplodoc/file-extension": "^0.2.0", "@diplodoc/tabs-extension": "^3.5.0", "chalk": "^4.1.2", "cheerio": "^1.0.0", @@ -4852,6 +4877,13 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "license": "MIT" }, + "node_modules/@types/linkify-it": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.5.tgz", + "integrity": "sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==", + "license": "MIT", + "peer": true + }, "node_modules/@types/lodash": { "version": "4.14.195", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.195.tgz", @@ -4859,6 +4891,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/markdown-it": { + "version": "13.0.9", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-13.0.9.tgz", + "integrity": "sha512-1XPwR0+MgXLWfTn9gCsZ55AHOKW1WN+P9vr0PaQh5aerR9LLQXUbjfEAFhjmEmyoYFWAyuN2Mqkn40MZ4ukjBw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/linkify-it": "^3", + "@types/mdurl": "^1" + } + }, "node_modules/@types/mdast": { "version": "3.0.15", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", @@ -4868,6 +4911,13 @@ "@types/unist": "^2" } }, + "node_modules/@types/mdurl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.5.tgz", + "integrity": "sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==", + "license": "MIT", + "peer": true + }, "node_modules/@types/mime-types": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz", @@ -5274,131 +5324,146 @@ "license": "ISC" }, "node_modules/@vitest/coverage-v8": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.6.0.tgz", - "integrity": "sha512-KvapcbMY/8GYIG0rlwwOKCVNRc0OL20rrhFkg/CHNzncV03TE2XWvO5w9uZYoxNiMEBacAJt3unSOiZ7svePew==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.8.tgz", + "integrity": "sha512-2Y7BPlKH18mAZYAW1tYByudlCYrQyl5RGvnnDYJKW5tCiO5qg3KSAy3XAxcxKz900a0ZXxWtKrMuZLe3lKBpJw==", "dev": true, "license": "MIT", "dependencies": { - "@ampproject/remapping": "^2.2.1", + "@ampproject/remapping": "^2.3.0", "@bcoe/v8-coverage": "^0.2.3", - "debug": "^4.3.4", + "debug": "^4.3.7", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", - "istanbul-lib-source-maps": "^5.0.4", - "istanbul-reports": "^3.1.6", - "magic-string": "^0.30.5", - "magicast": "^0.3.3", - "picocolors": "^1.0.0", - "std-env": "^3.5.0", - "strip-literal": "^2.0.0", - "test-exclude": "^6.0.0" + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magic-string": "^0.30.12", + "magicast": "^0.3.5", + "std-env": "^3.8.0", + "test-exclude": "^7.0.1", + "tinyrainbow": "^1.2.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "1.6.0" + "@vitest/browser": "2.1.8", + "vitest": "2.1.8" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } } }, "node_modules/@vitest/expect": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.0.tgz", - "integrity": "sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.8.tgz", + "integrity": "sha512-8ytZ/fFHq2g4PJVAtDX57mayemKgDR6X3Oa2Foro+EygiOJHUXhCqBAAKQYYajZpFoIfvBCF1j6R6IYRSIUFuw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "1.6.0", - "@vitest/utils": "1.6.0", - "chai": "^4.3.10" + "@vitest/spy": "2.1.8", + "@vitest/utils": "2.1.8", + "chai": "^5.1.2", + "tinyrainbow": "^1.2.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/runner": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.0.tgz", - "integrity": "sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==", + "node_modules/@vitest/mocker": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.8.tgz", + "integrity": "sha512-7guJ/47I6uqfttp33mgo6ga5Gr1VnL58rcqYKyShoRK9ebu8T5Rs6HN3s1NABiBeVTdWNrwUMcHH54uXZBN4zA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "1.6.0", - "p-limit": "^5.0.0", - "pathe": "^1.1.1" + "@vitest/spy": "2.1.8", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12" }, "funding": { "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } } }, - "node_modules/@vitest/runner/node_modules/p-limit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", - "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "node_modules/@vitest/pretty-format": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.8.tgz", + "integrity": "sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ==", "dev": true, "license": "MIT", "dependencies": { - "yocto-queue": "^1.0.0" - }, - "engines": { - "node": ">=18" + "tinyrainbow": "^1.2.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/runner/node_modules/yocto-queue": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz", - "integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==", + "node_modules/@vitest/runner": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.8.tgz", + "integrity": "sha512-17ub8vQstRnRlIU5k50bG+QOMLHRhYPAna5tw8tYbj+jzjcspnwnwtPtiOlkuKC4+ixDPTuLZiqiWWQ2PSXHVg==", "dev": true, "license": "MIT", - "engines": { - "node": ">=12.20" + "dependencies": { + "@vitest/utils": "2.1.8", + "pathe": "^1.1.2" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/snapshot": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.0.tgz", - "integrity": "sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.8.tgz", + "integrity": "sha512-20T7xRFbmnkfcmgVEz+z3AU/3b0cEzZOt/zmnvZEctg64/QZbSDJEVm9fLnnlSi74KibmRsO9/Qabi+t0vCRPg==", "dev": true, "license": "MIT", "dependencies": { - "magic-string": "^0.30.5", - "pathe": "^1.1.1", - "pretty-format": "^29.7.0" + "@vitest/pretty-format": "2.1.8", + "magic-string": "^0.30.12", + "pathe": "^1.1.2" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/spy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.0.tgz", - "integrity": "sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.8.tgz", + "integrity": "sha512-5swjf2q95gXeYPevtW0BLk6H8+bPlMb4Vw/9Em4hFxDcaOxS+e0LOX4yqNxoHzMR2akEB2xfpnWUzkZokmgWDg==", "dev": true, "license": "MIT", "dependencies": { - "tinyspy": "^2.2.0" + "tinyspy": "^3.0.2" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.0.tgz", - "integrity": "sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.8.tgz", + "integrity": "sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA==", "dev": true, "license": "MIT", "dependencies": { - "diff-sequences": "^29.6.3", - "estree-walker": "^3.0.3", - "loupe": "^2.3.7", - "pretty-format": "^29.7.0" + "@vitest/pretty-format": "2.1.8", + "loupe": "^3.1.2", + "tinyrainbow": "^1.2.0" }, "funding": { "url": "https://opencollective.com/vitest" @@ -5425,19 +5490,6 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -5719,13 +5771,13 @@ } }, "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, "license": "MIT", "engines": { - "node": "*" + "node": ">=12" } }, "node_modules/ast-types-flow": { @@ -6041,22 +6093,20 @@ "license": "CC-BY-4.0" }, "node_modules/chai": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", - "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz", + "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==", "dev": true, "license": "MIT", "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.3", - "deep-eql": "^4.1.3", - "get-func-name": "^2.0.2", - "loupe": "^2.3.6", - "pathval": "^1.1.1", - "type-detect": "^4.1.0" + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" }, "engines": { - "node": ">=4" + "node": ">=12" } }, "node_modules/chalk": { @@ -6086,16 +6136,13 @@ } }, "node_modules/check-error": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", - "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", "dev": true, "license": "MIT", - "dependencies": { - "get-func-name": "^2.0.2" - }, "engines": { - "node": "*" + "node": ">= 16" } }, "node_modules/cheerio": { @@ -6253,13 +6300,6 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "license": "MIT" }, - "node_modules/confbox": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", - "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", - "dev": true, - "license": "MIT" - }, "node_modules/consolidated-events": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/consolidated-events/-/consolidated-events-2.0.2.tgz", @@ -7113,14 +7153,11 @@ } }, "node_modules/deep-eql": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", - "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", "dev": true, "license": "MIT", - "dependencies": { - "type-detect": "^4.0.0" - }, "engines": { "node": ">=6" } @@ -7224,16 +7261,6 @@ "node": ">=0.3.1" } }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -7556,6 +7583,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", + "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", + "dev": true, + "license": "MIT" + }, "node_modules/es-object-atoms": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", @@ -8343,6 +8377,16 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/expect-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.1.0.tgz", + "integrity": "sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -8689,16 +8733,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/get-intrinsic": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", @@ -10247,23 +10281,6 @@ "dev": true, "license": "MIT" }, - "node_modules/local-pkg": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz", - "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mlly": "^1.7.3", - "pkg-types": "^1.2.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -10407,14 +10424,11 @@ } }, "node_modules/loupe": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", - "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz", + "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==", "dev": true, - "license": "MIT", - "dependencies": { - "get-func-name": "^2.0.1" - } + "license": "MIT" }, "node_modules/lower-case": { "version": "2.0.2", @@ -10554,6 +10568,16 @@ "integrity": "sha512-3OuqoRUlSxJiuQYu0cWTLHNhhq2xtoSFqsZK8plANg91+RJQU1ziQ6lA2LzmFAEes18uPBsHZpcX6We5l76Nzg==", "license": "MIT" }, + "node_modules/markdown-it-directive": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/markdown-it-directive/-/markdown-it-directive-2.0.4.tgz", + "integrity": "sha512-XtGbBcP0aoRgKiDIIsxvgSXZz3ptI5AOydLAfF+n/LRmdy4ROdHntboJMEg8Fd0B+SMtOynCpiDFDKboatPbMw==", + "license": "MIT", + "peerDependencies": { + "@types/markdown-it": "^12.0.0 || ^13.0.0", + "markdown-it": "^12.0.0 || ^13.0.0" + } + }, "node_modules/markdown-it-meta": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/markdown-it-meta/-/markdown-it-meta-0.0.1.tgz", @@ -11387,19 +11411,6 @@ "node": ">=16 || 14 >=14.17" } }, - "node_modules/mlly": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.3.tgz", - "integrity": "sha512-xUsx5n/mN0uQf4V548PKQ+YShA4/IW0KI1dZhrNrPCLG+xizETbHTkOa1f8/xut9JRPp8kQuMnz0oqwkTiLo/A==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.14.0", - "pathe": "^1.1.2", - "pkg-types": "^1.2.1", - "ufo": "^1.5.4" - } - }, "node_modules/monaco-editor": { "version": "0.38.0", "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.38.0.tgz", @@ -11926,13 +11937,13 @@ "license": "MIT" }, "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", "dev": true, "license": "MIT", "engines": { - "node": "*" + "node": ">= 14.16" } }, "node_modules/picocolors": { @@ -11966,18 +11977,6 @@ "node": ">=0.10" } }, - "node_modules/pkg-types": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.2.1.tgz", - "integrity": "sha512-sQoqa8alT3nHjGuTjuKgOnvjo4cljkufdtLMnO2LBP/wRwuDlo1tkaEdMxCRhyGRPacv/ztlZgDPm2b7FAmEvw==", - "dev": true, - "license": "MIT", - "dependencies": { - "confbox": "^0.1.8", - "mlly": "^1.7.2", - "pathe": "^1.1.2" - } - }, "node_modules/possible-typed-array-names": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", @@ -13750,26 +13749,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strip-literal": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.0.tgz", - "integrity": "sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==", - "dev": true, - "license": "MIT", - "dependencies": { - "js-tokens": "^9.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/strip-literal/node_modules/js-tokens": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.0.tgz", - "integrity": "sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==", - "dev": true, - "license": "MIT" - }, "node_modules/strnum": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", @@ -14159,64 +14138,18 @@ } }, "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", "dev": true, "license": "ISC", "dependencies": { "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/test-exclude/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "glob": "^10.4.1", + "minimatch": "^9.0.4" }, "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/test-exclude/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" + "node": ">=18" } }, "node_modules/text-decoder": { @@ -14282,10 +14215,27 @@ "dev": true, "license": "MIT" }, + "node_modules/tinyexec": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.1.tgz", + "integrity": "sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==", + "dev": true, + "license": "MIT" + }, "node_modules/tinypool": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", - "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz", + "integrity": "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", "dev": true, "license": "MIT", "engines": { @@ -14293,9 +14243,9 @@ } }, "node_modules/tinyspy": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", - "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", "dev": true, "license": "MIT", "engines": { @@ -14431,16 +14381,6 @@ "node": ">= 0.8.0" } }, - "node_modules/type-detect": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", - "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/type-fest": { "version": "0.21.3", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", @@ -14556,13 +14496,6 @@ "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", "license": "MIT" }, - "node_modules/ufo": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", - "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==", - "dev": true, - "license": "MIT" - }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -14812,16 +14745,16 @@ } }, "node_modules/vite-node": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.0.tgz", - "integrity": "sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.8.tgz", + "integrity": "sha512-uPAwSr57kYjAUux+8E2j0q0Fxpn8M9VoyfGiRI8Kfktz9NcYMCenwY5RnZxnF1WTu3TGiYipirIzacLL3VVGFg==", "dev": true, "license": "MIT", "dependencies": { "cac": "^6.7.14", - "debug": "^4.3.4", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", + "debug": "^4.3.7", + "es-module-lexer": "^1.5.4", + "pathe": "^1.1.2", "vite": "^5.0.0" }, "bin": { @@ -15285,32 +15218,32 @@ } }, "node_modules/vitest": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.0.tgz", - "integrity": "sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/expect": "1.6.0", - "@vitest/runner": "1.6.0", - "@vitest/snapshot": "1.6.0", - "@vitest/spy": "1.6.0", - "@vitest/utils": "1.6.0", - "acorn-walk": "^8.3.2", - "chai": "^4.3.10", - "debug": "^4.3.4", - "execa": "^8.0.1", - "local-pkg": "^0.5.0", - "magic-string": "^0.30.5", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", - "std-env": "^3.5.0", - "strip-literal": "^2.0.0", - "tinybench": "^2.5.1", - "tinypool": "^0.8.3", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.8.tgz", + "integrity": "sha512-1vBKTZskHw/aosXqQUlVWWlGUxSJR8YtiyZDJAFeW2kPAeX6S3Sool0mjspO+kXLuxVWlEDDowBAeqeAQefqLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "2.1.8", + "@vitest/mocker": "2.1.8", + "@vitest/pretty-format": "^2.1.8", + "@vitest/runner": "2.1.8", + "@vitest/snapshot": "2.1.8", + "@vitest/spy": "2.1.8", + "@vitest/utils": "2.1.8", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", + "pathe": "^1.1.2", + "std-env": "^3.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", + "tinyrainbow": "^1.2.0", "vite": "^5.0.0", - "vite-node": "1.6.0", - "why-is-node-running": "^2.2.2" + "vite-node": "2.1.8", + "why-is-node-running": "^2.3.0" }, "bin": { "vitest": "vitest.mjs" @@ -15324,8 +15257,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "1.6.0", - "@vitest/ui": "1.6.0", + "@vitest/browser": "2.1.8", + "@vitest/ui": "2.1.8", "happy-dom": "*", "jsdom": "*" }, @@ -15369,150 +15302,6 @@ } } }, - "node_modules/vitest/node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/vitest/node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest/node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=16.17.0" - } - }, - "node_modules/vitest/node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest/node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest/node_modules/npm-run-path": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest/node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/vitest/node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/void-elements": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", diff --git a/package.json b/package.json index 1f4b0333..f6352cf2 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,7 @@ "@diplodoc/openapi-extension": "^2.6.0", "@diplodoc/prettier-config": "^2.0.0", "@diplodoc/search-extension": "^1.1.5", - "@diplodoc/transform": "^4.38.2", + "@diplodoc/transform": "^4.40.1", "@diplodoc/tsconfig": "^1.0.2", "@gravity-ui/page-constructor": "^5.29.1", "@octokit/core": "4.2.4", @@ -86,7 +86,7 @@ "@types/shelljs": "0.8.15", "@types/tar-stream": "^2.2.2", "@types/yargs": "17.0.24", - "@vitest/coverage-v8": "^1.2.1", + "@vitest/coverage-v8": "^2.1.8", "ajv": "^8.11.0", "async": "^3.2.4", "axios": "^1.6.7", @@ -111,7 +111,7 @@ "ts-dedent": "^2.2.0", "typescript": "^5.4.5", "vite-tsconfig-paths": "^4.2.3", - "vitest": "^1.1.3", + "vitest": "^2.1.8", "vitest-when": "^0.5.0", "walk-sync": "^3.0.0" }, diff --git a/src/commands/build/__tests__/index.ts b/src/commands/build/__tests__/index.ts index de093631..c9ac1baf 100644 --- a/src/commands/build/__tests__/index.ts +++ b/src/commands/build/__tests__/index.ts @@ -1,10 +1,11 @@ -import type {Run} from '../run'; import type {BuildConfig, BuildRawConfig} from '..'; +import type {Mock, MockInstance} from 'vitest'; import {join} from 'node:path'; -import {Mock, describe, expect, it, vi} from 'vitest'; +import {describe, expect, it, vi} from 'vitest'; import {when} from 'vitest-when'; import {Build} from '..'; +import {Run} from '../run'; import {handler as originalHandler} from '../handler'; import {withConfigUtils} from '~/config'; @@ -14,6 +15,7 @@ export const handler = originalHandler as Mock; var resolveConfig: Mock; vi.mock('shelljs'); +vi.mock('../legacy-config'); vi.mock('../handler'); vi.mock('../run', async (importOriginal) => { return { @@ -32,8 +34,58 @@ vi.mock('~/config', async (importOriginal) => { }; }); +const Mocked = Symbol('Mocked'); + +export type RunSpy = Run & { + glob: MockInstance; + copy: MockInstance; + read: MockInstance; + write: MockInstance; + [Mocked]: boolean; +}; + +export function setupRun(config: DeepPartial, run?: Run): RunSpy { + run = + run || + new Run({ + input: '/dev/null/input', + output: '/dev/null/output', + ...config, + } as BuildConfig); + + const stringify = (arg: unknown) => { + if (typeof arg === 'object' && arg) { + return JSON.stringify(arg); + } + + return String(arg); + }; + + const impl = + (method: string) => + (...args: unknown[]) => { + throw new Error( + `Method ${method} with args\n${args.map(stringify).join('\n')} not implemented.`, + ); + }; + + for (const method of ['glob', 'copy', 'read', 'write'] as string[]) { + // @ts-ignore + vi.spyOn(run, method).mockImplementation(impl(method)); + } + + for (const method of ['proc', 'info', 'warn', 'error'] as string[]) { + // @ts-ignore + vi.spyOn(run.logger, method).mockImplementation(() => {}); + } + + (run as RunSpy)[Mocked] = true; + + return run as RunSpy; +} + type BuildState = { - globs?: Hash; + globs?: Hash; files?: Hash; }; export function setupBuild(state: BuildState = {}): Build & {run: Run} { @@ -43,21 +95,17 @@ export function setupBuild(state: BuildState = {}): Build & {run: Run} { build.hooks.BeforeAnyRun.tap('Tests', (run) => { (build as Build & {run: Run}).run = run; - // @ts-ignore - run.glob = vi.fn(() => []); - run.copy = vi.fn(); - run.write = vi.fn(); - run.fs.writeFile = vi.fn(); - // @ts-ignore - run.fs.readFile = vi.fn(); - // @ts-ignore - run.logger.proc = vi.fn(); - // @ts-ignore - run.logger.info = vi.fn(); - // @ts-ignore - run.logger.warn = vi.fn(); - // @ts-ignore - run.logger.error = vi.fn(); + if (!(run as RunSpy)[Mocked]) { + setupRun({}, run); + } + + when(run.copy).calledWith(expect.anything(), expect.anything()).thenResolve(); + when(run.copy) + .calledWith(expect.anything(), expect.anything(), expect.anything()) + .thenResolve(); + when(run.write).calledWith(expect.anything(), expect.anything()).thenResolve(); + when(run.glob).calledWith('**/toc.yaml', expect.anything()).thenResolve([]); + when(run.glob).calledWith('**/presets.yaml', expect.anything()).thenResolve([]); if (state.globs) { for (const [pattern, files] of Object.entries(state.globs)) { @@ -67,9 +115,7 @@ export function setupBuild(state: BuildState = {}): Build & {run: Run} { if (state.files) { for (const [file, content] of Object.entries(state.files)) { - when(run.fs.readFile) - .calledWith(join(run.input, file), expect.anything()) - .thenResolve(content); + when(run.read).calledWith(join(run.input, file)).thenResolve(content); } } }); diff --git a/src/commands/build/config.ts b/src/commands/build/config.ts index 804bf9e5..690b28c0 100644 --- a/src/commands/build/config.ts +++ b/src/commands/build/config.ts @@ -109,9 +109,9 @@ const staticContent = option({ }); const ignoreStage = option({ - flags: '--ignore-stage ', - defaultInfo: Stage.SKIP, - desc: 'Ignore tocs with stage.', + flags: '--ignore-stage ', + defaultInfo: [Stage.SKIP], + desc: 'Ignore tocs with selected stages.', }); const addSystemMeta = option({ diff --git a/src/commands/build/core/meta/index.ts b/src/commands/build/core/meta/index.ts new file mode 100644 index 00000000..a38d53a4 --- /dev/null +++ b/src/commands/build/core/meta/index.ts @@ -0,0 +1 @@ +export {addSourcePath} from './utils'; diff --git a/src/commands/build/core/meta/utils.ts b/src/commands/build/core/meta/utils.ts new file mode 100644 index 00000000..b0f19dc6 --- /dev/null +++ b/src/commands/build/core/meta/utils.ts @@ -0,0 +1,17 @@ +import {composeFrontMatter, extractFrontMatter} from '@diplodoc/transform/lib/frontmatter'; + +export function addSourcePath(fileContent: string, sourcePath: string) { + const [frontMatter, strippedContent] = extractFrontMatter(fileContent, sourcePath); + + if (frontMatter.sourcePath) { + return fileContent; + } + + return composeFrontMatter( + { + ...frontMatter, + sourcePath, + }, + strippedContent, + ); +} diff --git a/src/commands/build/core/vars/VarsService.ts b/src/commands/build/core/vars/VarsService.ts index 4fee897d..9678d7ee 100644 --- a/src/commands/build/core/vars/VarsService.ts +++ b/src/commands/build/core/vars/VarsService.ts @@ -5,12 +5,13 @@ import {merge} from 'lodash'; import {dump, load} from 'js-yaml'; import {Run} from '~/commands/build'; -import {freeze, own} from '~/utils'; +import {freeze, normalizePath, own} from '~/utils'; import {AsyncParallelHook, AsyncSeriesWaterfallHook} from 'tapable'; export type VarsServiceConfig = { varsPreset: string; vars: Hash; + ignore: string[]; }; type VarsServiceHooks = { @@ -30,9 +31,11 @@ type VarsServiceHooks = { export class VarsService { hooks: VarsServiceHooks; - private run: Run; + get entries() { + return [...Object.entries(this.cache)]; + } - private fs: Run['fs']; + private run: Run; private logger: Run['logger']; @@ -42,7 +45,6 @@ export class VarsService { constructor(run: Run) { this.run = run; - this.fs = run.fs; this.logger = run.logger; this.config = run.config; this.hooks = { @@ -51,7 +53,20 @@ export class VarsService { }; } + async init() { + const presets = await this.run.glob('**/presets.yaml', { + cwd: this.run.input, + ignore: this.config.ignore, + }); + + for (const preset of presets) { + await this.load(preset); + } + } + async load(path: RelativePath) { + path = normalizePath(path); + const varsPreset = this.config.varsPreset || 'default'; const file = join(dirname(path), 'presets.yaml'); @@ -59,7 +74,7 @@ export class VarsService { return this.cache[file]; } - this.logger.proc(path); + this.logger.proc(file); const scopes = []; @@ -69,7 +84,7 @@ export class VarsService { try { const presets = await this.hooks.PresetsLoaded.promise( - load(await this.fs.readFile(join(this.run.input, file), 'utf8')) as Presets, + load(await this.run.read(join(this.run.input, file))) as Presets, file, ); @@ -98,8 +113,4 @@ export class VarsService { lineWidth: 120, }); } - - entries() { - return Object.entries(this.cache); - } } diff --git a/src/commands/build/core/vars/index.spec.ts b/src/commands/build/core/vars/index.spec.ts index c12a68c2..d36d3c0c 100644 --- a/src/commands/build/core/vars/index.spec.ts +++ b/src/commands/build/core/vars/index.spec.ts @@ -1,4 +1,3 @@ -import type {Run} from '~/commands/build'; import type {VarsServiceConfig} from './VarsService'; import {join} from 'node:path'; @@ -7,6 +6,8 @@ import {when} from 'vitest-when'; import {dedent} from 'ts-dedent'; import {YAMLException} from 'js-yaml'; +import {setupRun} from '~/commands/build/__tests__'; + import {VarsService} from './VarsService'; const ENOENT = Object.assign(new Error('ENOENT: no such file or directory'), { @@ -15,38 +16,23 @@ const ENOENT = Object.assign(new Error('ENOENT: no such file or directory'), { type Options = Partial; -function prepare(content: string | Hash | Error, options: Options = {}) { - const input = '/dev/null/input' as AbsolutePath; - const output = '/dev/null/output' as AbsolutePath; - const run = { - input, - output, - config: { - varsPreset: options.varsPreset, - vars: options.vars || {}, - }, - logger: { - proc: vi.fn(), - }, - fs: { - readFile: vi.fn(), - }, - } as unknown as Run; +function prepare(content: string | Error | Hash, options: Options = {}) { + const run = setupRun({ + varsPreset: options.varsPreset, + vars: options.vars || {}, + }); + const service = new VarsService(run); - if (content instanceof Error) { - when(run.fs.readFile) - .calledWith(join(input, './presets.yaml'), expect.anything()) - .thenReject(content); - } else { - if (typeof content === 'string') { - content = {'./presets.yaml': content}; - } + if (typeof content === 'string' || content instanceof Error) { + content = {'./presets.yaml': content}; + } - for (const [file, data] of Object.entries(content)) { - when(run.fs.readFile) - .calledWith(join(input, file), expect.anything()) - .thenResolve(data); + for (const [file, data] of Object.entries(content)) { + if (data instanceof Error) { + when(run.read).calledWith(join(run.input, file)).thenReject(data); + } else { + when(run.read).calledWith(join(run.input, file)).thenResolve(data); } } @@ -55,7 +41,7 @@ function prepare(content: string | Hash | Error, options: Options = {}) async function call(content: string | Error, options: Options = {}) { const service = prepare(content, options); - const result = await service.load('./presets.yaml' as RelativePath); + const result = await service.load('presets.yaml' as NormalizedPath); expect(service.dump(result)).toMatchSnapshot(); } @@ -134,44 +120,45 @@ describe('vars', () => { const service = prepare( { './presets.yaml': dedent` - default: - field1: value1 - override1: value2 - override2: value2 - override3: value2 - override4: value2 - internal: - field2: value1 - override1: value1 - `, + default: + field1: value1 + override1: value2 + override2: value2 + override3: value2 + override4: value2 + internal: + field2: value1 + override1: value1 + `, './subfolder/presets.yaml': dedent` - default: - sub1: value1 - sub2: value2 - override2: value1 - override5: value2 - internal: - sub2: value1 - override3: value1 - override6: value2 - `, + default: + sub1: value1 + sub2: value2 + override2: value1 + override5: value2 + internal: + sub2: value1 + override3: value1 + override6: value2 + `, + './subfolder/subfolder/presets.yaml': ENOENT, './subfolder/subfolder/subfolder/presets.yaml': dedent` - default: - subsub1: value2 - override4: value2 - override5: value1 - internal: - subsub1: value1 - subsub2: value1 - override4: value1 - override6: value1 - `, + default: + subsub1: value2 + override4: value2 + override5: value1 + internal: + subsub1: value1 + subsub2: value1 + override4: value1 + override6: value1 + `, }, {varsPreset: 'internal'}, ); const result = await service.load( - './subfolder/subfolder/subfolder/presets.yaml' as RelativePath, + 'subfolder/subfolder/subfolder/presets.yaml' as NormalizedPath, ); expect(service.dump(result)).toMatchSnapshot(); @@ -187,7 +174,7 @@ describe('vars', () => { service.hooks.PresetsLoaded.tap('Test', spy); - await service.load('./presets.yaml' as RelativePath); + await service.load('presets.yaml' as NormalizedPath); expect(spy).toHaveBeenCalledWith({default: {field1: 'value1'}}, 'presets.yaml'); }); @@ -202,7 +189,7 @@ describe('vars', () => { service.hooks.Resolved.tap('Test', spy); - await service.load('./presets.yaml' as RelativePath); + await service.load('presets.yaml' as NormalizedPath); expect(spy).toHaveBeenCalledWith({field1: 'value1'}, 'presets.yaml'); }); @@ -219,7 +206,7 @@ describe('vars', () => { return presets; }); - const result = await service.load('./presets.yaml' as RelativePath); + const result = await service.load('presets.yaml' as NormalizedPath); expect(service.dump(result)).toMatchSnapshot(); }); @@ -236,7 +223,7 @@ describe('vars', () => { return presets; }); - const result = await service.load('./presets.yaml' as RelativePath); + const result = await service.load('presets.yaml' as NormalizedPath); expect(service.dump(result)).toMatchSnapshot(); }); @@ -252,7 +239,7 @@ describe('vars', () => { }); await expect(() => - service.load('./presets.yaml' as RelativePath), + service.load('presets.yaml' as NormalizedPath), ).rejects.toThrow(); }); @@ -267,7 +254,7 @@ describe('vars', () => { }); await expect(() => - service.load('./presets.yaml' as RelativePath), + service.load('presets.yaml' as NormalizedPath), ).rejects.toThrow(); }); @@ -283,8 +270,8 @@ describe('vars', () => { service.hooks.PresetsLoaded.tap('Test', spy1); service.hooks.Resolved.tap('Test', spy2); - await service.load('./presets.yaml' as RelativePath); - await service.load('./presets.yaml' as RelativePath); + await service.load('presets.yaml' as NormalizedPath); + await service.load('presets.yaml' as NormalizedPath); expect(spy1).toHaveBeenCalledOnce(); expect(spy2).toHaveBeenCalledOnce(); diff --git a/src/commands/build/errors/InsecureAccessError.ts b/src/commands/build/errors/InsecureAccessError.ts new file mode 100644 index 00000000..921fec89 --- /dev/null +++ b/src/commands/build/errors/InsecureAccessError.ts @@ -0,0 +1,19 @@ +export class InsecureAccessError extends Error { + readonly realpath: AbsolutePath; + + readonly realstack: AbsolutePath[]; + + constructor(file: AbsolutePath, stack?: AbsolutePath[]) { + const message = [ + `Requested file '${file}' is out of project scope.`, + stack && 'File resolution stack:\n\t' + stack.join('\n\t'), + ] + .filter(Boolean) + .join('\n'); + + super(message); + + this.realpath = file; + this.realstack = stack || []; + } +} diff --git a/src/commands/build/errors/index.ts b/src/commands/build/errors/index.ts new file mode 100644 index 00000000..ca6b1c2e --- /dev/null +++ b/src/commands/build/errors/index.ts @@ -0,0 +1 @@ +export {InsecureAccessError} from './InsecureAccessError'; diff --git a/src/commands/build/features/linter/index.ts b/src/commands/build/features/linter/index.ts index 6cb3a405..dc398bb3 100644 --- a/src/commands/build/features/linter/index.ts +++ b/src/commands/build/features/linter/index.ts @@ -1,7 +1,7 @@ import type {Build} from '../..'; import type {Command} from '~/config'; -import {resolve} from 'path'; +import {resolve} from 'node:path'; import shell from 'shelljs'; import {LogLevels} from '@diplodoc/transform/lib/log'; diff --git a/src/commands/build/features/templating/index.spec.ts b/src/commands/build/features/templating/index.spec.ts index ff220f81..a1c3edfc 100644 --- a/src/commands/build/features/templating/index.spec.ts +++ b/src/commands/build/features/templating/index.spec.ts @@ -217,7 +217,7 @@ describe('Build template feature', () => { it('should not save presets.yaml for html build', async () => { const build = setupBuild({ globs: { - '**/presets.yaml': ['./presets.yaml'], + '**/presets.yaml': ['presets.yaml'] as NormalizedPath[], }, files: { './presets.yaml': dedent` @@ -238,7 +238,7 @@ describe('Build template feature', () => { it('should save presets.yaml for md build with disabled templating', async () => { const build = setupBuild({ globs: { - '**/presets.yaml': ['./presets.yaml'], + '**/presets.yaml': ['presets.yaml'] as NormalizedPath[], }, files: { './presets.yaml': dedent` @@ -259,7 +259,7 @@ describe('Build template feature', () => { it('should filter presets.yaml for md build with disabled templating', async () => { const build = setupBuild({ globs: { - '**/presets.yaml': ['./presets.yaml'], + '**/presets.yaml': ['presets.yaml'] as NormalizedPath[], }, files: { './presets.yaml': dedent` diff --git a/src/commands/build/handler.ts b/src/commands/build/handler.ts index abe17161..90dfe68a 100644 --- a/src/commands/build/handler.ts +++ b/src/commands/build/handler.ts @@ -28,15 +28,13 @@ export async function handler(run: Run) { const {lintDisabled, buildDisabled, addMapFile} = ArgvService.getConfig(); PresetService.init(run.vars); - await preparingTocFiles(run); + await preparingTocFiles(); processExcludedFiles(); if (addMapFile) { prepareMapFile(); } - const outputBundlePath = run.bundlePath; - if (!lintDisabled) { /* Initialize workers in advance to avoid a timeout failure due to not receiving a message from them */ await initLinterWorkers(); @@ -44,7 +42,7 @@ export async function handler(run: Run) { const processes = [ !lintDisabled && processLinter(), - !buildDisabled && processPages(outputBundlePath), + !buildDisabled && processPages(run), ].filter(Boolean) as Promise[]; await Promise.all(processes); diff --git a/src/commands/build/index.spec.ts b/src/commands/build/index.spec.ts index 45b90235..622595dc 100644 --- a/src/commands/build/index.spec.ts +++ b/src/commands/build/index.spec.ts @@ -194,22 +194,33 @@ describe('Build command', () => { describe('ignoreStage', () => { test('should handle default', '', { - ignoreStage: 'skip', + ignoreStage: ['skip'], }); test('should handle arg', '--ignore-stage preview', { - ignoreStage: 'preview', + ignoreStage: ['preview'], }); test( 'should handle config', '', { - ignoreStage: 'preview', + ignoreStage: ['preview'], }, + { + ignoreStage: ['preview'], + }, + ); + + test( + 'should handle simplified config', + '', { ignoreStage: 'preview', }, + { + ignoreStage: ['preview'], + }, ); }); diff --git a/src/commands/build/index.ts b/src/commands/build/index.ts index acfe3472..dec4df1d 100644 --- a/src/commands/build/index.ts +++ b/src/commands/build/index.ts @@ -26,6 +26,9 @@ import {Changelogs, ChangelogsArgs, ChangelogsConfig} from './features/changelog import {Search, SearchArgs, SearchConfig, SearchRawConfig} from './features/search'; import {Legacy, LegacyArgs, LegacyConfig, LegacyRawConfig} from './features/legacy'; import shell from 'shelljs'; +import {intercept} from '~/utils'; + +export type * from './types'; export enum ResourceType { style = 'style', @@ -49,8 +52,7 @@ type BaseConfig = { vars: Hash; allowHtml: boolean; sanitizeHtml: boolean; - // TODO(minor): string[] - ignoreStage: string; + ignoreStage: string[]; ignore: string[]; addSystemMeta: boolean; // TODO(minor): we can generate this file all time @@ -72,39 +74,40 @@ export type {Run}; const command = 'Build'; -const hooks = () => ({ - /** - * Async series hook which runs before start of any Run type.

- * Args: - * - run - [Build.Run](./Run.ts) constructed context.
- * Best place to subscribe on Run hooks. - */ - BeforeAnyRun: new AsyncSeriesHook(['run'], `${command}.BeforeAnyRun`), - /** - * Async series hook map which runs before start of target Run type.

- * Args: - * - run - [Build.Run](./Run.ts) constructed context.
- * Best place to subscribe on target Run hooks. - */ - BeforeRun: new HookMap( - (format: `${OutputFormat}`) => - new AsyncSeriesHook(['run'], `${command}.${format}.BeforeRun`), - ), - /** - * Async parallel hook which runs on start of any Run type.

- * Args: - * - run - [Build.Run](./Run.ts) constructed context.
- * Best place to do something in parallel with main build process. - */ - Run: new AsyncParallelHook(['run'], `${command}.Run`), - // TODO: decompose handler and describe this hook - AfterRun: new HookMap( - (format: `${OutputFormat}`) => - new AsyncSeriesHook(['run'], `${command}.${format}.AfterRun`), - ), - // TODO: decompose handler and describe this hook - AfterAnyRun: new AsyncSeriesHook(['run'], `${command}.AfterAnyRun`), -}); +const hooks = () => + intercept(command, { + /** + * Async series hook which runs before start of any Run type.

+ * Args: + * - run - [Build.Run](./Run.ts) constructed context.
+ * Best place to subscribe on Run hooks. + */ + BeforeAnyRun: new AsyncSeriesHook(['run'], `${command}.BeforeAnyRun`), + /** + * Async series hook map which runs before start of target Run type.

+ * Args: + * - run - [Build.Run](./Run.ts) constructed context.
+ * Best place to subscribe on target Run hooks. + */ + BeforeRun: new HookMap( + (format: `${OutputFormat}`) => + new AsyncSeriesHook(['run'], `${command}.${format}.BeforeRun`), + ), + /** + * Async parallel hook which runs on start of any Run type.

+ * Args: + * - run - [Build.Run](./Run.ts) constructed context.
+ * Best place to do something in parallel with main build process. + */ + Run: new AsyncParallelHook(['run'], `${command}.Run`), + // TODO: decompose handler and describe this hook + AfterRun: new HookMap( + (format: `${OutputFormat}`) => + new AsyncSeriesHook(['run'], `${command}.${format}.AfterRun`), + ), + // TODO: decompose handler and describe this hook + AfterAnyRun: new AsyncSeriesHook(['run'], `${command}.AfterAnyRun`), + }); export type BuildArgs = ProgramArgs & BaseArgs & @@ -164,7 +167,7 @@ export class Build resources: [], allowCustomResources: false, staticContent: false, - ignoreStage: Stage.SKIP, + ignoreStage: [Stage.SKIP], addSystemMeta: false, buildDisabled: false, lint: {enabled: true, config: {'log-levels': {}}}, @@ -219,6 +222,7 @@ export class Build apply(program?: IProgram) { this.hooks.Config.tap('Build', (config, args) => { + const ignoreStage = defined('ignoreStage', args, config) || []; const langs = defined('langs', args, config) || []; const lang = defined('lang', config); @@ -243,6 +247,7 @@ export class Build Object.assign(config, pick(args, options)); + config.ignoreStage = [].concat(ignoreStage); config.langs = langs; config.lang = lang || langs[0]; @@ -268,7 +273,7 @@ export class Build } async action() { - if (typeof VERSION !== 'undefined') { + if (typeof VERSION !== 'undefined' && process.env.NODE_ENV !== 'test') { console.log(`Using v${VERSION} version`); } @@ -285,13 +290,7 @@ export class Build await run.copy(run.originalInput, run.input, ['node_modules/**', '*/node_modules/**']); - const presets = (await run.glob('**/presets.yaml', { - cwd: run.input, - ignore: run.config.ignore, - })) as RelativePath[]; - for (const preset of presets) { - await run.vars.load(preset); - } + await run.vars.init(); await Promise.all([handler(run), this.hooks.Run.promise(run)]); diff --git a/src/commands/build/legacy-config.ts b/src/commands/build/legacy-config.ts new file mode 100644 index 00000000..7a9e0087 --- /dev/null +++ b/src/commands/build/legacy-config.ts @@ -0,0 +1,54 @@ +import type {Run} from '.'; +import type {YfmArgv} from '~/models'; + +export function legacyConfig(run: Run): YfmArgv { + const {config} = run; + + return { + rootInput: run.originalInput, + input: run.input, + output: run.output, + quiet: config.quiet, + addSystemMeta: config.addSystemMeta, + addMapFile: config.addMapFile, + staticContent: config.staticContent, + strict: config.strict, + langs: config.langs, + lang: config.lang, + ignoreStage: config.ignoreStage[0], + singlePage: config.singlePage, + removeHiddenTocItems: config.removeHiddenTocItems, + allowCustomResources: config.allowCustomResources, + resources: config.resources, + analytics: config.analytics, + varsPreset: config.varsPreset, + vars: config.vars, + outputFormat: config.outputFormat, + allowHTML: config.allowHtml, + needToSanitizeHtml: config.sanitizeHtml, + useLegacyConditions: config.useLegacyConditions, + + ignore: config.ignore, + + applyPresets: config.template.features.substitutions, + resolveConditions: config.template.features.conditions, + conditionsInCode: config.template.scopes.code, + disableLiquid: !config.template.enabled, + + buildDisabled: config.buildDisabled, + + lintDisabled: !config.lint.enabled, + // @ts-ignore + lintConfig: config.lint.config, + + vcs: config.vcs, + connector: config.vcs.connector, + contributors: config.contributors, + ignoreAuthorPatterns: config.ignoreAuthorPatterns, + + changelogs: config.changelogs, + search: config.search, + + included: config.mergeIncludes, + }; +} diff --git a/src/commands/build/run.ts b/src/commands/build/run.ts index f563df66..25d1be57 100644 --- a/src/commands/build/run.ts +++ b/src/commands/build/run.ts @@ -1,11 +1,12 @@ import type {YfmArgv} from '~/models'; -import type {GlobOptions} from 'glob'; +import type {BuildConfig} from '.'; -// import {ok} from 'node:assert'; -import {dirname, join, resolve} from 'node:path'; -import {access, link, mkdir, readFile, stat, unlink, writeFile} from 'node:fs/promises'; +import {ok} from 'node:assert'; +import {dirname, join, relative, resolve} from 'node:path'; +import {access, link, mkdir, readFile, realpath, stat, unlink, writeFile} from 'node:fs/promises'; import {glob} from 'glob'; +import {bounded, normalizePath} from '~/utils'; import {configPath} from '~/config'; import { BUNDLE_FOLDER, @@ -15,13 +16,15 @@ import { YFM_CONFIG_FILENAME, } from '~/constants'; import {LogLevel, Logger} from '~/logger'; -import {BuildConfig} from '.'; -// import {InsecureAccessError} from './errors'; +import {legacyConfig} from './legacy-config'; +import {InsecureAccessError} from './errors'; import {VarsService} from './core/vars'; +import {addSourcePath} from './core/meta'; type FileSystem = { access: typeof access; stat: typeof stat; + realpath: typeof realpath; link: typeof link; unlink: typeof unlink; mkdir: typeof mkdir; @@ -29,8 +32,19 @@ type FileSystem = { writeFile: typeof writeFile; }; +type GlobOptions = { + cwd?: AbsolutePath; + ignore?: string[]; +}; + +type CopyOptions = { + ignore?: string[]; + sourcePath?: (path: string) => boolean; +}; + class RunLogger extends Logger { proc = this.topic(LogLevel.INFO, 'PROC'); + copy = this.topic(LogLevel.INFO, 'COPY'); } /** @@ -52,7 +66,7 @@ export class Run { readonly config: BuildConfig; - readonly fs: FileSystem = {access, stat, link, unlink, mkdir, readFile, writeFile}; + readonly fs: FileSystem = {access, stat, realpath, link, unlink, mkdir, readFile, writeFile}; readonly vars: VarsService; @@ -85,71 +99,77 @@ export class Run { ]); this.vars = new VarsService(this); - this.legacyConfig = { - rootInput: this.originalInput, - input: this.input, - output: this.output, - quiet: config.quiet, - addSystemMeta: config.addSystemMeta, - addMapFile: config.addMapFile, - staticContent: config.staticContent, - strict: config.strict, - langs: config.langs, - lang: config.lang, - ignoreStage: config.ignoreStage, - singlePage: config.singlePage, - removeHiddenTocItems: config.removeHiddenTocItems, - allowCustomResources: config.allowCustomResources, - resources: config.resources, - analytics: config.analytics, - varsPreset: config.varsPreset, - vars: config.vars, - outputFormat: config.outputFormat, - allowHTML: config.allowHtml, - needToSanitizeHtml: config.sanitizeHtml, - useLegacyConditions: config.useLegacyConditions, - - ignore: config.ignore, - - applyPresets: config.template.features.substitutions, - resolveConditions: config.template.features.conditions, - conditionsInCode: config.template.scopes.code, - disableLiquid: !config.template.enabled, - - buildDisabled: config.buildDisabled, - - lintDisabled: !config.lint.enabled, - // @ts-ignore - lintConfig: config.lint.config, - - vcs: config.vcs, - connector: config.vcs.connector, - contributors: config.contributors, - ignoreAuthorPatterns: config.ignoreAuthorPatterns, - - changelogs: config.changelogs, - search: config.search, - - included: config.mergeIncludes, - }; + this.legacyConfig = legacyConfig(this); } - write = async (path: AbsolutePath, content: string | Buffer) => { + /** + * Run.input bounded read helper + * + * Asserts file path is in project scope. + * + * @throws {InsecureAccessError} + * @param {AbsolutePath} path - unixlike absolute path to file + * + * @returns {Promise} + */ + @bounded async read(path: AbsolutePath) { + this.assertProjectScope(path); + + return this.fs.readFile(path, 'utf8'); + } + + /** + * Run.input bounded write helper. + * + * Asserts file path is in project scope. + * Drops hardlinks (unlink before write). + * Creates directory for file. + * + * @param {AbsolutePath} path - unixlike absolute path to file + * @param {string} content - file content + * + * @returns {Promise} + */ + @bounded async write(path: AbsolutePath, content: string) { + this.assertProjectScope(path); + await this.fs.mkdir(dirname(path), {recursive: true}); await this.fs.unlink(path).catch(() => {}); await this.fs.writeFile(path, content, 'utf8'); - }; + } - glob = async (pattern: string | string[], options: GlobOptions) => { - return glob(pattern, { + /** + * Glob wrapper with some default settings + * + * @param {string | string[]} pattern + * @param {GlobOptions} options + * + * @returns {NormalizedPath[]} + */ + @bounded async glob( + pattern: string | string[], + options: GlobOptions, + ): Promise { + const paths = await glob(pattern, { dot: true, nodir: true, follow: true, ...options, }); - }; - copy = async (from: AbsolutePath, to: AbsolutePath, ignore?: string[]) => { + return paths.map(normalizePath); + } + + @bounded async copy( + from: AbsolutePath, + to: AbsolutePath, + options: CopyOptions | CopyOptions['ignore'] = {}, + ) { + if (Array.isArray(options)) { + options = {ignore: options}; + } + + const {ignore, sourcePath} = options; const isFile = (await this.fs.stat(from)).isFile(); const hardlink = async (from: AbsolutePath, to: AbsolutePath) => { // const realpath = this.realpath(from); @@ -184,17 +204,48 @@ export class Run { dirs.add(dir); } - await hardlink(join(from, file), join(to, file)); + this.logger.copy(join(from, file), join(to, file)); + + if (sourcePath) { + const content = await this.read(join(from, file)); + this.write( + join(to, file), + addSourcePath(content, relative(this.input, join(from, file))), + ); + } else { + await hardlink(join(from, file), join(to, file)); + } } - }; + } - realpath = (path: AbsolutePath): AbsolutePath[] => { + realpath(path: AbsolutePath): Promise; + realpath(path: AbsolutePath, withStack: true): Promise; + realpath(path: AbsolutePath, withStack: false): Promise; + @bounded async realpath(path: AbsolutePath, withStack = true) { const stack = [path]; while (this._copyMap[path]) { path = this._copyMap[path]; stack.unshift(path); } - return stack; - }; + try { + const realpath = await this.fs.realpath(stack[0]); + + if (realpath !== stack[0]) { + stack.unshift(realpath); + } + } catch {} + + return withStack ? stack : stack[0]; + } + + private async assertProjectScope(path: AbsolutePath) { + const realpath = await this.realpath(path); + const isInScope = + realpath[0].startsWith(this.originalInput) || + realpath[0].startsWith(this.originalOutput) || + realpath[0].startsWith(this.input) || + realpath[0].startsWith(this.output); + ok(isInScope, new InsecureAccessError(path, realpath)); + } } diff --git a/src/commands/build/types.ts b/src/commands/build/types.ts new file mode 100644 index 00000000..cd567f4a --- /dev/null +++ b/src/commands/build/types.ts @@ -0,0 +1 @@ +export type * from './core/vars'; diff --git a/src/globals.d.ts b/src/globals.d.ts index 474aaea4..3ea9d4c7 100644 --- a/src/globals.d.ts +++ b/src/globals.d.ts @@ -8,24 +8,44 @@ type DeepPartial = { }; type UnresolvedPath = string & { - __type: 'unresolved'; -}; - -type AbsolutePath = string & { - __type: 'absolute'; -}; - -type RelativePath = string & { - __type: 'relative'; + __type: 'path'; + __mode: 'unresolved'; }; +type AbsolutePath = string & + ( + | { + __type: 'path'; + __mode: 'absolute'; + } + | `/${string}` + ); + +type RelativePath = string & + ( + | { + __type: 'path'; + __mode: 'relative'; + } + | `./${string}` + ); + +/** + * This is unix-like relative path with truncated heading ./ + */ type NormalizedPath = string & { - __type: 'normalized'; + __type: 'path'; + __mode: 'relative' & 'normalized'; }; type AnyPath = string | UnresolvedPath | AbsolutePath | RelativePath | NormalizedPath; -declare module 'path' { +type URIString = string & { + __type: 'uri'; + __mode: 'normalized'; +}; + +declare module 'node:path' { namespace path { interface PlatformPath extends PlatformPath { normalize(path: T): T; @@ -43,9 +63,25 @@ declare module 'path' { basename(path: AnyPath, suffix?: string): RelativePath; extname(path: AnyPath): string; + + sep: string; } } const path: path.PlatformPath; export = path; } + +declare module 'node:fs/promises' { + import {BufferEncoding, ObjectEncodingOptions} from 'node:fs'; + + export function readFile( + path: AbsolutePath, + options: ObjectEncodingOptions | BufferEncoding, + ): Promise; + + export function realpath( + path: AbsolutePath, + options?: ObjectEncodingOptions | BufferEncoding | null, + ): Promise; +} diff --git a/src/index.ts b/src/index.ts index 12fed307..b6bd580d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,5 @@ +import type {HookMeta} from './utils'; + import {MAIN_TIMER_ID} from '~/constants'; export type {ICallable, IProgram, ProgramConfig, ProgramArgs} from './program'; @@ -7,6 +9,7 @@ export type {Config, OptionInfo} from './config'; export {Command, option} from './config'; import {Program} from './program'; +import {own} from './utils'; if (require.main === module) { (async () => { @@ -21,16 +24,25 @@ if (require.main === module) { } catch (error: any) { exitCode = 1; - const message = error?.message || error; + if (own(error, 'hook')) { + const {service, hook, name} = error.hook; + // eslint-disable-next-line no-console + console.error( + `Intercept error for ${service}.${hook} hook from ${name} extension.`, + ); + } + const message = error?.message || error; if (message) { // eslint-disable-next-line no-console console.error(error.message || error); } } - // eslint-disable-next-line no-console - console.timeEnd(MAIN_TIMER_ID); + if (process.env.NODE_ENV !== 'test') { + // eslint-disable-next-line no-console + console.timeEnd(MAIN_TIMER_ID); + } process.exit(exitCode); })(); diff --git a/src/logger/index.ts b/src/logger/index.ts index 43fa6e77..79837ae2 100644 --- a/src/logger/index.ts +++ b/src/logger/index.ts @@ -30,11 +30,6 @@ type LoggerOptions = Readonly<{ quiet: boolean; }>; -type MessageInfo = { - level: LogLevels; - message: string; -}; - type Color = typeof red; const Write = Symbol('write'); @@ -95,8 +90,6 @@ export class Logger implements LogConsumer { private consumer: LogConsumer | null = null; - private buffer: MessageInfo[] = []; - private filters: ((level: LogLevels, message: string) => string)[]; constructor( @@ -120,6 +113,9 @@ export class Logger implements LogConsumer { * So if child and parent has the same topic with name 'proc', * only local topic will be applied to message. * Message will be decorated by local topic and will be passed to parent as raw string. + * + * @param {LogConsumer} consumer - parent logger + * @returns {Logger} */ pipe(consumer: LogConsumer) { if (this.consumer && this.consumer !== consumer) { @@ -128,18 +124,17 @@ export class Logger implements LogConsumer { this.consumer = consumer; - for (const {level, message} of this.buffer) { - this.consumer[Symbol.for(level) as keyof LogConsumer](message); - } - - this.buffer.length = 0; - return this; } /** * Defines new write decorator to one of defined log channeld. * Each decorator adds colored prefix to messages and apply preconfigured filters. + * + * @param {LogLevels} level + * @param {string} prefix - any bounded text prefix, which will be colored + * @param {Color} [color] - prefix color + * @returns new topic */ topic(level: LogLevels, prefix: string, color?: Color) { const channel = Symbol.for(level) as keyof LogConsumer; @@ -177,14 +172,7 @@ export class Logger implements LogConsumer { return this; } - clear() { - this.buffer.length = 0; - - return this; - } - reset() { - this.clear(); for (const level of Object.values(LogLevel)) { this[level].count = 0; } @@ -200,6 +188,7 @@ export class Logger implements LogConsumer { if (this.consumer) { this.consumer[Symbol.for(level) as keyof LogConsumer](message); } else { + // eslint-disable-next-line no-console console[level](message); } } diff --git a/src/models.ts b/src/models.ts index 16680fd5..cf35ab10 100644 --- a/src/models.ts +++ b/src/models.ts @@ -108,8 +108,6 @@ export interface YfmArgv extends YfmConfig { export type DocPreset = { default: YfmPreset; [varsPreset: string]: YfmPreset; -} & { - __metadata: Record[]; }; export interface YfmTocLabel extends Filter { @@ -124,14 +122,12 @@ export interface YfmToc extends Filter { items: YfmToc[]; stage?: Stage; base?: string; - deepBase?: number; title?: TextItems; include?: YfmTocInclude; id?: string; singlePage?: boolean; hidden?: boolean; label?: YfmTocLabel[] | YfmTocLabel; - root?: YfmToc; } export interface YfmTocInclude { @@ -268,12 +264,12 @@ export interface ResolverOptions { export interface PathData { pathToFile: string; - resolvedPathToFile: string; + resolvedPathToFile: AbsolutePath; filename: string; fileBaseName: string; fileExtension: string; - outputDir: string; - outputPath: string; + outputDir: AbsolutePath; + outputPath: AbsolutePath; outputFormat: string; outputBundlePath: string; outputTocDir: string; diff --git a/src/pages/redirect.ts b/src/pages/redirect.ts index 21e8f63d..fefc252c 100644 --- a/src/pages/redirect.ts +++ b/src/pages/redirect.ts @@ -1,10 +1,10 @@ import {join} from 'path'; -import {BUNDLE_FOLDER, Lang, RTL_LANGS} from '../constants'; +import {BUNDLE_FOLDER, RTL_LANGS} from '../constants'; import {PluginService} from '../services'; import manifest from '@diplodoc/client/manifest'; -export function generateStaticRedirect(lang: Lang, link: string): string { +export function generateStaticRedirect(lang: string, link: string): string { const isRTL = RTL_LANGS.includes(lang); return ` diff --git a/src/services/metadata/addSourcePath.ts b/src/services/metadata/addSourcePath.ts deleted file mode 100644 index 2bad3a82..00000000 --- a/src/services/metadata/addSourcePath.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { - emplaceFrontMatter, - separateAndExtractFrontMatter, -} from '@diplodoc/transform/lib/frontmatter'; -import {normalizeLineEndings} from './utils'; - -export const addSourcePath = (fileContent: string, sourcePath: string) => { - const {frontMatter, frontMatterStrippedContent} = separateAndExtractFrontMatter( - fileContent, - sourcePath, - ); - - return normalizeLineEndings( - emplaceFrontMatter(frontMatterStrippedContent, { - ...frontMatter, - sourcePath, - }), - ); -}; diff --git a/src/services/metadata/enrich.ts b/src/services/metadata/enrich.ts index 44aefa44..30b5c94d 100644 --- a/src/services/metadata/enrich.ts +++ b/src/services/metadata/enrich.ts @@ -1,12 +1,9 @@ +import type {FrontMatter} from '@diplodoc/transform/lib/frontmatter'; + import {MetaDataOptions, VarsMetadata} from '../../models'; import {mergeFrontMatter} from './mergeMetadata'; -import {FrontMatter} from '@diplodoc/transform/lib/frontmatter/common'; import {resolveVCSFrontMatter} from './vcsMetadata'; -import { - emplaceFrontMatter, - separateAndExtractFrontMatter, -} from '@diplodoc/transform/lib/frontmatter'; -import {normalizeLineEndings} from './utils'; +import {composeFrontMatter, extractFrontMatter} from '@diplodoc/transform/lib/frontmatter'; type FrontMatterVars = { metadataVars?: VarsMetadata; @@ -35,10 +32,7 @@ export const enrichWithFrontMatter = async ({ const {systemVars, metadataVars} = resolvedFrontMatterVars; const {resources, addSystemMeta, shouldAlwaysAddVCSPath, pathData} = metadataOptions; - const {frontMatter, frontMatterStrippedContent} = separateAndExtractFrontMatter( - fileContent, - pathData.pathToFile, - ); + const [frontMatter, strippedContent] = extractFrontMatter(fileContent, pathData.pathToFile); const vcsFrontMatter = metadataOptions.isContributorsEnabled ? await resolveVCSFrontMatter(frontMatter, metadataOptions, fileContent) @@ -59,5 +53,5 @@ export const enrichWithFrontMatter = async ({ }, }); - return normalizeLineEndings(emplaceFrontMatter(frontMatterStrippedContent, mergedFrontMatter)); + return composeFrontMatter(mergedFrontMatter, strippedContent); }; diff --git a/src/services/metadata/index.ts b/src/services/metadata/index.ts index 18d4b6b6..c5a27adb 100644 --- a/src/services/metadata/index.ts +++ b/src/services/metadata/index.ts @@ -1,3 +1,2 @@ -export * from './addSourcePath'; export * from './enrich'; export * from './vcsMetadata'; diff --git a/src/services/metadata/mergeMetadata.ts b/src/services/metadata/mergeMetadata.ts index d7226bbc..5a68ebd0 100644 --- a/src/services/metadata/mergeMetadata.ts +++ b/src/services/metadata/mergeMetadata.ts @@ -1,6 +1,6 @@ import {Resources, VarsMetadata} from '../../models'; import {isObject} from '../utils'; -import {FrontMatter} from '@diplodoc/transform/lib/frontmatter/common'; +import {FrontMatter} from '@diplodoc/transform/lib/frontmatter'; export const mergeFrontMatter = ({ existingMetadata, diff --git a/src/services/metadata/utils.ts b/src/services/metadata/utils.ts deleted file mode 100644 index 174862cb..00000000 --- a/src/services/metadata/utils.ts +++ /dev/null @@ -1,6 +0,0 @@ -import {CARRIAGE_RETURN} from '../../constants'; - -// IMO, we should just always apply this at the end of the whole processing pipeline, -// not when dumping meta/front matter -export const normalizeLineEndings = (input: string): string => - input.replace(/\r?\n/g, CARRIAGE_RETURN); diff --git a/src/services/metadata/vcsMetadata.ts b/src/services/metadata/vcsMetadata.ts index 18fbbac6..044f9139 100644 --- a/src/services/metadata/vcsMetadata.ts +++ b/src/services/metadata/vcsMetadata.ts @@ -1,4 +1,6 @@ import {TocService} from '..'; +import type {FrontMatter} from '@diplodoc/transform/lib/frontmatter'; + import {Contributor, MetaDataOptions, Metadata, PathData} from '../../models'; import {VCSConnector} from '../../vcs-connector/connector-models'; import { @@ -8,7 +10,6 @@ import { } from '../authors'; import {ContributorsServiceFileData, getFileContributors, getFileIncludes} from '../contributors'; import {isObject} from '../utils'; -import {FrontMatter} from '@diplodoc/transform/lib/frontmatter/common'; const getFileDataForContributorsService = ( pathData: PathData, diff --git a/src/services/preset.ts b/src/services/preset.ts index 13694c85..b67f31d3 100644 --- a/src/services/preset.ts +++ b/src/services/preset.ts @@ -8,7 +8,7 @@ export type PresetStorage = Map; let presetStorage: PresetStorage = new Map(); function init(vars: VarsService) { - for (const [path, values] of vars.entries()) { + for (const [path, values] of vars.entries) { presetStorage.set(dirname(path), values); } } diff --git a/src/services/tocs.ts b/src/services/tocs.ts index c8da036f..23cd2807 100644 --- a/src/services/tocs.ts +++ b/src/services/tocs.ts @@ -13,7 +13,7 @@ import {IncludeMode, Stage} from '../constants'; import {isExternalHref, logger} from '../utils'; import {filterFiles, firstFilterItem, firstFilterTextItems, liquidField} from './utils'; import {IncludersError, applyIncluders} from './includers'; -import {addSourcePath} from './metadata'; +import {addSourcePath} from '../commands/build/core/meta'; export interface TocServiceData { storage: Map; diff --git a/src/steps/processMapFile.ts b/src/steps/processMapFile.ts index 5dc9c7d6..c3ccc1aa 100644 --- a/src/steps/processMapFile.ts +++ b/src/steps/processMapFile.ts @@ -24,7 +24,7 @@ export function prepareMapFile(): void { return preparedPath; }); - const navigationPaths = {files: [...new Set(navigationPathsWithoutExtensions)]}; + const navigationPaths = {files: [...new Set(navigationPathsWithoutExtensions)].sort()}; const filesMapBuffer = Buffer.from(JSON.stringify(navigationPaths, null, '\t'), 'utf8'); const mapFile = join(outputFolderPath, 'files.json'); diff --git a/src/steps/processPages.ts b/src/steps/processPages.ts index 9ce664b6..9165b91b 100644 --- a/src/steps/processPages.ts +++ b/src/steps/processPages.ts @@ -1,5 +1,6 @@ import type {DocInnerProps} from '@diplodoc/client'; -import {basename, dirname, extname, join, resolve} from 'path'; +import type {Run} from '~/commands/build'; +import {basename, dirname, extname, join, resolve} from 'node:path'; import {existsSync, mkdirSync, readFileSync, writeFileSync} from 'fs'; import log from '@diplodoc/transform/lib/log'; import {asyncify, mapLimit} from 'async'; @@ -27,13 +28,7 @@ import {resolveMd2HTML, resolveMd2Md} from '../resolvers'; import {ArgvService, LeadingService, PluginService, SearchService, TocService} from '../services'; import {generateStaticMarkup} from '~/pages/document'; import {generateStaticRedirect} from '~/pages/redirect'; -import { - getDepth, - joinSinglePageResults, - logger, - transformToc, - transformTocForSinglePage, -} from '../utils'; +import {getDepth, joinSinglePageResults, transformToc, transformTocForSinglePage} from '../utils'; import {getVCSConnector} from '../vcs-connector'; import {VCSConnector} from '../vcs-connector/connector-models'; @@ -41,7 +36,7 @@ const singlePageResults: Record = {}; const singlePagePaths: Record> = {}; // Processes files of documentation (like index.yaml, *.md) -export async function processPages(outputBundlePath: string): Promise { +export async function processPages(run: Run): Promise { const { input: inputFolderPath, output: outputFolderPath, @@ -65,10 +60,10 @@ export async function processPages(outputBundlePath: string): Promise { inputFolderPath, outputFolderPath, outputFormat, - outputBundlePath, + run.bundlePath, ); - logger.proc(pathToFile); + run.logger.proc(pathToFile); const metaDataOptions = getMetaDataOptions(pathData, vcsConnector); @@ -82,11 +77,10 @@ export async function processPages(outputBundlePath: string): Promise { ); if (singlePage) { - await saveSinglePages(); - if (outputFormat === 'html') { await saveTocData(transformTocForSinglePage, 'single-page-toc'); } + await saveSinglePages(run); } if (outputFormat === 'html') { @@ -147,14 +141,7 @@ async function saveTocData(transform: (toc: YfmToc, tocDir: string) => YfmToc, f } } -async function saveSinglePages() { - const { - input: inputFolderPath, - lang: configLang, - langs: configLangs, - resources, - } = ArgvService.getConfig(); - +async function saveSinglePages(run: Run) { try { await Promise.all( Object.keys(singlePageResults).map(async (tocDir) => { @@ -163,25 +150,24 @@ async function saveSinglePages() { } const relativeTocDir = tocDir - .replace(inputFolderPath, '') + .replace(run.input, '') .replace(/\\/g, '/') .replace(/^\/?/, ''); const singlePageBody = joinSinglePageResults( singlePageResults[tocDir], - inputFolderPath, relativeTocDir, ); const toc = TocService.getForPath(join(relativeTocDir, 'toc.yaml'))[1] as YfmToc; - const lang = configLang ?? Lang.RU; - const langs = configLangs?.length ? configLangs : [lang]; + const lang = run.config.lang ?? Lang.RU; + const langs = run.config.langs.length ? run.config.langs : [lang]; const pageData = { data: { leading: false as const, html: singlePageBody, headings: [], - meta: resources || {}, + meta: run.config.resources, title: toc.title || '', }, router: { diff --git a/src/utils/common.ts b/src/utils/common.ts index bae1fff7..79310dc8 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -1,3 +1,4 @@ +import type {Hook, HookMap} from 'tapable'; import {cloneDeepWith, flatMapDeep, isArray, isObject, isString} from 'lodash'; import {isFileExists, resolveRelativePath} from '@diplodoc/transform/lib/utilsFS'; @@ -24,7 +25,7 @@ export function modifyValuesByKeys( modifyFn: (value: string) => string, ) { // Clone the object deeply with a customizer function that modifies matching keys - return cloneDeepWith(originalObj, function (value: unknown, key) { + return cloneDeepWith(originalObj, (value: unknown, key) => { if (keysToFind.includes(key as string) && isString(value)) { return modifyFn(value); } @@ -51,7 +52,10 @@ export function checkPathExists(path: string, parentFilePath: string) { return isFileExists(includePath); } -export function own(box: unknown, field: T): box is {[p in T]: unknown} { +export function own( + box: unknown, + field: T, +): box is {[p in T]: V} { return ( Boolean(box && typeof box === 'object') && Object.prototype.hasOwnProperty.call(box, field) ); @@ -75,3 +79,57 @@ export function freeze(target: T, visited = new Set()): T { return target; } + +export type HookMeta = { + service: string; + hook: string; + name: string; + type: string; +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function intercept | HookMap>>( + service: string, + hooks: T, +): T { + for (const [hook, handler] of Object.entries(hooks)) { + handler.intercept({ + register: (info) => { + const {type, name, fn} = info; + const meta = {service, hook, name, type}; + + if (type === 'promise') { + info.fn = async (...args: unknown[]) => { + try { + return await fn(...args); + } catch (error) { + if (error instanceof Error) { + Object.assign(error, {hook: meta}); + } + + throw error; + } + }; + } else if (type === 'sync') { + info.fn = (...args: unknown[]) => { + try { + return fn(...args); + } catch (error) { + if (error instanceof Error) { + Object.assign(error, {hook: meta}); + } + + throw error; + } + }; + } else { + throw new TypeError('Unexpected hook tap type - ' + type); + } + + return info; + }, + }); + } + + return hooks; +} diff --git a/src/utils/decorators.ts b/src/utils/decorators.ts new file mode 100644 index 00000000..dea73f1c --- /dev/null +++ b/src/utils/decorators.ts @@ -0,0 +1,12 @@ +export function bounded(_originalMethod: unknown, context: ClassMethodDecoratorContext) { + const methodName = context.name; + + if (context.private) { + throw new Error(`'bound' cannot decorate private properties like ${methodName as string}.`); + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + context.addInitializer(function (this: any) { + this[methodName] = this[methodName].bind(this); + }); +} diff --git a/src/utils/index.ts b/src/utils/index.ts index a495e0b1..87876f31 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -6,3 +6,4 @@ export * from './path'; export * from './toc'; export * from './presets'; export * from './file'; +export * from './decorators'; diff --git a/src/utils/path.ts b/src/utils/path.ts index 0aea9815..e01b6c22 100644 --- a/src/utils/path.ts +++ b/src/utils/path.ts @@ -1,6 +1,10 @@ -import {sep} from 'path'; +import {normalize, sep} from 'node:path'; import {Platforms} from '../constants'; +export function normalizePath(path: string): NormalizedPath { + return normalize(path).replace(/\\/g, '/') as NormalizedPath; +} + export function addSlashPrefix(path: string): string { const slashPrefix = path.startsWith(sep) ? '' : sep; diff --git a/src/utils/singlePage.ts b/src/utils/singlePage.ts index c9794b1a..b9e83289 100644 --- a/src/utils/singlePage.ts +++ b/src/utils/singlePage.ts @@ -143,7 +143,6 @@ export function getSinglePageUrl(tocDir: string, path: string) { export function joinSinglePageResults( singlePageResults: SinglePageResult[], - root: string, tocDir: string, ): string { const delimeter = `
`; diff --git a/tests/e2e/__snapshots__/generate-map.spec.ts.snap b/tests/e2e/__snapshots__/generate-map.spec.ts.snap index 4e5d6637..20eb9829 100644 --- a/tests/e2e/__snapshots__/generate-map.spec.ts.snap +++ b/tests/e2e/__snapshots__/generate-map.spec.ts.snap @@ -3,12 +3,12 @@ exports[`Generate map for project with external links in toc 1`] = ` "{ "files": [ - "ru/folder1/folder2/a1", - "ru/folder1/a1", "ru/", - "ru/settings", + "ru/folder1/a1", + "ru/folder1/folder2/a1", "ru/plugins/", - "ru/project/" + "ru/project/", + "ru/settings" ] }" `; @@ -17,17 +17,17 @@ exports[`Generate map for project with multiple language 1`] = ` "{ "files": [ "en/", - "en/settings", "en/plugins/", "en/plugins/import", "en/project/", "en/project/config", + "en/settings", "ru/", - "ru/settings", "ru/plugins/", "ru/plugins/import", "ru/project/", - "ru/project/config" + "ru/project/config", + "ru/settings" ] }" `; @@ -35,14 +35,14 @@ exports[`Generate map for project with multiple language 1`] = ` exports[`Generate map for project with single language and toc include - only md2html 1`] = ` "{ "files": [ - "ru/folder1/folder2/a1", - "ru/folder1/a1", "ru/", - "ru/settings", + "ru/folder1/a1", + "ru/folder1/folder2/a1", "ru/plugins/", "ru/plugins/import", "ru/project/", - "ru/project/config" + "ru/project/config", + "ru/settings" ] }" `; @@ -50,14 +50,14 @@ exports[`Generate map for project with single language and toc include - only md exports[`Generate map for project with single language and toc include 1`] = ` "{ "files": [ - "ru/folder1/folder2/a1", - "ru/folder1/a1", "ru/", - "ru/settings", + "ru/folder1/a1", + "ru/folder1/folder2/a1", "ru/plugins/", "ru/plugins/import", "ru/project/", - "ru/project/config" + "ru/project/config", + "ru/settings" ] }" `; diff --git a/tests/integrations/services/liquidInFrontMatter.test.ts b/tests/integrations/services/liquidInFrontMatter.test.ts index 150cdf53..04e29f09 100644 --- a/tests/integrations/services/liquidInFrontMatter.test.ts +++ b/tests/integrations/services/liquidInFrontMatter.test.ts @@ -1,24 +1,16 @@ -import { - emplaceFrontMatter, - separateAndExtractFrontMatter, -} from '@diplodoc/transform/lib/frontmatter'; +import {composeFrontMatter, extractFrontMatter} from '@diplodoc/transform/lib/frontmatter'; import liquid from '@diplodoc/transform/lib/liquid'; import {readFile} from 'fs/promises'; -import {normalizeLineEndings} from '../../../src/services/metadata/utils'; const propValuesMockPath = 'mocks/fileContent/metadata/substitutionsInMetadataPropertyValues.md'; -const propKeysMockPath = 'mocks/fileContent/metadata/substitutionsInMetadataPropertyKeys.md'; const emptyStringMockPath = 'mocks/fileContent/metadata/substitutionsWithEmptyString.md'; describe('Front matter (metadata) transformations', () => { it('do not break when a property value contains Liquid-style variable substitutions', async () => { const fileContent = await readFile(propValuesMockPath, {encoding: 'utf-8'}); - const {frontMatter, frontMatterStrippedContent} = - separateAndExtractFrontMatter(fileContent); - const processedContent = normalizeLineEndings( - emplaceFrontMatter(frontMatterStrippedContent, frontMatter), - ); + const [frontMatter, strippedContent] = extractFrontMatter(fileContent); + const processedContent = composeFrontMatter(frontMatter, strippedContent); expect(frontMatter).toMatchSnapshot(); expect(processedContent).toMatchSnapshot(); @@ -27,11 +19,8 @@ describe('Front matter (metadata) transformations', () => { it('emit valid metadata when a variable is substituted with an ampty string', async () => { const fileContent = await readFile(emptyStringMockPath, {encoding: 'utf-8'}); - const {frontMatter, frontMatterStrippedContent} = - separateAndExtractFrontMatter(fileContent); - const processedContent = normalizeLineEndings( - emplaceFrontMatter(frontMatterStrippedContent, frontMatter), - ); + const [frontMatter, frontMatterStrippedContent] = extractFrontMatter(fileContent); + const processedContent = composeFrontMatter(frontMatter, frontMatterStrippedContent); const liquidProcessedInput = liquid(processedContent, {var: ''}); diff --git a/tests/integrations/services/metadataAuthors.test.ts b/tests/integrations/services/metadataAuthors.test.ts index a0a7f3ac..dc1fbe1a 100644 --- a/tests/integrations/services/metadataAuthors.test.ts +++ b/tests/integrations/services/metadataAuthors.test.ts @@ -1,4 +1,4 @@ -import {separateAndExtractFrontMatter} from '@diplodoc/transform/lib/frontmatter'; +import {extractFrontMatter} from '@diplodoc/transform/lib/frontmatter'; import {readFileSync} from 'fs'; import {MetaDataOptions} from 'models'; import {enrichWithFrontMatter} from 'services/metadata'; @@ -37,12 +37,12 @@ describe('getContentWithUpdatedMetadata (Authors)', () => { metadataOptions = { pathData: { pathToFile: '', - resolvedPathToFile: '', + resolvedPathToFile: '' as AbsolutePath, filename: '', fileBaseName: '', fileExtension: '', - outputDir: '', - outputPath: '', + outputDir: '' as AbsolutePath, + outputPath: '' as AbsolutePath, outputFormat: '', outputBundlePath: '', outputTocDir: '', @@ -63,8 +63,8 @@ describe('getContentWithUpdatedMetadata (Authors)', () => { resolvedFrontMatterVars: {}, }); - const {frontMatter: originalMeta} = separateAndExtractFrontMatter(fileContent); - const {frontMatter: updatedMeta} = separateAndExtractFrontMatter(updatedFileContent); + const [originalMeta] = extractFrontMatter(fileContent); + const [updatedMeta] = extractFrontMatter(updatedFileContent); const expectedMeta = { ...originalMeta, @@ -83,8 +83,8 @@ describe('getContentWithUpdatedMetadata (Authors)', () => { resolvedFrontMatterVars: {}, }); - const {frontMatter: originalMeta} = separateAndExtractFrontMatter(fileContent); - const {frontMatter: updatedMeta} = separateAndExtractFrontMatter(updatedFileContent); + const [originalMeta] = extractFrontMatter(fileContent); + const [updatedMeta] = extractFrontMatter(updatedFileContent); expect(updatedMeta).toEqual(originalMeta); }); @@ -94,12 +94,12 @@ describe('getContentWithUpdatedMetadata (Authors)', () => { const metadataOptions: MetaDataOptions = { pathData: { pathToFile: '', - resolvedPathToFile: '', + resolvedPathToFile: '' as AbsolutePath, filename: '', fileBaseName: '', fileExtension: '', - outputDir: '', - outputPath: '', + outputDir: '' as AbsolutePath, + outputPath: '' as AbsolutePath, outputFormat: '', outputBundlePath: '', outputTocDir: '', @@ -119,10 +119,8 @@ describe('getContentWithUpdatedMetadata (Authors)', () => { resolvedFrontMatterVars: {}, }); - const {frontMatter: metadataBeforeEnrichment} = - separateAndExtractFrontMatter(fileContent); - const {frontMatter: metadataAfterEnrichment} = - separateAndExtractFrontMatter(updatedFileContent); + const [metadataBeforeEnrichment] = extractFrontMatter(fileContent); + const [metadataAfterEnrichment] = extractFrontMatter(updatedFileContent); expect(metadataAfterEnrichment).toEqual(metadataBeforeEnrichment); }); @@ -141,10 +139,8 @@ describe('getContentWithUpdatedMetadata (Authors)', () => { resolvedFrontMatterVars: {}, }); - const {frontMatter: metadataBeforeEnrichment} = - separateAndExtractFrontMatter(fileContent); - const {frontMatter: metadataAfterEnrichment} = - separateAndExtractFrontMatter(updatedFileContent); + const [metadataBeforeEnrichment] = extractFrontMatter(fileContent); + const [metadataAfterEnrichment] = extractFrontMatter(updatedFileContent); expect(metadataAfterEnrichment).toEqual(metadataBeforeEnrichment); }, @@ -168,10 +164,8 @@ describe('getContentWithUpdatedMetadata (Authors)', () => { resolvedFrontMatterVars: {}, }); - const {frontMatter: metadataBeforeEnrichment} = - separateAndExtractFrontMatter(fileContent); - const {frontMatter: metadataAfterEnrichment} = - separateAndExtractFrontMatter(updatedFileContent); + const [metadataBeforeEnrichment] = extractFrontMatter(fileContent); + const [metadataAfterEnrichment] = extractFrontMatter(updatedFileContent); expect(metadataAfterEnrichment).toEqual(metadataBeforeEnrichment); }, @@ -188,8 +182,8 @@ describe('getContentWithUpdatedMetadata (Authors)', () => { resolvedFrontMatterVars: {}, }); - const {frontMatter: originalMeta} = separateAndExtractFrontMatter(fileContent); - const {frontMatter: updatedMeta} = separateAndExtractFrontMatter(updatedFileContent); + const [originalMeta] = extractFrontMatter(fileContent); + const [updatedMeta] = extractFrontMatter(updatedFileContent); const expectedMeta = { ...originalMeta, diff --git a/tests/integrations/services/metadataContributors.test.ts b/tests/integrations/services/metadataContributors.test.ts index d9c67942..6a239b0f 100644 --- a/tests/integrations/services/metadataContributors.test.ts +++ b/tests/integrations/services/metadataContributors.test.ts @@ -3,7 +3,7 @@ import {normalize} from 'path'; import {Contributor, Contributors, MetaDataOptions} from 'models'; import {enrichWithFrontMatter} from 'services/metadata'; import {VCSConnector} from 'vcs-connector/connector-models'; -import {separateAndExtractFrontMatter} from '@diplodoc/transform/lib/frontmatter'; +import {extractFrontMatter} from '@diplodoc/transform/lib/frontmatter'; const simpleMetadataFilePath = 'mocks/fileContent/metadata/simpleMetadata.md'; const withoutMetadataFilePath = 'mocks/fileContent/metadata/withoutMetadata.md'; @@ -15,12 +15,12 @@ describe('getContentWithUpdatedMetadata (Contributors)', () => { const metadataOptions: MetaDataOptions = { pathData: { pathToFile: '', - resolvedPathToFile: '', + resolvedPathToFile: '' as AbsolutePath, filename: '', fileBaseName: '', fileExtension: '', - outputDir: '', - outputPath: '', + outputDir: '' as AbsolutePath, + outputPath: '' as AbsolutePath, outputFormat: '', outputBundlePath: '', outputTocDir: '', @@ -60,9 +60,8 @@ describe('getContentWithUpdatedMetadata (Contributors)', () => { resolvedFrontMatterVars: {}, }); - const {frontMatter: originalMeta} = separateAndExtractFrontMatter(fileContent); - const {frontMatter: updatedMeta} = - separateAndExtractFrontMatter(updatedFileContent); + const [originalMeta] = extractFrontMatter(fileContent); + const [updatedMeta] = extractFrontMatter(updatedFileContent); expect(updatedMeta).toEqual(originalMeta); }); @@ -84,9 +83,8 @@ describe('getContentWithUpdatedMetadata (Contributors)', () => { resolvedFrontMatterVars: {}, }); - const {frontMatter: originalMeta} = separateAndExtractFrontMatter(fileContent); - const {frontMatter: updatedMeta} = - separateAndExtractFrontMatter(updatedFileContent); + const [originalMeta] = extractFrontMatter(fileContent); + const [updatedMeta] = extractFrontMatter(updatedFileContent); expect(updatedMeta).toEqual(originalMeta); }, @@ -127,9 +125,8 @@ describe('getContentWithUpdatedMetadata (Contributors)', () => { resolvedFrontMatterVars: {}, }); - const {frontMatter: originalMeta} = separateAndExtractFrontMatter(fileContent); - const {frontMatter: updatedMeta} = - separateAndExtractFrontMatter(updatedFileContent); + const [originalMeta] = extractFrontMatter(fileContent); + const [updatedMeta] = extractFrontMatter(updatedFileContent); const expectedMeta = { ...originalMeta, @@ -169,9 +166,8 @@ describe('getContentWithUpdatedMetadata (Contributors)', () => { resolvedFrontMatterVars: {}, }); - const {frontMatter: originalMeta} = separateAndExtractFrontMatter(fileContent); - const {frontMatter: updatedMeta} = - separateAndExtractFrontMatter(updatedFileContent); + const [originalMeta] = extractFrontMatter(fileContent); + const [updatedMeta] = extractFrontMatter(updatedFileContent); const expectedMeta = { ...originalMeta, @@ -248,7 +244,7 @@ describe('getContentWithUpdatedMetadata (Contributors)', () => { contributors: getFileContributors(path), hasIncludes: item.getHasIncludes(path), }); - metadataOptions.pathData.resolvedPathToFile = withIncludesFilePath; + metadataOptions.pathData.resolvedPathToFile = withIncludesFilePath as AbsolutePath; const fileContent = readFileSync(withIncludesFilePath, 'utf8'); const updatedFileContent = await enrichWithFrontMatter({ @@ -257,10 +253,8 @@ describe('getContentWithUpdatedMetadata (Contributors)', () => { resolvedFrontMatterVars: {}, }); - const {frontMatter: originalMeta} = - separateAndExtractFrontMatter(fileContent); - const {frontMatter: updatedMeta} = - separateAndExtractFrontMatter(updatedFileContent); + const [originalMeta] = extractFrontMatter(fileContent); + const [updatedMeta] = extractFrontMatter(updatedFileContent); const expectedMeta = { ...originalMeta, @@ -286,10 +280,8 @@ describe('getContentWithUpdatedMetadata (Contributors)', () => { resolvedFrontMatterVars: {}, }); - const {frontMatter: metadataBeforeEnrichment} = - separateAndExtractFrontMatter(fileContent); - const {frontMatter: metadataAfterEnrichment} = - separateAndExtractFrontMatter(updatedFileContent); + const [metadataBeforeEnrichment] = extractFrontMatter(fileContent); + const [metadataAfterEnrichment] = extractFrontMatter(updatedFileContent); expect(metadataAfterEnrichment).toEqual(metadataBeforeEnrichment); }); @@ -308,10 +300,8 @@ describe('getContentWithUpdatedMetadata (Contributors)', () => { resolvedFrontMatterVars: {}, }); - const {frontMatter: metadataBeforeEnrichment} = - separateAndExtractFrontMatter(fileContent); - const {frontMatter: metadataAfterEnrichment} = - separateAndExtractFrontMatter(updatedFileContent); + const [metadataBeforeEnrichment] = extractFrontMatter(fileContent); + const [metadataAfterEnrichment] = extractFrontMatter(updatedFileContent); expect(metadataAfterEnrichment).toEqual(metadataBeforeEnrichment); }, diff --git a/tests/jest.config.js b/tests/jest.config.js index 8c356442..2cd11613 100644 --- a/tests/jest.config.js +++ b/tests/jest.config.js @@ -2,14 +2,14 @@ const path = require('path'); module.exports = { transform: { - '^.+\\.(j|t)s?$': ['ts-jest', {tsconfig: '/tsconfig.json'}], + '^.+\\.ts?$': ['ts-jest', {tsconfig: '/tsconfig.json'}], }, verbose: true, preset: 'ts-jest', testMatch: ['/**/*.@(test|spec).ts'], testEnvironment: 'node', roots: ['/..', '../src', '../tests'], - collectCoverageFrom: ['../src/**/*.ts', '!../src/app/**', '!../src/**/*.d.ts'], + collectCoverageFrom: ['../src/**/*.ts', '!../src/**/*.d.ts'], coverageProvider: 'v8', coverageDirectory: '/coverage', moduleDirectories: ['node_modules', path.join(__dirname, '../src'), path.join(__dirname, '../tests')], diff --git a/tests/tsconfig.json b/tests/tsconfig.json index 40e1635e..09d1f5a0 100644 --- a/tests/tsconfig.json +++ b/tests/tsconfig.json @@ -9,5 +9,12 @@ "~/*": ["../src/*"] }, "types": ["node", "jest"] - } + }, + "include": [ + "../src", + "." + ], + "exclude": [ + "node_modules" + ] } diff --git a/tests/units/services/metadata.test.ts b/tests/units/services/metadata.test.ts index a68f6e66..dda159c1 100644 --- a/tests/units/services/metadata.test.ts +++ b/tests/units/services/metadata.test.ts @@ -48,12 +48,12 @@ describe('getUpdatedMetadata', () => { metaDataOptions = { pathData: { pathToFile: '', - resolvedPathToFile: '', + resolvedPathToFile: '' as AbsolutePath, filename: '', fileBaseName: '', fileExtension: '', - outputDir: '', - outputPath: '', + outputDir: '' as AbsolutePath, + outputPath: '' as AbsolutePath, outputFormat: '', outputBundlePath: '', outputTocDir: '', diff --git a/tests/utils.ts b/tests/utils.ts index 4ef01437..66552301 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -39,7 +39,7 @@ export function compareDirectories(outputPath: string) { const filesFromOutput = walkSync(outputPath, { directories: false, includeBasePath: false, - }); + }).sort(); expect(bundleless(JSON.stringify(filesFromOutput, null, 2))).toMatchSnapshot('filelist'); diff --git a/tsconfig.json b/tsconfig.json index 3bc49dd1..7a503038 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,6 +6,7 @@ "outDir": "build", "module": "es2022", "moduleResolution": "bundler", + "experimentalDecorators": false, "paths": { "~/*": ["./src/*"] }