diff --git a/.eslintignore b/.eslintignore index 55b43acda07..4967569ba2e 100644 --- a/.eslintignore +++ b/.eslintignore @@ -4,6 +4,8 @@ **/config/** **/build/** **/npm/** +**/__tests__/integration/fixtures/** +!scripts/npm/** **/*.js.flow **/*.d.ts **/playwright*/** diff --git a/.eslintrc.js b/.eslintrc.js index c02e6d2f13a..f68ccf5cc29 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -51,6 +51,8 @@ module.exports = { // node scripts should be console logging so don't lint against that files: ['scripts/**/*.js'], rules: { + // https://github.com/Stuk/eslint-plugin-header/issues/39 + 'header/header': OFF, 'no-console': OFF, }, }, diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0337cb442d0..9c5628c70a2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -73,6 +73,33 @@ jobs: run: npm ci - run: npm run test-unit + integration: + if: github.repository_owner == 'facebook' + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [18.18.0] + env: + CI: true + steps: + - uses: actions/checkout@v3 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + - run: npm i -g npm@8 + - uses: actions/cache@v3 + id: cache + with: + path: | + node_modules + ~/.cache/ms-playwright + key: ${{ runner.os }}-v${{ secrets.CACHE_VERSION }}-${{ hashFiles('package-lock.json') }} + - name: Install dependencies + if: steps.cache.outputs.cache-hit != 'true' + run: npm ci + - run: npm run test-integration + e2e-mac: if: github.repository_owner == 'facebook' runs-on: macos-latest diff --git a/.prettierignore b/.prettierignore index 97fbe5bec01..e1b7e5bcd05 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,14 +1,16 @@ # NOTE: In general this should be kept in sync with .eslintignore -packages/**/dist/** -packages/**/build/** -packages/**/npm/** -packages/**/config/** +**/dist/** +**/build/** +**/npm/** +**/__tests__/integration/fixtures/** +**/config/** packages/**/.wxt/** packages/playwright packages/playwright-core packages/**/vite.config.js packages/**/vite.prod.config.js +packages/lexical-website/docs/api **/*.md **/*.js.flow **/node_modules diff --git a/examples/react-rich/src/plugins/ToolbarPlugin.tsx b/examples/react-rich/src/plugins/ToolbarPlugin.tsx index 1ace522d99f..264f4815997 100644 --- a/examples/react-rich/src/plugins/ToolbarPlugin.tsx +++ b/examples/react-rich/src/plugins/ToolbarPlugin.tsx @@ -57,7 +57,7 @@ export default function ToolbarPlugin() { }), editor.registerCommand( SELECTION_CHANGE_COMMAND, - (_payload, newEditor) => { + (_payload, _newEditor) => { $updateToolbar(); return false; }, diff --git a/examples/react-rich/tsconfig.json b/examples/react-rich/tsconfig.json index 49071185005..9645d952e73 100644 --- a/examples/react-rich/tsconfig.json +++ b/examples/react-rich/tsconfig.json @@ -16,7 +16,8 @@ /* Linting */ "strict": true, - "noUnusedLocals": true, + /* This is incompatible with monorepo eslint rules due to unnecessary React imports */ + "noUnusedLocals": false, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true }, diff --git a/jest.config.js b/jest.config.js index 2aa0744776b..7e89c93955d 100644 --- a/jest.config.js +++ b/jest.config.js @@ -135,6 +135,12 @@ module.exports = { '^.+\\.tsx$': 'ts-jest', }, }, + { + ...common, + displayName: 'integration', + globalSetup: './scripts/__tests__/integration/setup.js', + testMatch: ['**/scripts/__tests__/integration/**/*.test.js'], + }, { ...common, displayName: 'e2e', diff --git a/package-lock.json b/package-lock.json index ccaaf0a348c..aa0e8f3e912 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,6 @@ "packages/*" ], "dependencies": { - "@types/katex": "^0.14.0", "yjs": "^13.5.42" }, "devDependencies": { @@ -30,8 +29,10 @@ "@rollup/plugin-replace": "^3.0.0", "@rollup/plugin-terser": "^0.4.4", "@size-limit/preset-big-lib": "^11.1.2", + "@types/child-process-promise": "^2.2.6", "@types/jest": "^29.4.0", "@types/jsdom": "^21.1.6", + "@types/katex": "^0.16.7", "@types/node": "^17.0.31", "@types/prismjs": "^1.26.0", "@types/react": "^18.0.8", @@ -62,10 +63,11 @@ "eslint-plugin-sort-keys-fix": "^1.1.2", "flow-bin": "^0.226.0", "fs-extra": "^10.0.0", - "gen-flow-files": "^0.4.11", "glob": "^7.2.0", "google-closure-compiler": "^20220202.0.0", "gzip-size": "^6.0.0", + "hermes-parser": "^0.20.1", + "hermes-transform": "^0.20.1", "husky": "^7.0.1", "jest": "^29.4.0", "jest-environment-jsdom": "^29.4.0", @@ -74,13 +76,14 @@ "minimist": "^1.2.5", "npm-run-all": "^4.1.5", "prettier": "^2.3.2", + "prettier-plugin-hermes-parser": "^0.20.1", "prettier-plugin-organize-attributes": "^0.0.5", "prettier-plugin-tailwindcss": "^0.4.1", "react-test-renderer": "^17.0.2", "rollup": "^2.75.5", "size-limit": "^11.1.2", "tmp": "^0.2.1", - "ts-jest": "^29.0.0", + "ts-jest": "^29.1.2", "ts-node": "^10.9.1", "typedoc": "^0.25.12", "typescript": "5.1.6" @@ -999,22 +1002,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-throw-expressions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-throw-expressions/-/plugin-proposal-throw-expressions-7.18.6.tgz", - "integrity": "sha512-WHOrJyhGoGrdtW480L79cF7Iq/gZDZ/z6OqK7mVyFR5I37dTpog/wNgb6hmaM3HYZtULEJl++7VaMWkNZsOcHg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-throw-expressions": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-async-generators": { "version": "7.8.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", @@ -1244,21 +1231,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-throw-expressions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-throw-expressions/-/plugin-syntax-throw-expressions-7.18.6.tgz", - "integrity": "sha512-rp1CqEZXGv1z1YZ3qYffBH3rhnOxrTwQG8fh2yqulTurwv9zu3Gthfd+niZBLSOi1rY6146TgF+JmVeDXaX4TQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-top-level-await": { "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", @@ -7714,6 +7686,15 @@ "@types/node": "*" } }, + "node_modules/@types/child-process-promise": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@types/child-process-promise/-/child-process-promise-2.2.6.tgz", + "integrity": "sha512-g0pOHijr6Trug43D2bV0PLSIsSHa/xHEES2HeX5BAlduq1vW0nZcq27Zeud5lgmNB+kPYYVqiMap32EHGTco/w==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/connect": { "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", @@ -7915,9 +7896,10 @@ "dev": true }, "node_modules/@types/katex": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.14.0.tgz", - "integrity": "sha512-+2FW2CcT0K3P+JMR8YG846bmDwplKUTsWgT2ENwdQ1UdVfRk3GQrh6Mi4sTopy30gI8Uau5CEqHTDZ6YvWIUPA==" + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.7.tgz", + "integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==", + "dev": true }, "node_modules/@types/lodash": { "version": "4.14.182", @@ -11750,15 +11732,6 @@ } } }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/decimal.js": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", @@ -14694,6 +14667,12 @@ "node": ">=0.10.0" } }, + "node_modules/flow-enums-runtime": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz", + "integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==", + "dev": true + }, "node_modules/focus-lock": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/focus-lock/-/focus-lock-1.3.5.tgz", @@ -15146,196 +15125,6 @@ "which": "bin/which" } }, - "node_modules/gen-flow-files": { - "version": "0.4.11", - "resolved": "https://registry.npmjs.org/gen-flow-files/-/gen-flow-files-0.4.11.tgz", - "integrity": "sha512-88WjoL6gaxTaJtSal6Nsw9LerVKRVjQEAJy330VOmCS1k+lu2h1HJEJyU9LhviaphZBNG088j6JigA6YiP0xGQ==", - "dev": true, - "dependencies": { - "@babel/core": "^7.7.5", - "@babel/generator": "^7.7.4", - "@babel/parser": "^7.7.5", - "@babel/plugin-proposal-throw-expressions": "^7.7.4", - "@babel/plugin-syntax-flow": "^7.7.4", - "@babel/traverse": "^7.7.4", - "@babel/types": "^7.7.4", - "glob": "^7.1.6", - "mkdirp": "^0.5.1", - "yargs": "^15.0.2" - }, - "bin": { - "gen-flow-files": "bin/gen-flow-files.js" - } - }, - "node_modules/gen-flow-files/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/gen-flow-files/node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/gen-flow-files/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/gen-flow-files/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/gen-flow-files/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/gen-flow-files/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/gen-flow-files/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/gen-flow-files/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/gen-flow-files/node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/gen-flow-files/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/gen-flow-files/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/gen-flow-files/node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true - }, - "node_modules/gen-flow-files/node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/gen-flow-files/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -16074,6 +15863,50 @@ "he": "bin/he" } }, + "node_modules/hermes-eslint": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/hermes-eslint/-/hermes-eslint-0.20.1.tgz", + "integrity": "sha512-EhdvFV6RkPIJvbqN8oqFZO1oF4NlPWMjhMjCWkUJX1YL1MZMfkF7nSdx6RKTq6xK17yo+Bgv88L21xuH9GtRpw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "hermes-estree": "0.20.1", + "hermes-parser": "0.20.1" + } + }, + "node_modules/hermes-estree": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.20.1.tgz", + "integrity": "sha512-SQpZK4BzR48kuOg0v4pb3EAGNclzIlqMj3Opu/mu7bbAoFw6oig6cEt/RAi0zTFW/iW6Iz9X9ggGuZTAZ/yZHg==", + "dev": true + }, + "node_modules/hermes-parser": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.20.1.tgz", + "integrity": "sha512-BL5P83cwCogI8D7rrDCgsFY0tdYUtmFP9XaXtl2IQjC+2Xo+4okjfXintlTxcIwl4qeGddEl28Z11kbVIw0aNA==", + "dev": true, + "dependencies": { + "hermes-estree": "0.20.1" + } + }, + "node_modules/hermes-transform": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/hermes-transform/-/hermes-transform-0.20.1.tgz", + "integrity": "sha512-gpetyzAQvuLXVWIk8/I2A/ei/5+o8eT+QuSGd8FcWpXoYxVkYjVKLVNE9xKLsEkk2wQ1tXODY5OeOZoaz9jL7Q==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.16.0", + "esquery": "^1.4.0", + "flow-enums-runtime": "^0.0.6", + "hermes-eslint": "0.20.1", + "hermes-estree": "0.20.1", + "hermes-parser": "0.20.1" + }, + "peerDependencies": { + "prettier": "^3.0.0 || ^2.7.1", + "prettier-plugin-hermes-parser": "0.20.1" + } + }, "node_modules/highlight.js": { "version": "10.7.3", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", @@ -22675,6 +22508,7 @@ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, + "optional": true, "dependencies": { "minimist": "^1.2.6" }, @@ -25096,6 +24930,20 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/prettier-plugin-hermes-parser": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/prettier-plugin-hermes-parser/-/prettier-plugin-hermes-parser-0.20.1.tgz", + "integrity": "sha512-T6dfa1++ckTxd3MbLxS6sTv1T3yvTu1drahNt3g34hyCzSwYTKTByocLyhd1A9j9uCUlIPD+ogum7+i1h3+CEw==", + "dev": true, + "dependencies": { + "hermes-estree": "0.20.1", + "hermes-parser": "0.20.1", + "prettier-plugin-hermes-parser": "0.20.1" + }, + "peerDependencies": { + "prettier": "^3.0.0 || ^2.7.1" + } + }, "node_modules/prettier-plugin-organize-attributes": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/prettier-plugin-organize-attributes/-/prettier-plugin-organize-attributes-0.0.5.tgz", @@ -27060,12 +26908,6 @@ "node": "*" } }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, "node_modules/requireindex": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz", @@ -27759,12 +27601,6 @@ "node": ">= 0.8.0" } }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true - }, "node_modules/set-value": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/set-value/-/set-value-4.1.0.tgz", @@ -29372,9 +29208,9 @@ "dev": true }, "node_modules/ts-jest": { - "version": "29.0.5", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.0.5.tgz", - "integrity": "sha512-PL3UciSgIpQ7f6XjVOmbi96vmDHUqAyqDr8YxzopDqX3kfgYtX1cuNeBjP+L9sFXi6nzsGGA6R3fP3DDDJyrxA==", + "version": "29.1.2", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.2.tgz", + "integrity": "sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==", "dev": true, "dependencies": { "bs-logger": "0.x", @@ -29383,21 +29219,21 @@ "json5": "^2.2.3", "lodash.memoize": "4.x", "make-error": "1.x", - "semver": "7.x", + "semver": "^7.5.3", "yargs-parser": "^21.0.1" }, "bin": { "ts-jest": "cli.js" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^16.10.0 || ^18.0.0 || >=20.0.0" }, "peerDependencies": { "@babel/core": ">=7.0.0-beta.0 <8", "@jest/types": "^29.0.0", "babel-jest": "^29.0.0", "jest": "^29.0.0", - "typescript": ">=4.3" + "typescript": ">=4.3 <6" }, "peerDependenciesMeta": { "@babel/core": { @@ -31359,12 +31195,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", - "dev": true - }, "node_modules/widest-line": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", @@ -34272,16 +34102,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==" }, - "@babel/plugin-proposal-throw-expressions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-throw-expressions/-/plugin-proposal-throw-expressions-7.18.6.tgz", - "integrity": "sha512-WHOrJyhGoGrdtW480L79cF7Iq/gZDZ/z6OqK7mVyFR5I37dTpog/wNgb6hmaM3HYZtULEJl++7VaMWkNZsOcHg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-throw-expressions": "^7.18.6" - } - }, "@babel/plugin-syntax-async-generators": { "version": "7.8.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", @@ -34436,15 +34256,6 @@ "@babel/helper-plugin-utils": "^7.14.5" } }, - "@babel/plugin-syntax-throw-expressions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-throw-expressions/-/plugin-syntax-throw-expressions-7.18.6.tgz", - "integrity": "sha512-rp1CqEZXGv1z1YZ3qYffBH3rhnOxrTwQG8fh2yqulTurwv9zu3Gthfd+niZBLSOi1rY6146TgF+JmVeDXaX4TQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, "@babel/plugin-syntax-top-level-await": { "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", @@ -39236,6 +39047,15 @@ "@types/node": "*" } }, + "@types/child-process-promise": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@types/child-process-promise/-/child-process-promise-2.2.6.tgz", + "integrity": "sha512-g0pOHijr6Trug43D2bV0PLSIsSHa/xHEES2HeX5BAlduq1vW0nZcq27Zeud5lgmNB+kPYYVqiMap32EHGTco/w==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/connect": { "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", @@ -39437,9 +39257,10 @@ "dev": true }, "@types/katex": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.14.0.tgz", - "integrity": "sha512-+2FW2CcT0K3P+JMR8YG846bmDwplKUTsWgT2ENwdQ1UdVfRk3GQrh6Mi4sTopy30gI8Uau5CEqHTDZ6YvWIUPA==" + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.7.tgz", + "integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==", + "dev": true }, "@types/lodash": { "version": "4.14.182", @@ -42277,12 +42098,6 @@ "ms": "2.1.2" } }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true - }, "decimal.js": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", @@ -44339,6 +44154,12 @@ "integrity": "sha512-q8hXSRhZ+I14jS0KGDDsPYCvPufvBexk6nJXSOsSP6DgCuXbvCOByWhsXRAjPtmXKmO8v9RKSJm1kRaWaf0fZw==", "dev": true }, + "flow-enums-runtime": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz", + "integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==", + "dev": true + }, "focus-lock": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/focus-lock/-/focus-lock-1.3.5.tgz", @@ -44656,156 +44477,6 @@ } } }, - "gen-flow-files": { - "version": "0.4.11", - "resolved": "https://registry.npmjs.org/gen-flow-files/-/gen-flow-files-0.4.11.tgz", - "integrity": "sha512-88WjoL6gaxTaJtSal6Nsw9LerVKRVjQEAJy330VOmCS1k+lu2h1HJEJyU9LhviaphZBNG088j6JigA6YiP0xGQ==", - "dev": true, - "requires": { - "@babel/core": "^7.7.5", - "@babel/generator": "^7.7.4", - "@babel/parser": "^7.7.5", - "@babel/plugin-proposal-throw-expressions": "^7.7.4", - "@babel/plugin-syntax-flow": "^7.7.4", - "@babel/traverse": "^7.7.4", - "@babel/types": "^7.7.4", - "glob": "^7.1.6", - "mkdirp": "^0.5.1", - "yargs": "^15.0.2" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true - }, - "yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, - "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - } - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } - } - }, "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -45353,6 +45024,46 @@ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" }, + "hermes-eslint": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/hermes-eslint/-/hermes-eslint-0.20.1.tgz", + "integrity": "sha512-EhdvFV6RkPIJvbqN8oqFZO1oF4NlPWMjhMjCWkUJX1YL1MZMfkF7nSdx6RKTq6xK17yo+Bgv88L21xuH9GtRpw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "hermes-estree": "0.20.1", + "hermes-parser": "0.20.1" + } + }, + "hermes-estree": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.20.1.tgz", + "integrity": "sha512-SQpZK4BzR48kuOg0v4pb3EAGNclzIlqMj3Opu/mu7bbAoFw6oig6cEt/RAi0zTFW/iW6Iz9X9ggGuZTAZ/yZHg==", + "dev": true + }, + "hermes-parser": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.20.1.tgz", + "integrity": "sha512-BL5P83cwCogI8D7rrDCgsFY0tdYUtmFP9XaXtl2IQjC+2Xo+4okjfXintlTxcIwl4qeGddEl28Z11kbVIw0aNA==", + "dev": true, + "requires": { + "hermes-estree": "0.20.1" + } + }, + "hermes-transform": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/hermes-transform/-/hermes-transform-0.20.1.tgz", + "integrity": "sha512-gpetyzAQvuLXVWIk8/I2A/ei/5+o8eT+QuSGd8FcWpXoYxVkYjVKLVNE9xKLsEkk2wQ1tXODY5OeOZoaz9jL7Q==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.16.0", + "esquery": "^1.4.0", + "flow-enums-runtime": "^0.0.6", + "hermes-eslint": "0.20.1", + "hermes-estree": "0.20.1", + "hermes-parser": "0.20.1" + } + }, "highlight.js": { "version": "10.7.3", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", @@ -49879,6 +49590,7 @@ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, + "optional": true, "requires": { "minimist": "^1.2.6" } @@ -51553,6 +51265,17 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==" }, + "prettier-plugin-hermes-parser": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/prettier-plugin-hermes-parser/-/prettier-plugin-hermes-parser-0.20.1.tgz", + "integrity": "sha512-T6dfa1++ckTxd3MbLxS6sTv1T3yvTu1drahNt3g34hyCzSwYTKTByocLyhd1A9j9uCUlIPD+ogum7+i1h3+CEw==", + "dev": true, + "requires": { + "hermes-estree": "0.20.1", + "hermes-parser": "0.20.1", + "prettier-plugin-hermes-parser": "0.20.1" + } + }, "prettier-plugin-organize-attributes": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/prettier-plugin-organize-attributes/-/prettier-plugin-organize-attributes-0.0.5.tgz", @@ -52946,12 +52669,6 @@ "resolved": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz", "integrity": "sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A==" }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, "requireindex": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz", @@ -53493,12 +53210,6 @@ "send": "0.18.0" } }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true - }, "set-value": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/set-value/-/set-value-4.1.0.tgz", @@ -54722,9 +54433,9 @@ "dev": true }, "ts-jest": { - "version": "29.0.5", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.0.5.tgz", - "integrity": "sha512-PL3UciSgIpQ7f6XjVOmbi96vmDHUqAyqDr8YxzopDqX3kfgYtX1cuNeBjP+L9sFXi6nzsGGA6R3fP3DDDJyrxA==", + "version": "29.1.2", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.2.tgz", + "integrity": "sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==", "dev": true, "requires": { "bs-logger": "0.x", @@ -54733,7 +54444,7 @@ "json5": "^2.2.3", "lodash.memoize": "4.x", "make-error": "1.x", - "semver": "7.x", + "semver": "^7.5.3", "yargs-parser": "^21.0.1" } }, @@ -56112,12 +55823,6 @@ "is-symbol": "^1.0.3" } }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", - "dev": true - }, "widest-line": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", diff --git a/package.json b/package.json index c188d416ac6..7ac67ba7eb9 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "validation": "npx ts-node --cwdMode packages/lexical-playground/src/server/validation.ts", "test-unit": "jest --selectProjects unit --testPathPattern", "test-unit-watch": "npm run test-unit -- --watch --coverage=''", + "test-integration": "jest --selectProjects integration --testPathPattern", "test-e2e-chromium": "cross-env E2E_BROWSER=chromium playwright test --project=\"chromium\"", "test-e2e-firefox": "cross-env E2E_BROWSER=firefox playwright test --project=\"firefox\"", "test-e2e-webkit": "cross-env E2E_BROWSER=webkit playwright test --project=\"webkit\"", @@ -96,7 +97,11 @@ "changelog": "func() { git --no-pager log --oneline ${1}...HEAD --pretty=format:\"- %s %an\"; }; func", "increment-version": "node ./scripts/npm/increment-version", "update-changelog": "node ./scripts/npm/update-changelog", + "update-docs": "node ./scripts/update-docs", "update-version": "node ./scripts/updateVersion", + "update-tsconfig": "node ./scripts/update-tsconfig", + "update-flowconfig": "node ./scripts/update-flowconfig", + "update-packages": "npm run update-version && npm run update-tsconfig && npm run update-flowconfig && npm run update-docs", "postversion": "git checkout -b ${npm_package_version}__release && npm install && npm run update-version && npm run update-changelog && git add -A && git commit -m v${npm_package_version} && git tag -a v${npm_package_version} -m v${npm_package_version}", "publish-extension": "npm run zip -w @lexical/devtools && npm run publish -w @lexical/devtools", "release": "npm run prepare-release && node ./scripts/npm/release.js", @@ -117,8 +122,10 @@ "@rollup/plugin-replace": "^3.0.0", "@rollup/plugin-terser": "^0.4.4", "@size-limit/preset-big-lib": "^11.1.2", + "@types/child-process-promise": "^2.2.6", "@types/jest": "^29.4.0", "@types/jsdom": "^21.1.6", + "@types/katex": "^0.16.7", "@types/node": "^17.0.31", "@types/prismjs": "^1.26.0", "@types/react": "^18.0.8", @@ -149,10 +156,11 @@ "eslint-plugin-sort-keys-fix": "^1.1.2", "flow-bin": "^0.226.0", "fs-extra": "^10.0.0", - "gen-flow-files": "^0.4.11", "glob": "^7.2.0", "google-closure-compiler": "^20220202.0.0", "gzip-size": "^6.0.0", + "hermes-parser": "^0.20.1", + "hermes-transform": "^0.20.1", "husky": "^7.0.1", "jest": "^29.4.0", "jest-environment-jsdom": "^29.4.0", @@ -161,19 +169,19 @@ "minimist": "^1.2.5", "npm-run-all": "^4.1.5", "prettier": "^2.3.2", + "prettier-plugin-hermes-parser": "^0.20.1", "prettier-plugin-organize-attributes": "^0.0.5", "prettier-plugin-tailwindcss": "^0.4.1", "react-test-renderer": "^17.0.2", "rollup": "^2.75.5", "size-limit": "^11.1.2", "tmp": "^0.2.1", - "ts-jest": "^29.0.0", + "ts-jest": "^29.1.2", "ts-node": "^10.9.1", "typedoc": "^0.25.12", "typescript": "5.1.6" }, "dependencies": { - "@types/katex": "^0.14.0", "yjs": "^13.5.42" } } diff --git a/packages/lexical-devtools/tsconfig.json b/packages/lexical-devtools/tsconfig.json index 919db6be896..51d7b7b5b56 100644 --- a/packages/lexical-devtools/tsconfig.json +++ b/packages/lexical-devtools/tsconfig.json @@ -10,6 +10,12 @@ "lexical": ["../lexical/src/"], "lexicalOriginal": ["../lexical/src/"], "@lexical/devtools-core": ["../lexical-devtools-core/src/"], + "@lexical/html": ["../lexical-html/src/"], + "@lexical/link": ["../lexical-link/src/"], + "@lexical/mark": ["../lexical-mark/src/"], + "@lexical/selection": ["../lexical-selection/src/"], + "@lexical/table": ["../lexical-table/src/"], + "@lexical/utils": ["../lexical-utils/src/"], "shared/canUseDOM": ["../shared/src/canUseDOM.ts"], "shared/normalizeClassNames": ["../shared/src/normalizeClassNames.ts"], "shared/invariant": ["../shared/src/invariant.ts"], diff --git a/scripts/__tests__/integration/prepare-release.test.js b/scripts/__tests__/integration/prepare-release.test.js new file mode 100644 index 00000000000..4e7727da2a2 --- /dev/null +++ b/scripts/__tests__/integration/prepare-release.test.js @@ -0,0 +1,31 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ +// @ts-check +'use strict'; + +const fs = require('fs-extra'); +const glob = require('glob'); +const {packagesManager} = require('../../shared/packagesManager'); +const path = require('node:path'); +const {describeExample} = require('./utils'); + +describe('prepare-release tests', () => { + for (const pkg of packagesManager.getPublicPackages()) { + describe(pkg.getNpmName(), () => { + const tgzPath = path.join( + 'npm', + `${pkg.getDirectoryName()}-${pkg.packageJson.version}.tgz`, + ); + test(`${tgzPath} exists`, () => + expect(fs.existsSync(tgzPath)).toBe(true)); + }); + } +}); +['examples', 'scripts/__tests__/integration/fixtures'] + .flatMap((packagesDir) => glob.sync(`${packagesDir}/*/package.json`)) + .forEach((exampleJsonPath) => describeExample(exampleJsonPath)); diff --git a/scripts/__tests__/integration/setup.js b/scripts/__tests__/integration/setup.js new file mode 100644 index 00000000000..55bdf1925ac --- /dev/null +++ b/scripts/__tests__/integration/setup.js @@ -0,0 +1,45 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ +// @ts-check +'use strict'; + +const path = require('node:path'); +const fs = require('fs-extra'); +const {exec} = require('child-process-promise'); +const {packagesManager} = require('../../shared/packagesManager'); +const {version} = require('../../shared/readMonorepoPackageJson')(); + +/** + * @param {import('@jest/types').Config.GlobalConfig} globalConfig + * @param {import('@jest/types').Config.ProjectConfig} projectConfig + */ +module.exports = async function (globalConfig, projectConfig) { + const needsBuild = packagesManager + .getPublicPackages() + .some( + (pkg) => + !fs.existsSync( + path.resolve(`./npm/${pkg.getDirectoryName()}-${version}.tgz`), + ), + ); + if (!needsBuild) { + return; + } + await exec('npm run prepare-release'); + const packDest = path.resolve('./npm'); + fs.mkdirpSync(packDest); + for (const pkg of packagesManager.getPublicPackages()) { + const cwd = process.cwd(); + try { + process.chdir(pkg.resolve('npm')); + await exec(`npm pack --pack-destination '${packDest}'`); + } finally { + process.chdir(cwd); + } + } +}; diff --git a/scripts/__tests__/integration/utils.js b/scripts/__tests__/integration/utils.js new file mode 100644 index 00000000000..4e3f0c0d9c5 --- /dev/null +++ b/scripts/__tests__/integration/utils.js @@ -0,0 +1,156 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ +// @ts-check +'use strict'; + +const {exec} = require('child-process-promise'); +const {packagesManager} = require('../../shared/packagesManager'); +const fs = require('fs-extra'); +const path = require('node:path'); + +const monorepoVersion = require('../../shared/readMonorepoPackageJson')() + .version; + +const LONG_TIMEOUT = 240 * 1000; + +/** + * @function + * @template T + * @param {string} dir + * @param {() => Promise | T} cb + * @returns {Promise} + */ +async function withCwd(dir, cb) { + const cwd = process.cwd(); + try { + process.chdir(dir); + return await cb(); + } finally { + process.chdir(cwd); + } +} +exports.withCwd = withCwd; + +/** + * @param {string} cmd + * @returns {Promise} + */ +function expectSuccessfulExec(cmd) { + // playwright detects jest, so we clear this env var while running subcommands + const env = Object.fromEntries( + Object.entries(process.env).filter(([k]) => k !== 'JEST_WORKER_ID'), + ); + return exec(cmd, {capture: ['stdout', 'stderr'], env}).catch((err) => { + expect( + Object.fromEntries( + ['code', 'stdout', 'stderr'].map((prop) => [prop, err[prop]]), + ), + ).toBe(null); + throw err; + }); +} +exports.expectSuccessfulExec = expectSuccessfulExec; + +/** + * @typedef {Object} ExampleContext + * @property {string} packageJsonPath + * @property {string} exampleDir + * @property {Record} packageJson + */ + +/** + * @param {ctx} ExampleContext + * @returns {Promise>} The installed monorepo dependency map + */ +async function buildExample({packageJson, exampleDir}) { + let hasPlaywright = false; + const depsMap = packagesManager.computedMonorepoDependencyMap( + ['dependencies', 'devDependencies', 'peerDependencies'].flatMap( + (depType) => { + const deps = packageJson[depType] || {}; + hasPlaywright ||= '@playwright/test' in deps; + return Object.keys(deps); + }, + ), + ); + const installDeps = [...depsMap.values()].map((pkg) => + path.resolve('npm', `${pkg.getDirectoryName()}-${monorepoVersion}.tgz`), + ); + if (installDeps.length === 0) { + throw new Error(`No lexical dependencies detected: ${exampleDir}`); + } + ['node_modules', 'dist', 'build', '.next', '.svelte-kit'].forEach( + (cleanDir) => fs.removeSync(path.resolve(exampleDir, cleanDir)), + ); + await withCwd(exampleDir, async () => { + await exec( + `npm install --no-save --no-package-lock ${installDeps + .map((fn) => `'${fn}'`) + .join(' ')}`, + ); + await expectSuccessfulExec('npm run build'); + if (hasPlaywright) { + await exec('npx playwright install'); + } + }); + return depsMap; +} + +/** + * Build the example project with prerelease lexical artifacts + * + * @param {string} packgeJsonPath + * @param {undefined | (ctx: ExampleContext) => void} [bodyFun=undefined] + */ +function describeExample(packageJsonPath, bodyFun = undefined) { + const packageJson = fs.readJsonSync(packageJsonPath); + const exampleDir = path.dirname(packageJsonPath); + /** @type {ExampleContext} */ + const ctx = {exampleDir, packageJson, packageJsonPath}; + describe(exampleDir, () => { + /** @type {PackageMetadata[]} */ + const deps = []; + beforeAll(async () => { + deps.push(...(await buildExample(ctx)).values()); + }, LONG_TIMEOUT); + test('install & build succeeded', () => { + expect(true).toBe(true); + }); + test(`installed lexical ${monorepoVersion}`, () => { + const packageNames = deps.map((pkg) => pkg.getNpmName()); + expect(packageNames).toContain('lexical'); + for (const pkg of deps) { + const installedPath = path.join( + exampleDir, + 'node_modules', + pkg.getNpmName(), + ); + expect({[installedPath]: fs.existsSync(installedPath)}).toEqual({ + [installedPath]: true, + }); + expect( + fs.readJsonSync(path.join(installedPath, 'package.json')), + ).toMatchObject({name: pkg.getNpmName(), version: monorepoVersion}); + } + }); + if (packageJson.scripts.test) { + test( + 'tests pass', + async () => { + await withCwd(exampleDir, () => expectSuccessfulExec('npm run test')); + }, + LONG_TIMEOUT, + ); + } + if (bodyFun) { + bodyFun(ctx); + } + }); +} + +exports.describeExample = describeExample; diff --git a/scripts/__tests__/unit/build.test.js b/scripts/__tests__/unit/build.test.js new file mode 100644 index 00000000000..6e551995474 --- /dev/null +++ b/scripts/__tests__/unit/build.test.js @@ -0,0 +1,117 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ +// @ts-check +'use strict'; + +const fs = require('fs-extra'); +const glob = require('glob'); +const path = require('node:path'); +const {packagesManager} = require('../../shared/packagesManager'); + +const monorepoPackageJson = require('../../shared/readMonorepoPackageJson')(); + +const publicNpmNames = new Set( + packagesManager.getPublicPackages().map((pkg) => pkg.getNpmName()), +); + +describe('public package.json audits (`npm run update-version` to fix most issues)', () => { + packagesManager.getPublicPackages().forEach((pkg) => { + const npmName = pkg.getNpmName(); + const packageJson = pkg.packageJson; + describe(npmName, () => { + const sourceFiles = fs + .readdirSync(pkg.resolve('src')) + .filter((str) => /\.tsx?/.test(str)) + .map((str) => str.replace(/\.tsx?$/, '', str)) + .sort(); + const exportedModules = pkg.getExportedNpmModuleNames().sort(); + const {dependencies = {}, peerDependencies = {}} = packageJson; + if (packageJson.main) { + it('should only export the main module with main set', () => { + expect(exportedModules).toEqual([npmName]); + }); + } + it('has *.flow types', () => { + expect(glob.sync(pkg.resolve('flow', '*.flow'))).not.toEqual([]); + }); + it('uses the expected directory/npm naming convention', () => { + expect(npmName.replace(/^@/, '').replace('/', '-')).toBe( + pkg.getDirectoryName(), + ); + }); + it('matches monorepo version', () => { + expect(packageJson.version).toBe(monorepoPackageJson.version); + }); + it('must not have a direct dependency on react or react-dom', () => { + expect(dependencies).not.toContain('react'); + expect(dependencies).not.toContain('react-dom'); + }); + it('must not have monorepo peerDependencies', () => { + expect( + Object.keys(peerDependencies).filter((dep) => + publicNpmNames.has(dep), + ), + ).toEqual([]); + }); + it('monorepo dependencies must use the exact monorepo version', () => { + Object.entries(dependencies).forEach(([dep, version]) => { + if (publicNpmNames.has(dep)) { + expect([dep, version]).toEqual([dep, monorepoPackageJson.version]); + } + }); + }); + it('must export at least one module', () => { + expect(exportedModules.length).toBeGreaterThanOrEqual(1); + }); + test.each(exportedModules)( + `should have a source file for exported module %s`, + (exportedModule) => { + expect(sourceFiles).toContain( + exportedModule.slice(npmName.length + 1) || 'index', + ); + }, + ); + if (!sourceFiles.includes('index')) { + it('must not export a top-level module without an index.tsx?', () => { + expect(exportedModules).not.toContain(npmName); + }); + test.each(sourceFiles)( + `%s.tsx? must have an exported module`, + (sourceFile) => { + expect(exportedModules).toContain(`${npmName}/${sourceFile}`); + }, + ); + } + }); + }); +}); + +describe('documentation audits (`npm run update-docs` to fix most issues)', () => { + const webPkg = packagesManager.getPackageByDirectoryName('lexical-website'); + packagesManager.getPublicPackages().forEach((pkg) => { + const npmName = pkg.getNpmName(); + describe(npmName, () => { + const root = pkg.resolve('..', '..'); + [ + pkg.resolve('README.md'), + webPkg.resolve('docs', 'packages', `${pkg.getDirectoryName()}.md`), + ].forEach((docPath) => { + describe(path.relative(root, docPath), () => { + it('exists', () => expect(fs.existsSync(docPath)).toBe(true)); + if (path.basename(docPath) === 'README.md') { + it('does not have the TODO description', () => { + expect(fs.readFileSync(docPath, 'utf8')).not.toContain( + 'TODO: This package needs a description!', + ); + }); + } + }); + }); + }); + }); +}); diff --git a/scripts/npm/increment-version.js b/scripts/npm/increment-version.js index aea4a940d46..2e802a510d9 100644 --- a/scripts/npm/increment-version.js +++ b/scripts/npm/increment-version.js @@ -12,6 +12,7 @@ const {exec} = require('child-process-promise'); const argv = require('minimist')(process.argv.slice(2)); + const increment = argv.i; const validIncrements = new Set(['minor', 'patch', 'prerelease']); if (!validIncrements.has(increment)) { @@ -19,11 +20,11 @@ if (!validIncrements.has(increment)) { process.exit(1); } -async function incrementVersion(increment) { +async function incrementVersion() { const preId = increment === 'prerelease' ? '--preid next' : ''; const workspaces = ''; const command = `npm --no-git-tag-version version ${increment} --include-workspace-root true ${preId} ${workspaces}`; await exec(command); } -incrementVersion(increment); +incrementVersion(); diff --git a/scripts/shared/PackageMetadata.js b/scripts/shared/PackageMetadata.js new file mode 100644 index 00000000000..fcc9b0861ff --- /dev/null +++ b/scripts/shared/PackageMetadata.js @@ -0,0 +1,210 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ +// @ts-check +'use strict'; + +const path = require('node:path'); +const fs = require('fs-extra'); +const npmToWwwName = require('../www/npmToWwwName'); + +/** + * @typedef {Object} ModuleBuildDefinition + * @property {string} outputFileName + * @property {string} sourceFileName + */ + +/** + * @typedef {Object} PackageBuildDefinition + * @property {Array} modules + * @property {string} name + * @property {string} outputPath + * @property {string} packageName + * @property {string} sourcePath + */ + +/** + * @typedef {Object} ModuleExportEntry + * @property {string} name + * @property {string} sourceFileName + */ + +/** + * @typedef {readonly [string, Record<'import'|'require', Object>]} NpmModuleExportEntry + */ + +/** + * + * @param {string} wwwName + * @returns {string} An easier to read name ('Lexical' -> 'Lexical Core', 'LexicalRichText' -> 'Lexical Rich Text') + */ +function readableName(wwwName) { + return wwwName === 'Lexical' + ? 'Lexical Core' + : wwwName.replace(/([A-Z])/g, ' $1').trim(); +} + +/** + * Metadata abstraction for a package.json file + */ +class PackageMetadata { + /** @type {string} the path to the package.json file */ + packageJsonPath; + /** @type {Record} the parsed package.json */ + packageJson; + + /** + * @param {string} packageJsonPath the path to the package.json file + */ + constructor(packageJsonPath) { + this.packageJsonPath = packageJsonPath; + this.packageJson = fs.readJsonSync(packageJsonPath); + } + + /** + * @param {...string} paths to resolve in this package's directory + * @returns {string} Resolve a path in this package's directory + */ + resolve(...paths) { + return path.resolve(path.dirname(this.packageJsonPath), ...paths); + } + + /** + * @returns {string} the directory name of the package, e.g. 'lexical-rich-text' + */ + getDirectoryName() { + return path.basename(path.dirname(this.packageJsonPath)); + } + + /** + * @returns {string} the npm name of the package, e.g. '@lexical/rich-text' + */ + getNpmName() { + return this.packageJson.name; + } + + /** + * @returns {boolean} whether the package is marked private (not published to npm) + */ + isPrivate() { + return !!this.packageJson.private; + } + + /** + * Get an array of (fully qualified) exported module names and their + * corresponding export map. Ignores the backwards compatibility '.js' + * exports and replaces /^.[/]?/ with the npm name of the package. + * + * E.g. [['lexical', {...}]] or [['@lexical/react/LexicalComposer', {...}] + * + * @returns {Array} + */ + getNormalizedNpmModuleExportEntries() { + // It doesn't make much sense to do this for private modules + if (this.isPrivate()) { + throw new Error('This should only be called on public packages'); + } + // All our packages should have exports if update-version has been run + if (!this.packageJson.exports) { + throw new Error( + 'This package should have exports, try `npm run update-version` first', + ); + } + /** @type {Array} */ + const entries = []; + for (const [key, value] of Object.entries(this.packageJson.exports)) { + if (key.endsWith('.js')) { + continue; + } + entries.push([`${this.getNpmName()}${key.replace(/^./, '')}`, value]); + } + return entries.sort((a, b) => a[0].localeCompare(b[0])); + } + + /** + * @returns {Array} the npm module names that this package exports + */ + getExportedNpmModuleNames() { + return this.getNormalizedNpmModuleExportEntries().map(([name]) => name); + } + + /** + * The entries of npm module names to their .tsx? source files + * + * @returns {Array} + */ + getExportedNpmModuleEntries() { + const npmName = this.getNpmName(); + return this.getExportedNpmModuleNames().map((name) => { + const outputFileName = npmToWwwName(name); + const sourceBaseName = name === npmName ? 'index' : outputFileName; + const sourceCandidates = ['.ts', '.tsx'].map( + (ext) => sourceBaseName + ext, + ); + const sourceFileName = sourceCandidates.find((fn) => + fs.existsSync(this.resolve('src', fn)), + ); + if (!sourceFileName) { + throw new Error( + `Could not find source file for ${name} at packages/${this.getDirectoryName()}/src/${ + sourceCandidates[0] + }?`, + ); + } + return {name, sourceFileName}; + }); + } + + /** + * The map of import module names to their .tsx? source files + * (for private modules such as shared) + * + * @returns {Array} + */ + getPrivateModuleEntries() { + const npmName = this.getNpmName(); + const entries = []; + for (const sourceFileName of fs.readdirSync(this.resolve('src'))) { + const m = /^([^.]+)\.tsx?$/.exec(sourceFileName); + if (m) { + entries.push({ + name: m[1] === 'index' ? npmName : `${npmName}/${m[1]}`, + sourceFileName, + }); + } + } + return entries.sort((a, b) => a.name.localeCompare(b.name)); + } + + /** + * @returns {PackageBuildDefinition} + */ + getPackageBuildDefinition() { + const npmName = this.getNpmName(); + return { + modules: this.getExportedNpmModuleEntries().map( + ({name, sourceFileName}) => ({ + outputFileName: npmToWwwName(name), + sourceFileName, + }), + ), + name: readableName(npmToWwwName(npmName)), + outputPath: this.resolve('dist/'), + packageName: this.getDirectoryName(), + sourcePath: this.resolve('src/'), + }; + } + + /** + * Writes this.packageJson back to this.packageJsonPath + */ + writeSync() { + fs.writeJsonSync(this.packageJsonPath, this.packageJson, {spaces: 2}); + } +} + +exports.PackageMetadata = PackageMetadata; diff --git a/scripts/shared/packagesManager.js b/scripts/shared/packagesManager.js new file mode 100644 index 00000000000..d70a23ead0a --- /dev/null +++ b/scripts/shared/packagesManager.js @@ -0,0 +1,133 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ +// @ts-check +'use strict'; + +const path = require('node:path'); +const glob = require('glob'); + +const {PackageMetadata} = require('./PackageMetadata'); + +/** + * + * @param {PackageMetadata} a + * @param {PackageMetadata} b + * @returns {number} + */ +function packageSort(a, b) { + return a.getDirectoryName().localeCompare(b.getDirectoryName()); +} + +/** Cache of all PackageMetadata for the packages directory */ +class PackagesManager { + /** @type {Array} */ + packages; + /** @type {Map} */ + packagesByNpmName = new Map(); + /** @type {Map} */ + packagesByDirectoryName = new Map(); + + /** + * @param {Array} packagePaths + */ + constructor(packagePaths) { + this.packages = packagePaths + .map((packagePath) => new PackageMetadata(packagePath)) + .sort(packageSort); + for (const pkg of this.packages) { + this.packagesByNpmName.set(pkg.getNpmName(), pkg); + this.packagesByDirectoryName.set(pkg.getDirectoryName(), pkg); + } + } + + /** + * Get the PackageMetadata for a package by its npm name. + * @param {string} name + * @returns {PackageMetadata} + */ + getPackageByNpmName(name) { + const pkg = this.packagesByNpmName.get(name); + if (!pkg) { + throw new Error(`Missing package with npm name '${name}'`); + } + return pkg; + } + + /** + * Get the PackageMetadata for a package by its npm name. + * @param {string} name + * @returns {PackageMetadata} + */ + getPackageByDirectoryName(name) { + const pkg = this.packagesByDirectoryName.get(name); + if (!pkg) { + throw new Error(`Missing package with directory name '${name}'`); + } + return pkg; + } + + /** + * Get the cached metadata for all packages in the packages directory, + * sorted by directory name. + * @returns {Array} + */ + getPackages() { + return this.packages; + } + + /** + * Get the cached metadata for packages in the packages directory + * where the private field is not set to true, sorted by directory + * name ('lexical' will come before 'lexical-*'). + * @returns {Array} + */ + getPublicPackages() { + return this.packages.filter((pkg) => !pkg.isPrivate()); + } + + /** + * Given an array of npm dependencies (may include non-Lexical names), + * return all required transitive monorepo dependencies to have those + * packages (in a topologically ordered Map). + * + * @param {Array} npmDependencies + * @returns {Map} + */ + computedMonorepoDependencyMap(npmDependencies) { + /** @type {Map} */ + const depsMap = new Map(); + const visited = new Set(); + /** @param {string[]} deps */ + const traverse = (deps) => { + for (const dep of deps) { + if (visited.has(dep)) { + continue; + } + visited.add(dep); + if (!depsMap.has(dep)) { + const pkg = this.packagesByNpmName.get(dep); + if (pkg) { + traverse(Object.keys(pkg.packageJson.dependencies || {})); + depsMap.set(dep, pkg); + } + } + } + }; + traverse(npmDependencies); + return depsMap; + } +} + +exports.packagesManager = new PackagesManager( + glob.sync( + path.resolve( + path.dirname(path.dirname(__dirname)), + 'packages/*/package.json', + ), + ), +); diff --git a/scripts/shared/readMonorepoPackageJson.js b/scripts/shared/readMonorepoPackageJson.js new file mode 100644 index 00000000000..b905b31eb22 --- /dev/null +++ b/scripts/shared/readMonorepoPackageJson.js @@ -0,0 +1,16 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ +// @ts-check +'use strict'; + +const fs = require('fs-extra'); +const path = require('node:path'); + +module.exports = function readMonorepoPackageJson() { + return fs.readJsonSync(path.resolve(__dirname, '../../package.json')); +}; diff --git a/scripts/update-docs.js b/scripts/update-docs.js new file mode 100644 index 00000000000..fbea3037d36 --- /dev/null +++ b/scripts/update-docs.js @@ -0,0 +1,79 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ +// @ts-check +'use strict'; + +const fs = require('fs-extra'); +const path = require('node:path'); +const {packagesManager} = require('./shared/packagesManager'); + +const webPkg = packagesManager.getPackageByDirectoryName('lexical-website'); + +function sidebarTemplate(npmName, readmePath) { + return ( + ` +--- +title: '' +sidebar_label: '${npmName}' +--- + +{@import ${readmePath}} +`.trim() + '\n' + ); +} + +function readmeTemplate(npmName, directoryName, description) { + const apiModuleName = directoryName.replace(/-/g, '_'); + return ( + ` + # \`${npmName}\` + +[![See API Documentation](https://lexical.dev/img/see-api-documentation.svg)](https://lexical.dev/docs/api/modules/${apiModuleName}) + +${description} +`.trim() + '\n' + ); +} + +function updateDocs() { + packagesManager.getPublicPackages().forEach((pkg) => { + const npmName = pkg.getNpmName(); + const directoryName = pkg.getDirectoryName(); + const root = pkg.resolve('..', '..'); + const readmePath = pkg.resolve('README.md'); + const sidebarPath = webPkg.resolve( + 'docs', + 'packages', + `${directoryName}.md`, + ); + if (!fs.existsSync(readmePath)) { + console.log(`Creating ${path.relative(root, readmePath)}`); + fs.writeFileSync( + readmePath, + readmeTemplate( + npmName, + directoryName, + pkg.packageJson.description || + 'TODO: This package needs a description!', + ), + ); + } + if (!fs.existsSync(sidebarPath)) { + console.log(`Creating ${path.relative(root, sidebarPath)}`); + fs.writeFileSync( + sidebarPath, + sidebarTemplate( + npmName, + path.relative(path.dirname(sidebarPath), readmePath), + ), + ); + } + }); +} + +updateDocs(); diff --git a/scripts/update-flowconfig.js b/scripts/update-flowconfig.js new file mode 100644 index 00000000000..9d2e9b872f9 --- /dev/null +++ b/scripts/update-flowconfig.js @@ -0,0 +1,99 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ +// @ts-check +'use strict'; + +const fs = require('fs-extra'); +const path = require('node:path'); +const {packagesManager} = require('./shared/packagesManager'); +const npmToWwwName = require('./www/npmToWwwName'); + +const headerTemplate = fs.readFileSync( + path.resolve(__dirname, 'www', 'headerTemplate.js'), + 'utf8', +); + +const BLOCK_REGEX = + /^([\s\S]+?\n;; \[generated-start update-flowconfig\]\n)([\s\S]*?)(;; \[generated-end update-flowconfig\][\s\S]+)$/; + +function flowTemplate(wwwName) { + return ( + headerTemplate.replace(/^( *\/)$/, '* @flow strict\n$1') + + ` +/** + * ${wwwName} + */` + + '\n' + ); +} + +/** + * @param {string} configContents + * @param {string} generatedCode + */ +function replaceBlock(configContents, generatedCode) { + const m = configContents.match(BLOCK_REGEX); + if (!m) { + throw new Error( + `update-flowconfig block not found in .flowconfig, expecting ';; [generated-start update-flowconfig]' followed by ';; [generated-end update-flowconfig]`, + ); + } + return `${m[1]}${generatedCode}${m[3]}`; +} + +function updateFlowconfig(flowconfigPath = './.flowconfig') { + const prevConfig = fs.readFileSync(flowconfigPath, 'utf8'); + const configDir = path.resolve(path.dirname(flowconfigPath)); + /** @type {Array} */ + const generatedBlock = []; + const emit = (moduleName, flowFilename) => + generatedBlock.push( + `module.name_mapper='^${moduleName}$' -> '${flowFilename}'\n`, + ); + for (const pkg of packagesManager.getPackages()) { + const resolveRelative = (...subPaths) => + '/' + + path.relative(configDir, pkg.resolve(...subPaths)).replace(/^(?!\.)/, ''); + if (pkg.isPrivate()) { + if (pkg.getDirectoryName() !== 'shared') { + continue; + } + if (process.env.TODO_DOES_WWW_NEED_SHARED_FILES === '1') { + // Do these even work? These .js files aren't on disk and `npm run flow` + // passes without them. Code is left in for demonstration purposes if + // this is needed for www. + for (const {name, sourceFileName} of pkg.getPrivateModuleEntries()) { + emit( + name, + resolveRelative('src', sourceFileName.replace(/\.tsx?$/, '.js')), + ); + } + } + } else { + for (const name of pkg.getExportedNpmModuleNames()) { + const wwwName = npmToWwwName(name); + const flowFile = `${wwwName}.js.flow`; + const resolvedFlowFile = resolveRelative('flow', flowFile); + emit(name, resolveRelative('flow', flowFile)); + const flowFilePath = pkg.resolve('flow', flowFile); + if (!fs.existsSync(flowFilePath)) { + console.log( + `Creating boilerplate ${resolvedFlowFile.replace(/^[^/]+\//, '')}`, + ); + fs.writeFileSync(flowFilePath, flowTemplate(wwwName)); + } + } + } + } + const nextConfig = replaceBlock(prevConfig, generatedBlock.join('')); + if (prevConfig !== nextConfig) { + fs.writeFileSync(flowconfigPath, nextConfig); + } +} + +updateFlowconfig(); diff --git a/scripts/update-tsconfig.js b/scripts/update-tsconfig.js new file mode 100644 index 00000000000..64fa49c7c75 --- /dev/null +++ b/scripts/update-tsconfig.js @@ -0,0 +1,110 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ +// @ts-check +'use strict'; + +const fs = require('fs-extra'); +const glob = require('glob'); +const path = require('node:path'); +const prettier = require('prettier'); +const {packagesManager} = require('./shared/packagesManager'); + +/** + * @typedef {Object} UpdateTsconfigOptions + * @property {Array<[string, Array]>} extraPaths additional paths to add + * @property {string} jsonFileName path to the tsconfig.json + * @property {import('prettier').Options} prettierConfig the monorepo prettier config + * @property {boolean} test true to include the test paths (default: false) + */ + +/** + * @param {opts} UpdateTsconfigOptions + * @returns {Promise} + */ +async function updateTsconfig({ + extraPaths, + jsonFileName, + prettierConfig, + test, +}) { + const prevTsconfigContents = fs.readFileSync(jsonFileName, 'utf8'); + const tsconfig = JSON.parse(prevTsconfigContents); + const publicPaths = []; + const privatePaths = []; + const testPaths = []; + const configDir = path.resolve(path.dirname(jsonFileName)); + for (const pkg of packagesManager.getPackages()) { + const resolveRelative = (...subPaths) => + path + .relative(configDir, pkg.resolve(...subPaths)) + .replace(/^(?!\.)/, './'); + + if (pkg.isPrivate()) { + if (pkg.getDirectoryName() !== 'shared') { + continue; + } + for (const {name, sourceFileName} of pkg.getPrivateModuleEntries()) { + privatePaths.push([name, [resolveRelative('src', sourceFileName)]]); + } + } else { + for (const {name, sourceFileName} of pkg.getExportedNpmModuleEntries()) { + publicPaths.push([name, [resolveRelative('src', sourceFileName)]]); + } + } + if (test) { + testPaths.push([`${pkg.getNpmName()}/src`, [resolveRelative('src')]]); + for (const fn of glob.sync( + pkg.resolve('src', '__tests__', 'utils', '*.{ts,tsx,mjs,jsx}'), + )) { + testPaths.push([ + `${pkg.getNpmName()}/src/__tests__/utils`, + [resolveRelative(fn)], + ]); + } + } + } + const paths = Object.fromEntries([ + ...extraPaths, + ...publicPaths, + ...privatePaths, + ...testPaths, + ]); + tsconfig.compilerOptions.paths = paths; + // This is async in future versions of prettier + const nextTsconfigContents = await prettier.format(JSON.stringify(tsconfig), { + ...prettierConfig, + filepath: jsonFileName, + }); + if (prevTsconfigContents !== nextTsconfigContents) { + fs.writeFileSync(jsonFileName, nextTsconfigContents); + } +} + +async function updateAllTsconfig() { + const prettierConfig = (await prettier.resolveConfig('./')) || {}; + await updateTsconfig({ + extraPaths: [], + jsonFileName: './tsconfig.json', + prettierConfig, + test: true, + }); + await updateTsconfig({ + extraPaths: [], + jsonFileName: './tsconfig.build.json', + prettierConfig, + test: false, + }); + await updateTsconfig({ + extraPaths: [['lexicalOriginal', ['../lexical/src/']]], + jsonFileName: './packages/lexical-devtools/tsconfig.json', + prettierConfig, + test: false, + }); +} + +updateAllTsconfig(); diff --git a/scripts/www/npmToWwwName.js b/scripts/www/npmToWwwName.js new file mode 100644 index 00000000000..7f3880d97a5 --- /dev/null +++ b/scripts/www/npmToWwwName.js @@ -0,0 +1,34 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ +'use strict'; + +/** + * Converts a package name in the npm name convention to the www + * convention, e.g.: + * '@lexical/rich-text' -> 'LexicalRichText' + * '@lexical/react/LexicalComposer' -> 'LexicalComposer' + * '@lexical/react/useLexicalEditor' -> 'useLexicalEditor' + * + * @param {string} name the npm or directory name of a package + * @returns {string} the name of the package in www format + */ +module.exports = function npmToWwwName(name) { + const parts = name.replace(/^@/, '').split(/\//g); + // Handle the @lexical/react/FlatNameSpace scenario + if (parts.length > 2) { + parts.splice(0, parts.length - 1); + } + return parts + .flatMap((part) => part.split('-')) + .map((part) => + part.startsWith('useLexical') + ? part + : part.charAt(0).toUpperCase() + part.slice(1), + ) + .join(''); +};