diff --git a/.github/workflows/prod-build.yml b/.github/workflows/prod-build.yml index da267b3640f3..60e929778fc7 100644 --- a/.github/workflows/prod-build.yml +++ b/.github/workflows/prod-build.yml @@ -349,6 +349,8 @@ jobs: - name: Deploy Function if: ${{ ! vars.SKIP_FUNCTION }} run: |- + set -eo pipefail + for region in europe-west1 us-west1 asia-east1; do gcloud beta functions deploy mdn-prod-prod-$region \ --gen2 \ diff --git a/.github/workflows/stage-build.yml b/.github/workflows/stage-build.yml index 5c1a301b94ab..2f708eda19b1 100644 --- a/.github/workflows/stage-build.yml +++ b/.github/workflows/stage-build.yml @@ -365,6 +365,8 @@ jobs: - name: Deploy Function if: ${{ ! vars.SKIP_FUNCTION }} run: |- + set -eo pipefail + for region in europe-west1 us-west1 asia-east1; do gcloud beta functions deploy mdn-nonprod-stage-$region \ --gen2 \ diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml index bfa70d1d960d..4039bbabf04b 100644 --- a/.github/workflows/test-build.yml +++ b/.github/workflows/test-build.yml @@ -245,6 +245,8 @@ jobs: - name: Deploy Function if: ${{ ! vars.SKIP_FUNCTION }} run: |- + set -eo pipefail + for region in europe-west3; do gcloud beta functions deploy mdn-nonprod-test-$region \ --gen2 \ @@ -264,6 +266,7 @@ jobs: --set-env-vars="ORIGIN_PLAY=test.mdnyalp.dev" \ --set-env-vars="SOURCE_CONTENT=https://storage.googleapis.com/${{ vars.GCP_BUCKET_NAME }}/main/" \ --set-env-vars="SOURCE_API=https://api.developer.allizom.org/" \ + --set-env-vars="BSA_ENABLED=true" \ --set-env-vars="SENTRY_DSN=${{ secrets.SENTRY_DSN_CLOUD_FUNCTION }}" \ --set-env-vars="SENTRY_ENVIRONMENT=test" \ --set-env-vars="SENTRY_TRACES_SAMPLE_RATE=${{ vars.SENTRY_TRACES_SAMPLE_RATE }}" \ @@ -271,6 +274,7 @@ jobs: --set-secrets="KEVEL_SITE_ID=projects/${{ secrets.GCP_PROJECT_NAME }}/secrets/stage-kevel-site-id/versions/latest" \ --set-secrets="KEVEL_NETWORK_ID=projects/${{ secrets.GCP_PROJECT_NAME }}/secrets/stage-kevel-network-id/versions/latest" \ --set-secrets="SIGN_SECRET=projects/${{ secrets.GCP_PROJECT_NAME }}/secrets/stage-sign-secret/versions/latest" \ + --set-secrets="BSA_ZONE_KEYS=projects/${{ secrets.GCP_PROJECT_NAME }}/secrets/stage-bsa-zone-keys/versions/latest" \ 2>&1 | sed "s/^/[$region] /" & pids+=($!) done diff --git a/.gitignore b/.gitignore index 2426cd7bcd72..db0c3e8198af 100644 --- a/.gitignore +++ b/.gitignore @@ -46,9 +46,6 @@ yarn-error.log* /server/*.js /server/*.js.map /ssr/dist/ -/ssr/*.js -!/ssr/mozilla.dnthelper.min.js -!/ssr/webpack.config.js /ssr/*.js.map /tool/*.js /tool/*.js.map diff --git a/.release-please-manifest.json b/.release-please-manifest.json index d73eb3f50f23..2c8b732d60a7 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "2.62.0" + ".": "2.63.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 01bbc4133968..7c91f97fb704 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,142 @@ # Changelog +## [2.63.1](https://github.com/mdn/yari/compare/v2.63.0...v2.63.1) (2024-09-16) + + +### Bug Fixes + +* **build:** use pipefail to fail function deployments ([#11806](https://github.com/mdn/yari/issues/11806)) ([5141019](https://github.com/mdn/yari/commit/5141019b54e24d8df6db8619dd1585c8fb1a7587)) +* **placement:** use variable for horizontal banner ([#11795](https://github.com/mdn/yari/issues/11795)) ([d880aaf](https://github.com/mdn/yari/commit/d880aaf8116f1fe692452f68107e173d042e5cb9)) +* **sidebar:** reuse icon margin for non-nested entries ([#11786](https://github.com/mdn/yari/issues/11786)) ([2294df0](https://github.com/mdn/yari/commit/2294df00ba8be26bf0767b78c02a0abad5c7894e)) + + +### Enhancements + +* **ad-free:** hide all banners completely ([#11787](https://github.com/mdn/yari/issues/11787)) ([f93b850](https://github.com/mdn/yari/commit/f93b85005b8f921e9f27266d02750af0363e1d06)) + + +### Miscellaneous + +* **deps-dev:** bump @playwright/test from 1.47.0 to 1.47.1 ([#11812](https://github.com/mdn/yari/issues/11812)) ([8e698db](https://github.com/mdn/yari/commit/8e698db7231a8ce0f87630bc50f19667b0deb136)) +* **deps-dev:** bump @swc/core from 1.7.24 to 1.7.25 ([#11791](https://github.com/mdn/yari/issues/11791)) ([0e30971](https://github.com/mdn/yari/commit/0e309719d5193d4dcc193cfd997dbd591475e590)) +* **deps-dev:** bump @swc/core from 1.7.25 to 1.7.26 ([#11793](https://github.com/mdn/yari/issues/11793)) ([51c07ba](https://github.com/mdn/yari/commit/51c07ba14af4d142b9ea16315a92fe65e35999c8)) +* **deps-dev:** bump @types/jest from 29.5.12 to 29.5.13 in the types group ([#11801](https://github.com/mdn/yari/issues/11801)) ([fc79d49](https://github.com/mdn/yari/commit/fc79d49564ba9bbb0fb7edff63c77947532e97d1)) +* **deps-dev:** bump @types/react from 18.3.5 to 18.3.6 in the types group ([#11808](https://github.com/mdn/yari/issues/11808)) ([9baf777](https://github.com/mdn/yari/commit/9baf777611cf9f3f7c0f65e5ece25d577290e856)) +* **deps-dev:** bump babel-loader from 9.1.3 to 9.2.0 ([#11811](https://github.com/mdn/yari/issues/11811)) ([fc35868](https://github.com/mdn/yari/commit/fc3586803901ea4d3a3fc66d0b6c18e57d025545)) +* **deps-dev:** bump eslint-plugin-react from 7.35.2 to 7.36.1 ([#11805](https://github.com/mdn/yari/issues/11805)) ([35fc136](https://github.com/mdn/yari/commit/35fc1362f82b1d6bea60d4a0225cf4e34d5fa6a1)) +* **deps-dev:** bump husky from 9.1.5 to 9.1.6 ([#11798](https://github.com/mdn/yari/issues/11798)) ([b3288e0](https://github.com/mdn/yari/commit/b3288e016c54fa41faac200e6a78be00f74509a0)) +* **deps-dev:** bump postcss from 8.4.45 to 8.4.47 ([#11810](https://github.com/mdn/yari/issues/11810)) ([2497484](https://github.com/mdn/yari/commit/2497484c5e94efaf9186940e28b84e3453459d05)) +* **deps-dev:** bump the types group across 1 directory with 2 updates ([#11743](https://github.com/mdn/yari/issues/11743)) ([a38975e](https://github.com/mdn/yari/commit/a38975ece5e7b4cf588964ea15b1abdb19b8c69b)) +* **deps:** bump express from 4.20.0 to 4.21.0 ([#11796](https://github.com/mdn/yari/issues/11796)) ([90e5231](https://github.com/mdn/yari/commit/90e5231100588a6a65073ff1946d05fff02ab33e)) +* **deps:** bump mdn-data from 2.11.0 to 2.11.1 ([#11802](https://github.com/mdn/yari/issues/11802)) ([16d9403](https://github.com/mdn/yari/commit/16d9403db82e6bb4cfa7e94e7fb36f42ce98e4c5)) +* **deps:** bump openai from 4.58.2 to 4.59.0 ([#11799](https://github.com/mdn/yari/issues/11799)) ([4877d5b](https://github.com/mdn/yari/commit/4877d5bf0dcd1c6baf6cbdeaa46f64d4e2c820ca)) +* **deps:** bump openai from 4.59.0 to 4.60.0 ([#11804](https://github.com/mdn/yari/issues/11804)) ([dd1c110](https://github.com/mdn/yari/commit/dd1c11065374834c0ed4dd48f20fc0a3db9d7657)) +* **deps:** bump openai from 4.60.0 to 4.61.0 ([#11809](https://github.com/mdn/yari/issues/11809)) ([bcc7727](https://github.com/mdn/yari/commit/bcc7727d0be0c3eb0abcbba2a256dd718d40b47f)) +* **deps:** bump the dependencies group in /deployer with 2 updates ([#11807](https://github.com/mdn/yari/issues/11807)) ([0caaee1](https://github.com/mdn/yari/commit/0caaee198887ff226a9d9d10bb48d1e5d5b6564b)) +* **deps:** bump web-features from 1.2.0 to 1.3.0 ([#11800](https://github.com/mdn/yari/issues/11800)) ([39e1574](https://github.com/mdn/yari/commit/39e1574b4a21e15bc287fcd5310755a7b2f7f015)) +* **deps:** run npm audit fix in /cloud-function ([#11792](https://github.com/mdn/yari/issues/11792)) ([43b307f](https://github.com/mdn/yari/commit/43b307fe9c59ee3e16eecb16be943a8dfaad6d7d)) +* **deps:** run yarn upgrade ([#11789](https://github.com/mdn/yari/issues/11789)) ([2777d58](https://github.com/mdn/yari/commit/2777d58faffad32ce25e479968306331cef3b001)) +* **macros:** delete DOMAttributeMethods + unimplemented_inline macros ([#11790](https://github.com/mdn/yari/issues/11790)) ([e0b7616](https://github.com/mdn/yari/commit/e0b76166d13511d773ee67c397a058eb883bb278)) +* **placement:** add scrimba discount ([#11785](https://github.com/mdn/yari/issues/11785)) ([ac157ff](https://github.com/mdn/yari/commit/ac157ff0a659e10b7fed6646d105e1ed90a7ea44)) +* **tools-menu:** remove "New" indicator ([#11794](https://github.com/mdn/yari/issues/11794)) ([f2bca97](https://github.com/mdn/yari/commit/f2bca97057fe51e69fb2ada1e1360767d798b531)) + +## [2.63.0](https://github.com/mdn/yari/compare/v2.62.0...v2.63.0) (2024-09-10) + + +### Features + +* translate MathML section ([606e5cb](https://github.com/mdn/yari/commit/606e5cb54b2eae8749d32cf5b8ce0af8b861bc9e)) + + +### Bug Fixes + +* **ai-help:** handle invalid chat ids correctly ([#11678](https://github.com/mdn/yari/issues/11678)) ([225fe24](https://github.com/mdn/yari/commit/225fe24478291a16f026969724aee03dfcabceb2)) +* **ai-help:** set chatId on user message ([#11751](https://github.com/mdn/yari/issues/11751)) ([97f4b23](https://github.com/mdn/yari/commit/97f4b233c4af2572d1f81d4175af918e5119490d)) +* **ci:** check dependabot PR user instead of actor ([#11741](https://github.com/mdn/yari/issues/11741)) ([0d8dd6b](https://github.com/mdn/yari/commit/0d8dd6b92637a8756b4e3862b00e42e928de21fc)) +* **contributor-spotlight:** some old pages not building ([#11679](https://github.com/mdn/yari/issues/11679)) ([5adb9eb](https://github.com/mdn/yari/commit/5adb9eb2be4a06d9e3dd208f28a79003082bddba)) +* **curriculum:** fix module overview sidescroll ([#11681](https://github.com/mdn/yari/issues/11681)) ([0be1a35](https://github.com/mdn/yari/commit/0be1a35c965f34795d7c827e6b0b458ba09bd42c)) +* **layout:** avoid sidebar overflow beyond footer ([#11621](https://github.com/mdn/yari/issues/11621)) ([8b0792f](https://github.com/mdn/yari/commit/8b0792f2554e7d64521a29f83861ff4e7d18f059)) +* **macros/WebExtAllExamples:** update branch name ([#11747](https://github.com/mdn/yari/issues/11747)) ([61ef395](https://github.com/mdn/yari/commit/61ef395d613e15e1c6fd493eeb7bb9ef3dfeb080)) +* **macro:** update redirected URLs ([61ef395](https://github.com/mdn/yari/commit/61ef395d613e15e1c6fd493eeb7bb9ef3dfeb080)) + + +### Miscellaneous + +* **deps-dev:** add @types/js-yaml 4.0.9 ([#11620](https://github.com/mdn/yari/issues/11620)) ([0c4e3f7](https://github.com/mdn/yari/commit/0c4e3f7c8819ac5cda4547e965beb524d3db0d7f)) +* **deps-dev:** bump @playwright/test from 1.46.1 to 1.47.0 ([#11749](https://github.com/mdn/yari/issues/11749)) ([0de6066](https://github.com/mdn/yari/commit/0de606681abdedfd73d6f2bde34759f28177b877)) +* **deps-dev:** bump @swc/core from 1.7.18 to 1.7.19 ([#11706](https://github.com/mdn/yari/issues/11706)) ([1042246](https://github.com/mdn/yari/commit/104224655d35aa9680a2e5e0b727e5e83b8bfb57)) +* **deps-dev:** bump @swc/core from 1.7.19 to 1.7.21 ([#11708](https://github.com/mdn/yari/issues/11708)) ([05b9e81](https://github.com/mdn/yari/commit/05b9e81f49d3abc42ad5f891cfe7bea666e5f181)) +* **deps-dev:** bump @swc/core from 1.7.21 to 1.7.22 ([#11716](https://github.com/mdn/yari/issues/11716)) ([1e4867f](https://github.com/mdn/yari/commit/1e4867fc1154707acdfdaad63bc15cf8f4b4586a)) +* **deps-dev:** bump @swc/core from 1.7.22 to 1.7.23 ([#11728](https://github.com/mdn/yari/issues/11728)) ([9a73951](https://github.com/mdn/yari/commit/9a73951ab4f49194baea447a7ec59e55afd91944)) +* **deps-dev:** bump @swc/core from 1.7.23 to 1.7.24 ([#11766](https://github.com/mdn/yari/issues/11766)) ([5047a7a](https://github.com/mdn/yari/commit/5047a7af9a2acebc719ab15b1eeb4a0962b559dc)) +* **deps-dev:** bump @types/node from 18.19.46 to 18.19.47 in the types group ([#11705](https://github.com/mdn/yari/issues/11705)) ([151cdf1](https://github.com/mdn/yari/commit/151cdf18507917be6a29845a5f295bacba81b3a3)) +* **deps-dev:** bump diff from 5.2.0 to 6.0.0 ([#11729](https://github.com/mdn/yari/issues/11729)) ([418da90](https://github.com/mdn/yari/commit/418da90ed6b4945f2a052f2f6d109c683b0f8973)) +* **deps-dev:** bump diff from 6.0.0 to 7.0.0 ([#11764](https://github.com/mdn/yari/issues/11764)) ([6dc8934](https://github.com/mdn/yari/commit/6dc8934176aae8bdfe405dbcf58eb9ce0b6ead44)) +* **deps-dev:** bump eslint-plugin-import from 2.29.1 to 2.30.0 ([#11727](https://github.com/mdn/yari/issues/11727)) ([65c7920](https://github.com/mdn/yari/commit/65c792087b4ea5828b5ba17d711bdf82ae6785d0)) +* **deps-dev:** bump eslint-plugin-jest from 28.8.0 to 28.8.1 ([#11712](https://github.com/mdn/yari/issues/11712)) ([a4f2535](https://github.com/mdn/yari/commit/a4f2535b7ef037cdbb6995d52cfb06d93181c661)) +* **deps-dev:** bump eslint-plugin-jest from 28.8.1 to 28.8.2 ([#11721](https://github.com/mdn/yari/issues/11721)) ([50276a3](https://github.com/mdn/yari/commit/50276a3a8b92cd6f43ec72e5912ee4f4f765ee7d)) +* **deps-dev:** bump eslint-plugin-jest from 28.8.2 to 28.8.3 ([#11745](https://github.com/mdn/yari/issues/11745)) ([b5d1ac2](https://github.com/mdn/yari/commit/b5d1ac235dc887d23dc9452fa71788f0d59aeddd)) +* **deps-dev:** bump eslint-plugin-jsx-a11y from 6.9.0 to 6.10.0 ([#11735](https://github.com/mdn/yari/issues/11735)) ([b0fe30c](https://github.com/mdn/yari/commit/b0fe30c5b1c5884256bc21d632cf3609eb9ff0c6)) +* **deps-dev:** bump eslint-plugin-react from 7.35.0 to 7.35.1 ([#11726](https://github.com/mdn/yari/issues/11726)) ([d026632](https://github.com/mdn/yari/commit/d026632c1d3730d07774bfd01f175bf559c9de32)) +* **deps-dev:** bump eslint-plugin-react from 7.35.1 to 7.35.2 ([#11740](https://github.com/mdn/yari/issues/11740)) ([4b3903d](https://github.com/mdn/yari/commit/4b3903d7a3d5ff01fe619cea81a65fa46823afa7)) +* **deps-dev:** bump html-validate from 8.21.0 to 8.22.0 ([#11765](https://github.com/mdn/yari/issues/11765)) ([4febe01](https://github.com/mdn/yari/commit/4febe01541e571de800942b3df1d95f772ae7cf7)) +* **deps-dev:** bump jsdom from 24.1.1 to 25.0.0 ([#11691](https://github.com/mdn/yari/issues/11691)) ([3ed5009](https://github.com/mdn/yari/commit/3ed5009eab3f4d19b6afa4aeb1db8111befdc945)) +* **deps-dev:** bump postcss from 8.4.41 to 8.4.44 ([#11720](https://github.com/mdn/yari/issues/11720)) ([8c0bc24](https://github.com/mdn/yari/commit/8c0bc2437c6a5773f1766cbf6ecd77a95b6770a8)) +* **deps-dev:** bump postcss from 8.4.44 to 8.4.45 ([#11732](https://github.com/mdn/yari/issues/11732)) ([54891cd](https://github.com/mdn/yari/commit/54891cdf07e72150724b31657bcd1e67a739120f)) +* **deps-dev:** bump postcss-normalize from 10.0.1 to 13.0.0 ([#11769](https://github.com/mdn/yari/issues/11769)) ([90824b9](https://github.com/mdn/yari/commit/90824b9478ebcd67ae70625f36c58f68a6a3a823)) +* **deps-dev:** bump postcss-preset-env from 10.0.2 to 10.0.3 ([#11763](https://github.com/mdn/yari/issues/11763)) ([d515d49](https://github.com/mdn/yari/commit/d515d493fcdae177d382980f218f9cee69729a4a)) +* **deps-dev:** bump postcss-preset-env from 9.6.0 to 10.0.2 ([#11650](https://github.com/mdn/yari/issues/11650)) ([8450196](https://github.com/mdn/yari/commit/8450196bbd935809efa985394249e544f9c6529a)) +* **deps-dev:** bump react-router-dom from 6.26.1 to 6.26.2 ([#11779](https://github.com/mdn/yari/issues/11779)) ([d3953b8](https://github.com/mdn/yari/commit/d3953b81c0c092f7a1cb4ac69615a842e8cdda1d)) +* **deps-dev:** bump sass from 1.77.6 to 1.78.0 ([#11736](https://github.com/mdn/yari/issues/11736)) ([6b2cde6](https://github.com/mdn/yari/commit/6b2cde6955926e7b0033146e470f870fc6d31deb)) +* **deps-dev:** bump sass-loader from 15.0.0 to 16.0.1 ([#11661](https://github.com/mdn/yari/issues/11661)) ([b4c6ec5](https://github.com/mdn/yari/commit/b4c6ec53bff459ae54e85a4695eff55ac2f355ec)) +* **deps-dev:** bump typescript from 5.5.4 to 5.6.2 ([#11780](https://github.com/mdn/yari/issues/11780)) ([cda5388](https://github.com/mdn/yari/commit/cda5388bd751a5428ab41d9b2fd1b17cf8f957a3)) +* **deps-dev:** bump typescript from 5.5.4 to 5.6.2 in /client/pwa ([#11784](https://github.com/mdn/yari/issues/11784)) ([35a38cf](https://github.com/mdn/yari/commit/35a38cfda471b409758449e8b6ff7f98d3a19f98)) +* **deps-dev:** bump typescript-eslint from 8.3.0 to 8.4.0 ([#11730](https://github.com/mdn/yari/issues/11730)) ([46e9a9f](https://github.com/mdn/yari/commit/46e9a9f54b65da1921406ed39d97102efdd7bd27)) +* **deps-dev:** bump typescript-eslint from 8.4.0 to 8.5.0 ([#11777](https://github.com/mdn/yari/issues/11777)) ([a19a636](https://github.com/mdn/yari/commit/a19a636b45c11c44fdcb6679624c4ee214d00f32)) +* **deps-dev:** bump webpack-dev-server from 5.0.4 to 5.1.0 ([#11738](https://github.com/mdn/yari/issues/11738)) ([63f24c6](https://github.com/mdn/yari/commit/63f24c679fdfe9dcfea926fd24c3b38d97a0b0b0)) +* **deps:** add @types/js-yaml devDependency ([0c4e3f7](https://github.com/mdn/yari/commit/0c4e3f7c8819ac5cda4547e965beb524d3db0d7f)) +* **deps:** bump [@zip](https://github.com/zip).js/zip.js from 2.7.51 to 2.7.52 in /client/pwa ([#11703](https://github.com/mdn/yari/issues/11703)) ([9199073](https://github.com/mdn/yari/commit/91990735c66405080f88e9121db76edf25aecd56)) +* **deps:** bump @codemirror/lang-css from 6.2.1 to 6.3.0 ([#11761](https://github.com/mdn/yari/issues/11761)) ([e98de74](https://github.com/mdn/yari/commit/e98de7418cd0aa27c439b2ba6c45912fb70e6f8e)) +* **deps:** bump @mdn/browser-compat-data from 5.5.49 to 5.5.50 ([#11737](https://github.com/mdn/yari/issues/11737)) ([16b85db](https://github.com/mdn/yari/commit/16b85dbabac846f8db73718227cf36a644a5a04e)) +* **deps:** bump @mdn/browser-compat-data from 5.5.50 to 5.5.51 ([#11767](https://github.com/mdn/yari/issues/11767)) ([e18d9f4](https://github.com/mdn/yari/commit/e18d9f4d5030dd9554fa89c0867ec74453760ca9)) +* **deps:** bump @sentry/node from 8.26.0 to 8.27.0 in the sentry group ([#11704](https://github.com/mdn/yari/issues/11704)) ([8e108bc](https://github.com/mdn/yari/commit/8e108bc0eb51ad4124a4b6e3e95125135a238796)) +* **deps:** bump @sentry/node from 8.27.0 to 8.28.0 in the sentry group ([#11725](https://github.com/mdn/yari/issues/11725)) ([165b2e8](https://github.com/mdn/yari/commit/165b2e8554a42b7e06f60f5f9068e8ae8334cae1)) +* **deps:** bump @sentry/node from 8.28.0 to 8.29.0 in the sentry group ([#11760](https://github.com/mdn/yari/issues/11760)) ([9b5103f](https://github.com/mdn/yari/commit/9b5103f9dae5b2a81a31978dbbddd85211409150)) +* **deps:** bump @stripe/stripe-js from 4.3.0 to 4.4.0 ([#11709](https://github.com/mdn/yari/issues/11709)) ([4b3e75b](https://github.com/mdn/yari/commit/4b3e75bdef8b83433020fcb616437b3632d2fe90)) +* **deps:** bump @webref/css from 6.14.2 to 6.15.0 ([#11714](https://github.com/mdn/yari/issues/11714)) ([d27da87](https://github.com/mdn/yari/commit/d27da8708fcb0e499f2c360cf43f86b3bef7b05a)) +* **deps:** bump @webref/css from 6.15.0 to 6.15.1 ([#11748](https://github.com/mdn/yari/issues/11748)) ([4d66823](https://github.com/mdn/yari/commit/4d668234dc2d0b9b867216acc5fc692cbe466fed)) +* **deps:** bump boto3 from 1.35.10 to 1.35.14 in /deployer in the dependencies group ([#11759](https://github.com/mdn/yari/issues/11759)) ([4c1e99e](https://github.com/mdn/yari/commit/4c1e99e22d266b3570fd2fe982a1eb074c36703b)) +* **deps:** bump boto3 from 1.35.5 to 1.35.10 in /deployer in the dependencies group ([#11723](https://github.com/mdn/yari/issues/11723)) ([b34fa69](https://github.com/mdn/yari/commit/b34fa6901855d96aad1c4017c1b117006c2eaf7f)) +* **deps:** bump cryptography from 42.0.4 to 43.0.1 in /deployer ([#11731](https://github.com/mdn/yari/issues/11731)) ([64a7e82](https://github.com/mdn/yari/commit/64a7e821dd45516947a9727de73c4f032ce3d43a)) +* **deps:** bump express from 4.19.2 to 4.20.0 ([#11781](https://github.com/mdn/yari/issues/11781)) ([77bdb78](https://github.com/mdn/yari/commit/77bdb78718aa90bd624b130cc106058318cd9946)) +* **deps:** bump file-type from 19.4.1 to 19.5.0 ([#11770](https://github.com/mdn/yari/issues/11770)) ([34ab42d](https://github.com/mdn/yari/commit/34ab42db5177eee917bb973ede22effdcf1f10cd)) +* **deps:** bump loglevel from 1.9.1 to 1.9.2 ([#11762](https://github.com/mdn/yari/issues/11762)) ([7fdfffa](https://github.com/mdn/yari/commit/7fdfffa67ddefaaad42995995a511419b68e0be9)) +* **deps:** bump mdn-data from 2.10.0 to 2.11.0 ([#11782](https://github.com/mdn/yari/issues/11782)) ([24e8009](https://github.com/mdn/yari/commit/24e8009837ae6f1ff3dbfd956d7db952587f0aad)) +* **deps:** bump mdn-data from 2.9.0 to 2.10.0 ([#11722](https://github.com/mdn/yari/issues/11722)) ([965e9b7](https://github.com/mdn/yari/commit/965e9b75dca03e92a8507a9162a7a7a634fbf749)) +* **deps:** bump micromatch from 4.0.5 to 4.0.8 ([28bd03e](https://github.com/mdn/yari/commit/28bd03e8068edd45a6ec9cee0ab7fa65ea21c4e7)) +* **deps:** bump micromatch from 4.0.5 to 4.0.8 in /cloud-function ([#11756](https://github.com/mdn/yari/issues/11756)) ([28bd03e](https://github.com/mdn/yari/commit/28bd03e8068edd45a6ec9cee0ab7fa65ea21c4e7)) +* **deps:** bump nodemon from 2.0.22 to 3.1.4 ([1a3e72b](https://github.com/mdn/yari/commit/1a3e72bd0618250e585473c2db3208cd07cbcaba)) +* **deps:** bump nodemon from 2.0.22 to 3.1.4 in /cloud-function ([#11754](https://github.com/mdn/yari/issues/11754)) ([1a3e72b](https://github.com/mdn/yari/commit/1a3e72bd0618250e585473c2db3208cd07cbcaba)) +* **deps:** bump openai from 4.56.0 to 4.56.1 ([#11707](https://github.com/mdn/yari/issues/11707)) ([c1eb963](https://github.com/mdn/yari/commit/c1eb963f238f15a9cb20753e5abb83fb1f9b4052)) +* **deps:** bump openai from 4.56.1 to 4.57.0 ([#11713](https://github.com/mdn/yari/issues/11713)) ([dcbc926](https://github.com/mdn/yari/commit/dcbc9265d8f2066684c708a4fa69fbaaea17a6cf)) +* **deps:** bump openai from 4.57.0 to 4.57.1 ([#11739](https://github.com/mdn/yari/issues/11739)) ([98f96bc](https://github.com/mdn/yari/commit/98f96bcec90ae1169f1ceb308b085b663cd2c354)) +* **deps:** bump openai from 4.57.1 to 4.57.3 ([#11746](https://github.com/mdn/yari/issues/11746)) ([fc7b9d6](https://github.com/mdn/yari/commit/fc7b9d65cbc30c2e3ac5862d0115089d210170e9)) +* **deps:** bump openai from 4.57.3 to 4.58.0 ([#11750](https://github.com/mdn/yari/issues/11750)) ([9465fec](https://github.com/mdn/yari/commit/9465fecee903cdd947f2bd25afa2a8a2f373243e)) +* **deps:** bump openai from 4.58.0 to 4.58.1 ([#11768](https://github.com/mdn/yari/issues/11768)) ([86bf27c](https://github.com/mdn/yari/commit/86bf27c4a131bd68665b95a589eb87bfb0423a1a)) +* **deps:** bump openai from 4.58.1 to 4.58.2 ([#11783](https://github.com/mdn/yari/issues/11783)) ([0960844](https://github.com/mdn/yari/commit/09608440f17b3292413d61490538f85609be2eda)) +* **deps:** bump pyquery from 2.0.0 to 2.0.1 in /testing/integration in the dependencies group ([#11718](https://github.com/mdn/yari/issues/11718)) ([e0c6a42](https://github.com/mdn/yari/commit/e0c6a420b0577239e6dff98e17d7caa05cf53495)) +* **deps:** bump send from 0.18.0 to 0.19.0 ([#11776](https://github.com/mdn/yari/issues/11776)) ([9f35bf8](https://github.com/mdn/yari/commit/9f35bf84f18741d88b4bab37455e573262501f47)) +* **deps:** bump web-specs from 3.17.0 to 3.18.0 ([#11710](https://github.com/mdn/yari/issues/11710)) ([81dacb3](https://github.com/mdn/yari/commit/81dacb3fc0a58e864d9d2bd0837be51a1b5d4464)) +* **deps:** bump web-specs from 3.18.0 to 3.18.1 ([#11715](https://github.com/mdn/yari/issues/11715)) ([faed92a](https://github.com/mdn/yari/commit/faed92a42cc563c9281f1116c51f7bd17ec9439b)) +* **deps:** bump web-specs from 3.18.1 to 3.19.0 ([#11733](https://github.com/mdn/yari/issues/11733)) ([e185b30](https://github.com/mdn/yari/commit/e185b3046f2e8fa69a6ccc6577d0e9596c58c87d)) +* **deps:** bump web-specs from 3.19.0 to 3.20.0 ([#11744](https://github.com/mdn/yari/issues/11744)) ([deb979b](https://github.com/mdn/yari/commit/deb979b8b4032db8655630c46ed2f8545cd8aeb5)) +* **deps:** bump web-specs from 3.20.0 to 3.21.0 ([#11778](https://github.com/mdn/yari/issues/11778)) ([55643a5](https://github.com/mdn/yari/commit/55643a513c1bda40933a84e2ca972b15e93975e8)) +* **deps:** pin cheerio to 1.0.0-rc.12 ([#11752](https://github.com/mdn/yari/issues/11752)) ([722b130](https://github.com/mdn/yari/commit/722b13033ca9b0b2234d927ac4e0c298a3b00740)) +* **macros/LearnSidebar:** add Japanese translation for MathML section ([#11680](https://github.com/mdn/yari/issues/11680)) ([606e5cb](https://github.com/mdn/yari/commit/606e5cb54b2eae8749d32cf5b8ce0af8b861bc9e)) +* **news:** add scrimba partnership to news ([#11701](https://github.com/mdn/yari/issues/11701)) ([60d8dee](https://github.com/mdn/yari/commit/60d8dee49883a0c43b996938b5257bdadc667df7)) +* **spas:** re-use section from docs for spas ([#11742](https://github.com/mdn/yari/issues/11742)) ([460c019](https://github.com/mdn/yari/commit/460c019791c05d2c5a866101e90c3afd33147f63)) +* **telemetry:** renew metrics (and remove some) ([#11724](https://github.com/mdn/yari/issues/11724)) ([852d867](https://github.com/mdn/yari/commit/852d8678eec9354dfc6e95faaa825958ec1f8565)) + ## [2.62.0](https://github.com/mdn/yari/compare/v2.61.0...v2.62.0) (2024-08-28) diff --git a/build/blog.ts b/build/blog.ts index 715115345a98..4caafa0bc912 100644 --- a/build/blog.ts +++ b/build/blog.ts @@ -28,7 +28,7 @@ import { postProcessSmallerHeadingIDs, } from "./utils.js"; import { slugToFolder } from "../libs/slug-utils/index.js"; -import { syntaxHighlight } from "./syntax-highlight.js"; +import { wrapCodeExamples } from "./code-headers.js"; import { wrapTables } from "./wrap-tables.js"; import { Doc } from "../libs/types/document.js"; import { extractSections } from "./extract-sections.js"; @@ -391,7 +391,7 @@ export async function buildPost( doc.hasMathML = true; } $("div.hidden").remove(); - syntaxHighlight($, doc); + wrapCodeExamples($); injectNoTranslate($); injectLoadingLazyAttributes($); postProcessExternalLinks($); diff --git a/build/code-headers.ts b/build/code-headers.ts new file mode 100644 index 000000000000..8b0823fb32a5 --- /dev/null +++ b/build/code-headers.ts @@ -0,0 +1,45 @@ +import * as cheerio from "cheerio"; + +// Over the years we have accumulated some weird
 tags whose
+// brush is more or less "junk".
+// TODO: Perhaps, if you have a doc with 
 tags that matches
+// this, it should become a flaw.
+const IGNORE = new Set(["none", "text", "plain", "unix"]);
+
+/**
+ * Mutate the `$` instance by adding headers to 
 tags containing code blocks.
+ *
+ */
+export function wrapCodeExamples($: cheerio.CheerioAPI) {
+  // Our content will be like this: `
` or
+  // `
` so we're technically not looking for an exact
+  // match. The wildcard would technically match `
`
+  // too. But within the loop, we do a more careful regex on the class name
+  // and only proceed if it's something sensible.
+  $("pre[class*=brush]").each((_, element) => {
+    // The language is whatever string comes after the `brush(:)`
+    // portion of the class name.
+    const $pre = $(element);
+
+    const className = $pre.attr("class").toLowerCase();
+    const match = className.match(/brush:?\s*([\w_-]+)/);
+    if (!match) {
+      return;
+    }
+    const name = match[1].replace("-nolint", "");
+    if (IGNORE.has(name)) {
+      // Seems to exist a couple of these in our docs. Just bail.
+      return;
+    }
+    const code = $pre.text();
+    $pre.wrapAll(`
`); + if (!$pre.hasClass("hidden")) { + $( + `
${name}
` + ).insertBefore($pre); + } + const $code = $("").text(code); + + $pre.empty().append($code); + }); +} diff --git a/build/curriculum.ts b/build/curriculum.ts index 863e338070ff..068c7fc15f4e 100644 --- a/build/curriculum.ts +++ b/build/curriculum.ts @@ -9,7 +9,7 @@ import { DocParent } from "../libs/types/document.js"; import { CURRICULUM_TITLE, DEFAULT_LOCALE } from "../libs/constants/index.js"; import * as kumascript from "../kumascript/index.js"; import LANGUAGES_RAW from "../libs/languages/index.js"; -import { syntaxHighlight } from "./syntax-highlight.js"; +import { wrapCodeExamples } from "./code-headers.js"; import { escapeRegExp, injectLoadingLazyAttributes, @@ -321,7 +321,7 @@ export async function buildCurriculumPage( doc.hasMathML = true; } $("div.hidden").remove(); - syntaxHighlight($, doc); + wrapCodeExamples($); injectNoTranslate($); injectLoadingLazyAttributes($); postProcessCurriculumLinks($, (p: string | undefined) => { diff --git a/build/index.ts b/build/index.ts index c9cb88533910..ccacb3b2b8ef 100644 --- a/build/index.ts +++ b/build/index.ts @@ -28,7 +28,7 @@ import { } from "./flaws/index.js"; import { checkImageReferences, checkImageWidths } from "./check-images.js"; import { getPageTitle } from "./page-title.js"; -import { syntaxHighlight } from "./syntax-highlight.js"; +import { wrapCodeExamples } from "./code-headers.js"; import { formatNotecards } from "./format-notecards.js"; import buildOptions from "./build-options.js"; import LANGUAGES_RAW from "../libs/languages/index.js"; @@ -456,8 +456,8 @@ export async function buildDocument( plainHTML = $.html(); } - // Apply syntax highlighting all
 tags.
-  syntaxHighlight($, doc);
+  // Add headers to all 
 tags with code.
+  wrapCodeExamples($);
 
   // Post process HTML so that the right elements gets tagged so they
   // *don't* get translated by tools like Google Translate.
diff --git a/build/syntax-highlight.ts b/build/syntax-highlight.ts
deleted file mode 100644
index d27a6902a60c..000000000000
--- a/build/syntax-highlight.ts
+++ /dev/null
@@ -1,139 +0,0 @@
-import Prism from "prismjs";
-import loadLanguages from "prismjs/components/index.js";
-import "prism-svelte";
-import * as cheerio from "cheerio";
-import { createHmac } from "node:crypto";
-import { SAMPLE_SIGN_KEY } from "../libs/env/index.js";
-
-const lazy = (creator) => {
-  let res;
-  let processed = false;
-  return (...args) => {
-    if (processed) return res;
-    res = creator.apply(this, args);
-    processed = true;
-    return res;
-  };
-};
-
-const loadAllLanguages = lazy(() => {
-  // Some languages are always loaded by Prism, so we can omit them here:
-  // - Markup (atom, html, markup, mathml, rss, ssml, svg, xml)
-  // - CSS (css)
-  // - C-like (clike)
-  // - JavaScript (javascript, js)
-  loadLanguages([
-    "apacheconf",
-    "bash",
-    "batch",
-    "c",
-    "cpp",
-    "cs",
-    "diff",
-    "django",
-    "glsl",
-    "go",
-    "handlebars",
-    "http",
-    "ignore",
-    "ini",
-    "java",
-    "json",
-    "jsx",
-    "latex",
-    "less",
-    "md",
-    "nginx",
-    "php",
-    "powershell",
-    "pug",
-    "python",
-    "regex",
-    "rust",
-    "scss",
-    "sql",
-    // 'svelte', // Loaded by `prism-svelte` extension
-    "toml",
-    "tsx",
-    "typescript",
-    "uri",
-    "wasm",
-    "webidl",
-    "yaml",
-  ]);
-});
-
-// Add things to this list to help make things convenient. Sometimes
-// there are `
` whose name is not that which
-// Prism expects. It'd be hard to require that content writers
-// have to stick to the exact naming conventions that Prism uses
-// because Prism is an implementation detail.
-const ALIASES = new Map([
-  ["sh", "shell"],
-  ["vue", "markup"], // See https://github.com/PrismJS/prism/issues/1665#issuecomment-536529608
-]);
-
-// Over the years we have accumulated some weird 
 tags whose
-// brush is more or less "junk".
-// TODO: Perhaps, if you have a doc with 
 tags that matches
-// this, it should become a flaw.
-const IGNORE = new Set(["none", "text", "plain", "unix"]);
-
-/**
- * Mutate the `$` instance for by looking for 
 tags that can be
- * syntax highlighted with Prism.
- *
- */
-export function syntaxHighlight($: cheerio.CheerioAPI, doc) {
-  loadAllLanguages();
-
-  // Our content will be like this: `
` or
-  // `
` so we're technically not looking for an exact
-  // match. The wildcard would technically match `
`
-  // too. But within the loop, we do a more careful regex on the class name
-  // and only proceed if it's something sensible we can use in Prism.
-  $("pre[class*=brush]").each((_, element) => {
-    // The language is whatever string comes after the `brush(:)`
-    // portion of the class name.
-    const $pre = $(element);
-
-    const className = $pre.attr("class").toLowerCase();
-    const match = className.match(/brush:?\s*([\w_-]+)/);
-    if (!match) {
-      return;
-    }
-    let name = match[1].replace("-nolint", "");
-    if (ALIASES.has(name)) {
-      name = ALIASES.get(name);
-    }
-    if (IGNORE.has(name)) {
-      // Seems to exist a couple of these in our docs. Just bail.
-      return;
-    }
-    const code = $pre.text();
-    if (SAMPLE_SIGN_KEY) {
-      const hmac = createHmac("sha256", SAMPLE_SIGN_KEY);
-      hmac.update(name.toLowerCase());
-      hmac.update(code);
-      const signature = hmac.digest("base64");
-      $pre.attr("data-signature", signature);
-    }
-    $pre.wrapAll(`
`); - if (!$pre.hasClass("hidden")) { - $( - `
${name}
` - ).insertBefore($pre); - } - const grammar = Prism.languages[name]; - if (!grammar) { - console.warn( - `Unable to find a Prism grammar for '${name}' found in ${doc.mdn_url}` - ); - return; // bail! - } - const html = Prism.highlight(code, grammar, name); - const $code = $("").html(html); - - $pre.empty().append($code); - }); -} diff --git a/client/config/webpack.config.js b/client/config/webpack.config.js index 92906a67a3b3..52a17f0098ba 100644 --- a/client/config/webpack.config.js +++ b/client/config/webpack.config.js @@ -395,31 +395,16 @@ function config(webpackEnv) { }, plugins: [ // Generates an `index.html` file with the
diff --git a/client/pwa/package.json b/client/pwa/package.json index 5781d96ea711..31760e323aef 100644 --- a/client/pwa/package.json +++ b/client/pwa/package.json @@ -18,7 +18,7 @@ "devDependencies": { "@types/dexie": "1.3.1", "ts-loader": "^9.5.1", - "typescript": "^5.5.4", + "typescript": "^5.6.2", "webpack": "^5.94.0", "webpack-cli": "^5.1.4", "workers-preview": "^1.0.6" diff --git a/client/pwa/yarn.lock b/client/pwa/yarn.lock index c2ab0cb2f372..95a6ebb39980 100644 --- a/client/pwa/yarn.lock +++ b/client/pwa/yarn.lock @@ -826,10 +826,10 @@ ts-loader@^9.5.1: semver "^7.3.4" source-map "^0.7.4" -typescript@^5.5.4: - version "5.5.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba" - integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q== +typescript@^5.6.2: + version "5.6.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.2.tgz#d1de67b6bef77c41823f822df8f0b3bcff60a5a0" + integrity sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw== update-browserslist-db@^1.0.13: version "1.0.13" diff --git a/client/scripts/build.js b/client/scripts/build.js index 3f1091c13e84..fac73d1b79fd 100644 --- a/client/scripts/build.js +++ b/client/scripts/build.js @@ -1,14 +1,12 @@ // Ensure environment variables are read. import "../config/env.js"; -import path from "node:path"; import chalk from "chalk"; import fs from "fs-extra"; import webpack from "webpack"; import configFactory from "../config/webpack.config.js"; import paths from "../config/paths.js"; -import { hashSomeStaticFilesForClientBuild } from "./postprocess-client-build.js"; // Makes the script crash on unhandled rejections instead of silently // ignoring them. In the future, promise rejections that are not handled will @@ -37,17 +35,6 @@ build() process.exit(1); } ) - .then(async () => { - const { results } = await hashSomeStaticFilesForClientBuild(paths.appBuild); - console.log( - chalk.green( - `Hashed ${results.length} files in ${path.join( - paths.appBuild, - "index.html" - )}` - ) - ); - }) .catch((err) => { if (err && err.message) { console.log(err.message); diff --git a/client/scripts/postprocess-client-build.js b/client/scripts/postprocess-client-build.js deleted file mode 100644 index 446f27d6420e..000000000000 --- a/client/scripts/postprocess-client-build.js +++ /dev/null @@ -1,121 +0,0 @@ -/** - * This script does all the necessary things the `yarn client:build` - * (react-scripts) can't do. - * - */ -import fs from "node:fs"; -import path from "node:path"; - -import cheerio from "cheerio"; -import md5File from "md5-file"; - -export async function hashSomeStaticFilesForClientBuild(buildRoot) { - const indexHtmlFilePath = path.join(buildRoot, "index.html"); - const indexHtml = fs.readFileSync(indexHtmlFilePath, "utf-8"); - - const results = []; - - // For every favicon referred there, change it to a file URL that - // has a hash in it. - const $ = cheerio.load(indexHtml); - $('link[rel], meta[property="og:image"]').each((i, element) => { - let href; - let attributeKey; - let hrefPrefix = ""; - if (element.tagName === "meta") { - if (element.attribs.property !== "og:image") { - return; - } - // This is a can of worms. Using from environment for now. - // We need to use an absolute URL for "og:image". - hrefPrefix = process.env.BASE_URL || ""; - href = element.attribs.content; - attributeKey = "content"; - } else { - href = element.attribs.href; - if (!href) { - return; - } - const rel = element.attribs.rel; - if ( - ![ - "icon", - "shortcut icon", - "apple-touch-icon", - "apple-touch-icon-precomposed", - "manifest", - ].includes(rel) - ) { - return; - } - attributeKey = "href"; - } - - // If this script is, for some reason, already run before we can - // bail if it looks like the href already is hashed. - if (/\.[a-f0-9]{8}\./.test(href)) { - console.warn(`Looks like ${href} is already hashed`); - return; - } - const filePath = hrefToFilePath(buildRoot, href); - if (!filePath || !fs.existsSync(filePath)) { - console.warn(`Unable to turn '${href}' into a valid file path`); - return; - } - // 8 because that's what react-scripts (which uses webpack somehow) - // uses to create those `build/static/**/*` files it builds. - const hash = md5File.sync(filePath).slice(0, 8); - const extName = path.extname(filePath); - const splitName = filePath.split(extName); - const hashedFilePath = `${splitName[0]}.${hash}${extName}`; - fs.copyFileSync(filePath, hashedFilePath); - const hashedHref = filePathToHref(buildRoot, hashedFilePath, href); - results.push({ - filePath, - href, - url: hrefPrefix + hashedHref, - hashedFilePath, - attributeKey, - }); - }); - - if (results.length > 0) { - // It clearly hashed some files. Let's update the HTML! - let newIndexHtml = indexHtml; - for (const { href, url, attributeKey } of results) { - newIndexHtml = newIndexHtml.replace( - new RegExp(`${attributeKey}="${href}"`), - `${attributeKey}="${url}"` - ); - } - fs.writeFileSync(indexHtmlFilePath, newIndexHtml, "utf-8"); - } - - return { results }; -} - -// Turn 'C:\Path\to\client\build\favicon.ico' to '/favicon.ico' -// or 'https://foo.bar/favicon.ico' if href is an absolute URL. -function filePathToHref(root, filePath, href) { - let dummyOrExistingUrl = new URL(href, "http://localhost.example"); - dummyOrExistingUrl.pathname = ""; - let url = new URL( - `${filePath.replace(root, "").replace(path.sep, "/")}`, - dummyOrExistingUrl - ); - if (url.hostname === "localhost.example") { - return url.pathname; - } else { - return url.href; - } -} - -// Turn '/favicon.ico' to 'C:\Path\to\client\build\favicon.ico' -function hrefToFilePath(root, href) { - // The href is always expected to start with a `/` which is part of a - // URL and not a file path. - const pathname = new URL(href, "http://localhost.example").pathname; - if (pathname.startsWith("/")) { - return path.join(root, pathname.slice(1).replace(/\//g, path.sep)); - } -} diff --git a/client/src/blog/post.tsx b/client/src/blog/post.tsx index 75282a72fc3d..4f817184489b 100644 --- a/client/src/blog/post.tsx +++ b/client/src/blog/post.tsx @@ -14,10 +14,7 @@ import { BlogPostLimitedMetadata, AuthorMetadata, } from "../../../libs/types/blog"; -import { - useCopyExamplesToClipboardAndAIExplain, - useRunSample, -} from "../document/hooks"; +import { useDecorateCodeExamples, useRunSample } from "../document/hooks"; import { DEFAULT_LOCALE } from "../../../libs/constants"; import { SignUpSection as NewsletterSignUp } from "../newsletter"; import { TOC } from "../document/organisms/toc"; @@ -190,7 +187,7 @@ export function BlogPost(props: HydrationData) { ); const { doc, blogMeta } = data || props || {}; useRunSample(doc); - useCopyExamplesToClipboardAndAIExplain(doc); + useDecorateCodeExamples(doc); return ( <> {doc && blogMeta && ( diff --git a/client/src/document/code/syntax-highlight.tsx b/client/src/document/code/syntax-highlight.tsx new file mode 100644 index 000000000000..d60461d0502c --- /dev/null +++ b/client/src/document/code/syntax-highlight.tsx @@ -0,0 +1,168 @@ +import Prism from "prismjs"; +import components from "prismjs/components"; +import { useMemo, useState, useEffect } from "react"; + +Prism.manual = true; + +const PRISM_LANGUAGES = components.languages as Record< + string, + { + alias?: string | string[]; + require?: string | string[]; + optional?: string | string[]; + [key: string]: any; + } +>; + +// Add things to this list to help make things convenient. Sometimes +// there are `
` whose name is not that which
+// Prism expects. It'd be hard to require that content writers
+// have to stick to the exact naming conventions that Prism uses
+// because Prism is an implementation detail.
+const ALIASES = new Map([
+  ["vue", "markup"], // See https://github.com/PrismJS/prism/issues/1665#issuecomment-536529608
+  ...Object.entries(PRISM_LANGUAGES).flatMap(([lang, config]) => {
+    if (config.alias) {
+      const aliases =
+        typeof config.alias === "string" ? [config.alias] : config.alias;
+      return aliases.map((alias) => [alias, lang] satisfies [string, string]);
+    }
+    return [];
+  }),
+]);
+
+interface HighlightedCodeProps extends React.HTMLAttributes {
+  language?: string;
+  children: React.ReactNode;
+}
+
+export function CodeWithSyntaxHighlight({
+  language,
+  children,
+  ...props
+}: HighlightedCodeProps) {
+  const initial = useMemo(
+    // needed to prevent flashing
+    () =>
+      language ? highlightStringSync(String(children), language) : undefined,
+    [children, language]
+  );
+  const [html, setHtml] = useState(initial);
+
+  useEffect(() => {
+    (async () => {
+      if (language) {
+        const highlighted = await highlightString(String(children), language);
+        setHtml(highlighted);
+      }
+    })();
+  }, [children, language]);
+
+  return html ? (
+    
+  ) : (
+    {children}
+  );
+}
+
+export async function highlightElement(element: Element, language: string) {
+  const highlighted = await highlightString(
+    element.textContent || "",
+    language
+  );
+  if (highlighted) {
+    element.innerHTML = `${highlighted}`;
+  }
+}
+
+async function highlightString(
+  text: string,
+  language: string
+): Promise {
+  const resolvedLanguage = ALIASES.get(language) || language;
+
+  try {
+    await importLanguage(resolvedLanguage);
+  } catch {
+    return;
+  }
+
+  return highlightStringSync(text, language);
+}
+
+function highlightStringSync(
+  text: string,
+  language: string
+): string | undefined {
+  const resolvedLanguage = ALIASES.get(language) || language;
+  const prismLanguage = Prism.languages[resolvedLanguage];
+  if (prismLanguage) {
+    try {
+      return Prism.highlight(text, prismLanguage, resolvedLanguage);
+    } catch {
+      console.warn("Syntax highlighting: prism error");
+    }
+  }
+  return;
+}
+
+async function importLanguage(language: string, recursiveDepth = 0) {
+  if (recursiveDepth > 100) {
+    console.warn("Syntax highlighting: recursion error");
+    throw new Error("Syntax highlighting: recursion error");
+  }
+
+  const prismLanguage = Prism.languages[language];
+
+  if (!prismLanguage) {
+    if (language === "svelte") {
+      try {
+        await import(
+          /* webpackChunkName: "prism-svelte" */
+          "prism-svelte"
+        );
+      } catch (e) {
+        console.warn(
+          `Syntax highlighting: failed to import ${language} prism language`
+        );
+        throw e;
+      }
+    } else {
+      const config = PRISM_LANGUAGES[language];
+      if (config.require) {
+        try {
+          await Promise.all(
+            (typeof config.require === "string"
+              ? [config.require]
+              : config.require
+            ).map((dependency) =>
+              importLanguage(dependency, recursiveDepth + 1)
+            )
+          );
+        } catch {
+          return;
+        }
+      }
+      if (config.optional) {
+        await Promise.allSettled(
+          (typeof config.optional === "string"
+            ? [config.optional]
+            : config.optional
+          ).map((dependency) => importLanguage(dependency, recursiveDepth + 1))
+        );
+      }
+      try {
+        await import(
+          /* webpackChunkName: "[request]" */
+          /* webpackExclude: /\.min\.js$/ */
+          `prismjs/components/prism-${language}.js`
+        );
+      } catch (e) {
+        console.warn(
+          `Syntax highlighting: failed to import ${language} prism language`
+        );
+        throw e;
+      }
+    }
+  }
+}
diff --git a/client/src/document/hooks.ts b/client/src/document/hooks.ts
index 38c9114cea10..c75aa69515a5 100644
--- a/client/src/document/hooks.ts
+++ b/client/src/document/hooks.ts
@@ -95,15 +95,11 @@ export function useRunSample(doc: Doc | undefined) {
     });
   }, [doc, isServer, locale]);
 }
-export function useCopyExamplesToClipboardAndAIExplain(doc: Doc | undefined) {
+
+export function useDecorateCodeExamples(doc: Doc | undefined) {
   const location = useLocation();
-  const isServer = useIsServer();
 
   useEffect(() => {
-    if (isServer) {
-      return;
-    }
-
     if (!doc) {
       return;
     }
@@ -122,8 +118,14 @@ export function useCopyExamplesToClipboardAndAIExplain(doc: Doc | undefined) {
         } else {
           addCopyToClipboardButton(element, header);
         }
+        import("./code/syntax-highlight").then(({ highlightElement }) => {
+          highlightElement(
+            element,
+            header?.querySelector(".language-name")?.textContent || "plain"
+          );
+        });
       });
-  }, [doc, location, isServer]);
+  }, [doc, location]);
 }
 
 /**
diff --git a/client/src/document/index.tsx b/client/src/document/index.tsx
index bc19fe391833..52ab4700bb75 100644
--- a/client/src/document/index.tsx
+++ b/client/src/document/index.tsx
@@ -6,11 +6,7 @@ import { WRITER_MODE, PLACEMENT_ENABLED } from "../env";
 import { useGA } from "../ga-context";
 import { useIsServer, useLocale } from "../hooks";
 
-import {
-  useDocumentURL,
-  useCopyExamplesToClipboardAndAIExplain,
-  useRunSample,
-} from "./hooks";
+import { useDocumentURL, useDecorateCodeExamples, useRunSample } from "./hooks";
 import { Doc } from "../../../libs/types/document";
 // Ingredients
 import { Prose } from "./ingredients/prose";
@@ -124,7 +120,7 @@ export function Document(props /* TODO: define a TS interface for this */) {
   useIncrementFrequentlyViewed(doc);
   useRunSample(doc);
   //useCollectSample(doc);
-  useCopyExamplesToClipboardAndAIExplain(doc);
+  useDecorateCodeExamples(doc);
   useInteractiveExamplesTelemetry();
 
   React.useEffect(() => {
diff --git a/client/src/document/organisms/sidebar/index.scss b/client/src/document/organisms/sidebar/index.scss
index 596ca39da4e7..93318a12676b 100644
--- a/client/src/document/organisms/sidebar/index.scss
+++ b/client/src/document/organisms/sidebar/index.scss
@@ -63,17 +63,15 @@
       padding-left: 0.5rem;
     }
 
-    ol {
-      li {
-        .icon {
-          margin-right: 0.01em;
-        }
+    li {
+      .icon {
+        margin-right: 0.01em;
+      }
 
-        &.no-bullet {
-          display: block;
-          font-weight: var(--font-body-strong-weight);
-          list-style-type: none;
-        }
+      &.no-bullet {
+        display: block;
+        font-weight: var(--font-body-strong-weight);
+        list-style-type: none;
       }
     }
   }
diff --git a/client/src/observatory/results.tsx b/client/src/observatory/results.tsx
index 7667b6244281..5a2002c2b9fd 100644
--- a/client/src/observatory/results.tsx
+++ b/client/src/observatory/results.tsx
@@ -103,7 +103,7 @@ export default function ObservatoryResults() {
           
           
- { || null} +
{hasData && !combinedError && ( diff --git a/client/src/playground/editor.tsx b/client/src/playground/editor.tsx index 42623df728a3..78c28a53ac31 100644 --- a/client/src/playground/editor.tsx +++ b/client/src/playground/editor.tsx @@ -77,7 +77,10 @@ function cmExtensions(colorScheme: string, language: string) { ]; } -const Editor = forwardRef(function EditorInner( +const Editor = forwardRef< + EditorHandle, + { language: string; callback: () => void } +>(function EditorInner( { language, callback = () => {}, diff --git a/client/src/playground/index.scss b/client/src/playground/index.scss index 82bd16faf519..a81f059e6552 100644 --- a/client/src/playground/index.scss +++ b/client/src/playground/index.scss @@ -258,7 +258,11 @@ main.play { > div.content { align-items: end; - background: linear-gradient(to left, #111 16rem, transparent); + background: linear-gradient( + to left, + var(--place-new-side-background) 16rem, + transparent + ); flex-direction: column; height: 100%; justify-content: end; diff --git a/client/src/plus/ai-help/index.tsx b/client/src/plus/ai-help/index.tsx index 9f44827e36c0..d0fb7d464d80 100644 --- a/client/src/plus/ai-help/index.tsx +++ b/client/src/plus/ai-help/index.tsx @@ -1,4 +1,3 @@ -import Prism from "prismjs"; import { Children, MutableRefObject, @@ -59,6 +58,7 @@ import { } from "./constants"; import InternalLink from "../../ui/atoms/internal-link"; import { isPlusSubscriber } from "../../utils"; +import { CodeWithSyntaxHighlight } from "../../document/code/syntax-highlight"; type Category = "apis" | "css" | "html" | "http" | "js" | "learn"; @@ -482,19 +482,15 @@ function AIHelpAssistantResponse({ }, code: ({ className, children, ...props }) => { const match = /language-(\w+)/.exec(className || ""); - const lang = Prism.languages[match?.[1]]; - return lang ? ( - - ) : ( - + {...props} + > {children} - + ); }, }} diff --git a/client/src/plus/ai-help/use-ai.ts b/client/src/plus/ai-help/use-ai.ts index a5d80f33cb3e..e94c8b3e6009 100644 --- a/client/src/plus/ai-help/use-ai.ts +++ b/client/src/plus/ai-help/use-ai.ts @@ -408,8 +408,20 @@ export function useAiChat({ }); }, [setSearchParams, chatId]); + const removeChatIdFromUrl = useCallback(() => { + setSearchParams( + (prev) => { + prev.delete("c"); + return prev; + }, + { replace: true } + ); + }, [setSearchParams]); + useEffect(() => { if (!isHistoryEnabled) { + // If we got a chat id passed in without history enabled, clear parameters from URL + removeChatIdFromUrl(); return; } let timeoutID; @@ -452,12 +464,19 @@ export function useAiChat({ } } catch (e) { setPreviousChatId(undefined); - setChatId(convId); setPath([]); dispatchState({ type: "reset", }); - handleError(e); + // If we got a 404 from the API, reset and remove the parameter from the URL, + // do not show an error. + if (e instanceof Error && e.message.startsWith("404")) { + setChatId(undefined); + removeChatIdFromUrl(); + } else { + setChatId(convId); + handleError(e); + } } setIsHistoryLoading(false); }; @@ -467,7 +486,14 @@ export function useAiChat({ if (r) { reset(); } - }, [isHistoryEnabled, searchParams, chatId, reset, handleError]); + }, [ + isHistoryEnabled, + removeChatIdFromUrl, + searchParams, + chatId, + reset, + handleError, + ]); useEffect(() => { if (remoteQuota !== undefined) { diff --git a/client/src/ui/molecules/tools-menu/index.tsx b/client/src/ui/molecules/tools-menu/index.tsx index 67bf16d06219..ce1a83b316e2 100644 --- a/client/src/ui/molecules/tools-menu/index.tsx +++ b/client/src/ui/molecules/tools-menu/index.tsx @@ -9,11 +9,7 @@ export const ToolsMenu = ({ visibleSubMenuId, toggleMenu }) => { const menu = { id: "tools", - label: ( - <> - Tools New - - ), + label: "Tools", items: [ { description: "Write, test and share your code", @@ -28,7 +24,6 @@ export const ToolsMenu = ({ visibleSubMenuId, toggleMenu }) => { iconClasses: "submenu-icon", label: OBSERVATORY_TITLE, url: `/en-US/observatory`, - dot: "New", }, { description: "Get real-time assistance and support", diff --git a/client/src/ui/organisms/placement/index.scss b/client/src/ui/organisms/placement/index.scss index 3ebdfa10ea50..867a8c1d8c0d 100644 --- a/client/src/ui/organisms/placement/index.scss +++ b/client/src/ui/organisms/placement/index.scss @@ -272,6 +272,10 @@ section.place { width: initial; } } + + html[data-nop] & { + display: none; + } } .dark section.place .pong-box2 { diff --git a/client/src/ui/organisms/placement/index.tsx b/client/src/ui/organisms/placement/index.tsx index 53850d0fcf4c..e9f7a051ceb3 100644 --- a/client/src/ui/organisms/placement/index.tsx +++ b/client/src/ui/organisms/placement/index.tsx @@ -92,7 +92,22 @@ export function SidePlacement() { function TopPlacementFallbackContent() { const gleanClick = useGleanClick(); - return ( + return Date.now() < Date.parse("2024-10-12") ? ( +

+ Learn front-end development with a 30% discount on{" "} + { + gleanClick(BANNER_SCRIMBA_CLICK); + }} + > + Scrimba + {" "} + — limited time offer! +

+ ) : (

Learn front-end development with high quality, interactive courses from{" "} = 1.4.16" } }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1318,12 +1334,19 @@ } }, "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "license": "MIT", "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -1578,6 +1601,23 @@ "ms": "2.0.0" } }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/define-properties": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", @@ -1704,6 +1744,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-set-tostringtag": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", @@ -1752,6 +1813,7 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -1762,36 +1824,37 @@ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.20.0.tgz", + "integrity": "sha512-pLdae7I6QqShF5PnNTCVn4hI91Dx0Grkn2+IAsMTgMIKuQVte2dN9PeGSSAME2FR8anOhVA62QDIUaWVfEXVLw==", + "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "1.2.0", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", "qs": "6.11.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.0", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -1810,6 +1873,15 @@ "node": ">= 0.6" } }, + "node_modules/express/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -1909,6 +1981,7 @@ "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -1928,9 +2001,13 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/function.prototype.name": { "version": "1.1.5", @@ -1960,13 +2037,19 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", - "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "license": "MIT", "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2061,12 +2144,12 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "dev": true, + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", "dependencies": { - "get-intrinsic": "^1.1.1" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2076,7 +2159,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -2109,6 +2191,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -2768,9 +2862,13 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/methods": { "version": "1.1.2", @@ -3035,9 +3133,13 @@ } }, "node_modules/object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -3217,9 +3319,10 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", + "license": "MIT" }, "node_modules/path-type": { "version": "3.0.0", @@ -3411,6 +3514,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -3627,9 +3731,10 @@ } }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -3652,12 +3757,14 @@ "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.0.tgz", + "integrity": "sha512-pDLK8zwl2eKaYrs8mrPZBJua4hMplRWJ1tIFksVC3FtBEBnl8dxgeHtsaMS8DhS9i4fLObaon6ABoc4/hQGdPA==", + "license": "MIT", "dependencies": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", @@ -3668,6 +3775,53 @@ "node": ">= 0.8.0" } }, + "node_modules/serve-static/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static/node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -3709,13 +3863,18 @@ "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==" }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" diff --git a/deployer/poetry.lock b/deployer/poetry.lock index 2f83222fb857..bde9d0101824 100644 --- a/deployer/poetry.lock +++ b/deployer/poetry.lock @@ -48,17 +48,17 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "boto3" -version = "1.35.14" +version = "1.35.19" description = "The AWS SDK for Python" optional = false python-versions = ">=3.8" files = [ - {file = "boto3-1.35.14-py3-none-any.whl", hash = "sha256:c3e138e9041d59cd34cdc28a587dfdc899dba02ea26ebc3e10fb4bc88e5cf31b"}, - {file = "boto3-1.35.14.tar.gz", hash = "sha256:7bc78d7140c353b10a637927fe4bc4c4d95a464d1b8f515d5844def2ee52cbd5"}, + {file = "boto3-1.35.19-py3-none-any.whl", hash = "sha256:84b3fe1727945bc3cada832d969ddb3dc0d08fce1677064ca8bdc13a89c1a143"}, + {file = "boto3-1.35.19.tar.gz", hash = "sha256:9979fe674780a0b7100eae9156d74ee374cd1638a9f61c77277e3ce712f3e496"}, ] [package.dependencies] -botocore = ">=1.35.14,<1.36.0" +botocore = ">=1.35.19,<1.36.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.10.0,<0.11.0" @@ -67,13 +67,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.35.14" +version = "1.35.19" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.8" files = [ - {file = "botocore-1.35.14-py3-none-any.whl", hash = "sha256:24823135232f88266b66ae8e1d0f3d40872c14cd976781f7fe52b8f0d79035a0"}, - {file = "botocore-1.35.14.tar.gz", hash = "sha256:8515a2fc7ca5bcf0b10016ba05ccf2d642b7cb77d8773026ff2fa5aa3bf38d2e"}, + {file = "botocore-1.35.19-py3-none-any.whl", hash = "sha256:c83f7f0cacfe7c19b109b363ebfa8736e570d24922f16ed371681f58ebab44a9"}, + {file = "botocore-1.35.19.tar.gz", hash = "sha256:42d6d8db7250cbd7899f786f9861e02cab17dc238f64d6acb976098ed9809625"}, ] [package.dependencies] @@ -82,7 +82,7 @@ python-dateutil = ">=2.1,<3.0.0" urllib3 = {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""} [package.extras] -crt = ["awscrt (==0.21.2)"] +crt = ["awscrt (==0.21.5)"] [[package]] name = "certifi" @@ -634,13 +634,13 @@ tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] [[package]] name = "pytest" -version = "8.3.2" +version = "8.3.3" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, - {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, + {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, + {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, ] [package.dependencies] @@ -931,4 +931,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "30854a8b32ec6583dd9579ef6bdde268a6969ed5e5d00671cb288a53a9f94df3" +content-hash = "c01b8f3c14a209010ec86754e0e1adde504fd8ce69065da03505cc4cc0e1f0fe" diff --git a/deployer/pyproject.toml b/deployer/pyproject.toml index 09a0d7c24f93..eefead0ecd9b 100644 --- a/deployer/pyproject.toml +++ b/deployer/pyproject.toml @@ -10,7 +10,7 @@ readme = "README.md" [tool.poetry.dependencies] python = "^3.10" click = "^8.1.7" -boto3 = "^1.35.14" +boto3 = "^1.35.19" python-decouple = "^3.8" requests = {extras = ["security"], version = "^2.32.3"} elasticsearch-dsl = "^7.4.1" @@ -21,7 +21,7 @@ unidiff = "^0.7.5" [tool.poetry.dev-dependencies] black = "^24.8" flake8 = "^7.1.1" -pytest = "^8.3.2" +pytest = "^8.3.3" [tool.poetry.scripts] deployer = "deployer.main:cli" diff --git a/kumascript/macros/DOMAttributeMethods.ejs b/kumascript/macros/DOMAttributeMethods.ejs deleted file mode 100644 index e6a931821c8b..000000000000 --- a/kumascript/macros/DOMAttributeMethods.ejs +++ /dev/null @@ -1,81 +0,0 @@ -<% -// Removed from mdn/content in https://github.com/mdn/content/pull/32268 -mdn.deprecated(); - -var tableTitle = 'DOM methods dealing with element\'s attributes:'; -var tableHead1 = 'Not namespace-aware, most commonly used methods'; -var tableHead2 = 'Namespace-aware variants (DOM Level 2)'; -var tableHead3 = 'DOM Level 1 methods for dealing with Attr nodes directly (seldom used)'; -var tableHead4 = 'DOM Level 2 namespace-aware methods for dealing with Attr nodes directly (seldom used)'; - -switch(env.locale) { - case 'es': - tableTitle = 'Métodos DOM que tratan con atributos de elementos:'; - break; - case 'fr': - tableTitle = 'Méthodes DOM traitant des attributs sur les éléments :'; - tableHead1 = 'DOM Level 1 (le plus courant)'; - tableHead2 = 'DOM Level 2'; - tableHead3 = 'DOM Level 1 traitant directement les nœuds Attr'; - tableHead4 = 'DOM Level 2 traitant directement les nœuds Attr'; - break; - case 'ja': - tableTitle = 'DOM メソッドは要素の属性を取り扱います。'; - tableHead1 = '名前空間に無関係、
最も一般的に使用されるメソッド'; - tableHead2 = '名前空間に限定される変数
(DOM Level 2)'; - tableHead3 = 'Attr ノードを直接扱う DOM レベル 1 のメソッド
(ほとんど使用されない)'; - tableHead4 = 'Attr ノードを直接扱う DOM レベル 2 名前空間に限定されるメソッド
(ほとんど使用されない)'; - break; - case 'ru': - tableTitle = 'Методы DOM имеют дело с атрибутами элементов:', - tableHead1 = 'Не знают пространства имён, наиболее часто используемые методы', - tableHead2 = 'Вариант, знающий пространство имён (Уровень DOM 2)', - tableHead3 = 'Уровень DOM 1 методы для работы с Attr узлами напрямую (используется редко)', - tableHead4 = 'Уровень DOM 2 знает о методах пространства имён для работы с Attr узлами напрямую (используется редко)'; - break; - case 'ko': - tableTitle = '요소의 특성을 처리하는 DOM 메서드입니다.'; - tableHead1 = '네임스페이스 인식이 아닌, 가장 일반적으로 사용되는 메서드'; - tableHead2 = '네임스페이스 인식 변형 (DOM Level 2)'; - tableHead3 = 'Attr 노드를 직접 처리하는 DOM Level 1 메서드 (거의 사용되지 않음)'; - tableHead4 = 'Attr 노드를 직접 처리하는 DOM Level 2 네임스페이스 인식 메서드 (거의 사용되지 않음)'; - default: break; -} -%> -

<%- tableTitle %>

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
<%- tableHead1 %><%- tableHead2 %><%- tableHead3 %><%- tableHead4 %>
<%- await template("domxref",["element.setAttribute","setAttribute"]) %> (DOM 1)<%- await template("domxref",["element.setAttributeNS","setAttributeNS"]) %><%- await template("domxref",["element.setAttributeNode","setAttributeNode"]) %><%- await template("domxref",["element.setAttributeNodeNS","setAttributeNodeNS"]) %>
<%- await template("domxref",["element.getAttribute","getAttribute"]) %> (DOM 1)<%- await template("domxref",["element.getAttributeNS","getAttributeNS"]) %><%- await template("domxref",["element.getAttributeNode","getAttributeNode"]) %><%- await template("domxref",["element.getAttributeNodeNS","getAttributeNodeNS"]) %>
<%- await template("domxref",["element.hasAttribute","hasAttribute"]) %> (DOM 2)<%- await template("domxref",["element.hasAttributeNS","hasAttributeNS"]) %>--
<%- await template("domxref",["element.removeAttribute","removeAttribute"]) %> (DOM 1)<%- await template("domxref",["element.removeAttributeNS","removeAttributeNS"]) %><%- await template("domxref",["element.removeAttributeNode","removeAttributeNode"]) %>-
diff --git a/kumascript/macros/unimplemented_inline.ejs b/kumascript/macros/unimplemented_inline.ejs deleted file mode 100644 index db29b937407c..000000000000 --- a/kumascript/macros/unimplemented_inline.ejs +++ /dev/null @@ -1 +0,0 @@ -<% mdn.deprecated(); %> <%- await template("unimplementedGeneric", ["inline",$0]) %> diff --git a/kumascript/tests/lib/__snapshots__/css-syntax.test.ts.snap b/kumascript/tests/lib/__snapshots__/css-syntax.test.ts.snap index f541c96c2c25..ef311f2859f1 100644 --- a/kumascript/tests/lib/__snapshots__/css-syntax.test.ts.snap +++ b/kumascript/tests/lib/__snapshots__/css-syntax.test.ts.snap @@ -4,7 +4,7 @@ exports[`CSSSyntax renders at-rule: @import 1`] = `"
src = 
<font-src-list>

"`; -exports[`CSSSyntax renders function: polygon 1`] = `"
<polygon()> = 
polygon( <'fill-rule'>? , [ <length-percentage> <length-percentage> ]# )

<fill-rule> =
nonzero |
evenodd

<length-percentage> =
<length> |
<percentage>

"`; +exports[`CSSSyntax renders function: polygon 1`] = `"
<polygon()> = 
polygon( <'fill-rule'>? [ round <length> ]? , [ <length-percentage> <length-percentage> ]# )

<fill-rule> =
nonzero |
evenodd

<length-percentage> =
<length> |
<percentage>

"`; exports[`CSSSyntax renders function: sin 1`] = `"
<sin()> = 
sin( <calc-sum> )

<calc-sum> =
<calc-product> [ [ '+' | '-' ] <calc-product> ]*

<calc-product> =
<calc-value> [ [ '*' | '/' ] <calc-value> ]*

<calc-value> =
<number> |
<dimension> |
<percentage> |
<calc-keyword> |
( <calc-sum> )

<calc-keyword> =
e |
pi |
infinity |
-infinity |
NaN

"`; diff --git a/libs/constants/index.js b/libs/constants/index.js index cf445f4149f2..a6fd9289b8ad 100644 --- a/libs/constants/index.js +++ b/libs/constants/index.js @@ -74,7 +74,7 @@ export const CSP_SCRIPT_SRC_VALUES = [ "https://js.stripe.com", /* - * Inline scripts (defined in `client/public/index.html`). + * Inline scripts (imported in `ssr/render.tsx`). * * If we modify them, we must always update their CSP hash here. * diff --git a/package.json b/package.json index d56fe0c66266..f2745894b429 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@mdn/yari", - "version": "2.62.0", + "version": "2.63.1", "repository": "https://github.com/mdn/yari", "license": "MPL-2.0", "author": "MDN Web Docs", @@ -24,7 +24,7 @@ "build:docs": "cross-env NODE_ENV=production NODE_OPTIONS='--no-warnings=ExperimentalWarning --loader ts-node/esm' node build/cli.ts -n", "build:glean": "cd client && cross-env VIRTUAL_ENV=venv glean translate src/telemetry/metrics.yaml src/telemetry/pings.yaml -f typescript -o src/telemetry/generated", "build:prepare": "yarn build:client && yarn build:ssr && yarn tool popularities && yarn tool spas && yarn tool gather-git-history && yarn tool build-robots-txt", - "build:ssr": "cross-env NODE_ENV=production NODE_OPTIONS='--no-warnings=ExperimentalWarning --loader ts-node/esm' node ssr/prepare.ts && cd ssr && webpack --mode=production", + "build:ssr": "cross-env NODE_ENV=production NODE_OPTIONS='--no-warnings=ExperimentalWarning --loader ts-node/esm' node ssr/prepare.ts && webpack --mode=production --config=ssr/webpack.config.js", "build:sw": "cd client/pwa && yarn && yarn build:prod", "build:sw-dev": "cd client/pwa && yarn && yarn build", "check:tsc": "find . -name 'tsconfig.json' ! -wholename '**/node_modules/**' -print0 | xargs -n1 -P 2 -0 sh -c 'cd `dirname $0` && echo \"🔄 $(pwd)\" && npx tsc --noEmit && echo \"☑️ $(pwd)\" || exit 255'", @@ -40,7 +40,7 @@ "prettier-check": "prettier --check .", "prettier-format": "prettier --write .", "render:html": "cross-env NODE_ENV=production NODE_OPTIONS='--no-warnings=ExperimentalWarning --loader ts-node/esm' node build/ssr-cli.ts", - "start": "(test -f client/build/index.html || yarn build:client) && (test -f ssr/dist/main.js || yarn build:ssr) && (test -f popularities.json || yarn tool popularities) && (test -d client/build/en-us/_spas || yarn tool spas) && nf -j Procfile.start start", + "start": "(test -f client/build/asset-manifest.json || yarn build:client) && (test -f ssr/dist/main.js || yarn build:ssr) && (test -f popularities.json || yarn tool popularities) && (test -d client/build/en-us/_spas || yarn tool spas) && nf -j Procfile.start start", "start:client": "cd client && cross-env NODE_ENV=development BABEL_ENV=development PORT=3000 node scripts/start.js", "start:server": "node-dev --experimental-loader ts-node/esm server/index.ts", "start:static-server": "cross-env NODE_OPTIONS='--no-warnings=ExperimentalWarning --loader ts-node/esm' node server/static.ts", @@ -55,7 +55,7 @@ "test:prepare": "yarn build:prepare && yarn build:docs && yarn render:html && yarn start:static-server", "test:testing": "yarn jest --rootDir testing", "tool": "cross-env NODE_OPTIONS='--no-warnings=ExperimentalWarning --loader ts-node/esm' node ./tool/cli.ts", - "watch:ssr": "cd ssr && webpack --mode=production --watch" + "watch:ssr": "webpack --mode=production --watch --config=ssr/webpack.config.js" }, "resolutions": { "http-cache-semantics": ">=4.1.1", @@ -71,13 +71,13 @@ "@codemirror/theme-one-dark": "^6.1.2", "@fast-csv/parse": "^5.0.0", "@mdn/bcd-utils-api": "^0.0.7", - "@mdn/browser-compat-data": "^5.5.51", + "@mdn/browser-compat-data": "^5.6.0", "@mozilla/glean": "5.0.3", "@sentry/node": "^8.29.0", - "@stripe/stripe-js": "^4.4.0", + "@stripe/stripe-js": "^4.5.0", "@use-it/interval": "^1.0.0", "@vscode/ripgrep": "^1.15.9", - "@webref/css": "^6.15.1", + "@webref/css": "^6.15.2", "accept-language-parser": "^1.5.0", "async": "^3.2.6", "chalk": "^5.3.0", @@ -93,7 +93,7 @@ "dexie": "^4.0.8", "dotenv": "^16.4.5", "ejs": "^3.1.10", - "express": "^4.19.2", + "express": "^4.21.0", "fdir": "^6.3.0", "feed": "^4.2.2", "file-type": "^19.5.0", @@ -116,10 +116,10 @@ "md5-file": "^5.0.0", "mdast-util-from-markdown": "^2.0.1", "mdast-util-phrasing": "^4.1.0", - "mdn-data": "^2.10.0", + "mdn-data": "^2.11.1", "open": "^10.1.0", "open-editor": "^5.0.0", - "openai": "^4.58.1", + "openai": "^4.61.1", "pg": "^8.12.0", "pgvector": "^0.2.0", "prism-svelte": "^0.5.0", @@ -138,15 +138,15 @@ "remark-rehype": "^11.1.0", "remark-stringify": "^11.0.0", "sanitize-filename": "^1.6.3", - "send": "^0.18.0", + "send": "^0.19.0", "source-map-support": "^0.5.21", "sse.js": "^2.5.0", "tempy": "^3.1.0", "unified": "^11.0.5", "unist-builder": "^4.0.0", "unist-util-visit": "^5.0.0", - "web-features": "^1.2.0", - "web-specs": "^3.20.0" + "web-features": "^1.3.0", + "web-specs": "^3.21.0" }, "devDependencies": { "@babel/core": "^7.25.2", @@ -155,24 +155,25 @@ "@babel/preset-env": "^7.25.4", "@mdn/dinocons": "^0.5.5", "@mdn/minimalist": "^2.0.4", - "@playwright/test": "^1.47.0", + "@playwright/test": "^1.47.1", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.15", "@svgr/webpack": "^8.1.0", - "@swc/core": "^1.7.24", + "@swc/core": "^1.7.26", "@testing-library/react": "^15.0.7", "@types/async": "^3.2.24", "@types/cli-progress": "^3.11.6", "@types/imagemin": "^9.0.0", - "@types/jest": "^29.5.12", + "@types/jest": "^29.5.13", "@types/js-yaml": "^4.0.9", "@types/mdast": "^4.0.4", - "@types/node": "^18.19.47", - "@types/react": "^18.3.4", + "@types/node": "^18.19.50", + "@types/prismjs": "^1.26.4", + "@types/react": "^18.3.7", "@types/react-dom": "^18.3.0", "@types/react-modal": "^3.16.3", "@types/webpack-bundle-analyzer": "^4.7.0", "babel-jest": "^29.7.0", - "babel-loader": "^9.1.3", + "babel-loader": "^9.2.1", "babel-plugin-named-asset-import": "^0.3.8", "babel-preset-react-app": "^10.0.1", "bfj": "^8.0.0", @@ -183,9 +184,10 @@ "cross-env": "^7.0.3", "css-loader": "^7.1.2", "css-minimizer-webpack-plugin": "^7.0.0", + "cssnano": "^7.0.6", "diff": "^7.0.0", "downshift": "^7.6.1", - "eslint": "^8.57.0", + "eslint": "^8.57.1", "eslint-config-react-app": "^7.0.1", "eslint-gitignore": "^0.1.0", "eslint-plugin-flowtype": "^8.0.3", @@ -193,7 +195,7 @@ "eslint-plugin-jest": "^28.8.3", "eslint-plugin-jsx-a11y": "^6.10.0", "eslint-plugin-n": "^17.10.2", - "eslint-plugin-react": "^7.35.2", + "eslint-plugin-react": "^7.36.1", "eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-unicorn": "^55.0.0", "eslint-webpack-plugin": "^4.2.0", @@ -204,7 +206,7 @@ "history": "^5.2.0", "html-validate": "^8.22.0", "html-webpack-plugin": "^5.6.0", - "husky": "^9.1.5", + "husky": "^9.1.6", "identity-obj-proxy": "^3.0.0", "ignore-loader": "^0.1.2", "jest": "^29.7.0", @@ -218,7 +220,7 @@ "mini-css-extract-plugin": "^2.9.1", "node-dev": "^8.0.0", "peggy": "^4.0.3", - "postcss": "^8.4.45", + "postcss": "^8.4.47", "postcss-flexbugs-fixes": "^5.0.2", "postcss-loader": "^8.1.1", "postcss-normalize": "^13.0.0", @@ -232,7 +234,7 @@ "react-is": "^18.3.1", "react-refresh": "^0.14", "react-router": "^6.17.0", - "react-router-dom": "^6.26.1", + "react-router-dom": "^6.26.2", "remark-prettier": "^2.0.0", "resolve": "^1.22.8", "resolve-url-loader": "^5.0.0", @@ -250,12 +252,13 @@ "stylelint-prettier": "^4.1.0", "stylelint-scss": "^5.3.2", "swr": "^2.2.5", + "terser-loader": "^2.0.3", "terser-webpack-plugin": "^5.3.10", "ts-jest": "^29.2.5", "ts-loader": "^9.5.1", "ts-node": "^10.9.2", - "typescript": "^5.5.4", - "typescript-eslint": "^8.4.0", + "typescript": "^5.6.2", + "typescript-eslint": "^8.6.0", "webpack": "^5.94.0", "webpack-bundle-analyzer": "^4.10.2", "webpack-cli": "^5.1.4", diff --git a/ssr/include.d.ts b/ssr/include.d.ts index b7b76f87e0c2..894812f5d06d 100644 --- a/ssr/include.d.ts +++ b/ssr/include.d.ts @@ -1,4 +1,10 @@ -export const WEBFONT_TAGS: string; +interface AssetManifest { + files: Record; + entrypoints: string[]; +} + +export const WEBFONT_URLS: string[]; export const GTAG_PATH: null | string; export const BASE_URL: string; export const ALWAYS_ALLOW_ROBOTS: boolean; +export const ASSET_MANIFEST: AssetManifest; diff --git a/ssr/prepare.ts b/ssr/prepare.ts index fce23157a5fa..b9d05334538c 100644 --- a/ssr/prepare.ts +++ b/ssr/prepare.ts @@ -37,15 +37,6 @@ function* extractCSSURLs(css, filterFunction) { } } -function webfontTags(webfontURLs): string { - return webfontURLs - .map( - (url) => - `` - ) - .join(""); -} - function gtagScriptPath(relPath = "/static/js/gtag.js") { const filePath = relPath.split("/").slice(1).join(path.sep); if (fs.existsSync(path.join(BUILD_OUT_ROOT, filePath))) { @@ -56,16 +47,19 @@ function gtagScriptPath(relPath = "/static/js/gtag.js") { function prepare() { const webfontURLs = extractWebFontURLs(); - const tags = webfontTags(webfontURLs); const gtagPath = gtagScriptPath(); + const assetManifest = JSON.parse( + fs.readFileSync(path.join(clientBuildRoot, "asset-manifest.json"), "utf-8") + ); fs.writeFileSync( path.join(dirname, "ssr", "include.ts"), ` -export const WEBFONT_TAGS = ${JSON.stringify(tags)}; +export const WEBFONT_URLS = ${JSON.stringify(webfontURLs)}; export const GTAG_PATH = ${JSON.stringify(gtagPath)}; export const BASE_URL = ${JSON.stringify(BASE_URL)}; export const ALWAYS_ALLOW_ROBOTS = ${JSON.stringify(ALWAYS_ALLOW_ROBOTS)}; +export const ASSET_MANIFEST = ${JSON.stringify(assetManifest)}; ` ); } diff --git a/ssr/print.css b/ssr/print.css new file mode 100644 index 000000000000..de4edbf69eac --- /dev/null +++ b/ssr/print.css @@ -0,0 +1,22 @@ +.article-actions-container, +.main-menu-toggle, +.document-toc-container, +.on-github, +.sidebar, +.top-navigation-main, +.page-footer, +.top-banner, +.place, +ul.prev-next, +.language-menu { + display: none !important; +} + +.main-page-content, +.main-page-content pre { + padding: 2px; +} + +.main-page-content pre { + border-left-width: 2px; +} diff --git a/ssr/react-app.d.ts b/ssr/react-app.d.ts index 8206136fee8c..8930f7c00dc1 100644 --- a/ssr/react-app.d.ts +++ b/ssr/react-app.d.ts @@ -89,3 +89,13 @@ declare module "*.module.sass" { const classes: { readonly [key: string]: string }; export default classes; } + +declare module "*?inline" { + const source: string; + export default source; +} + +declare module "*?public" { + const src: string; + export default src; +} diff --git a/ssr/render.ts b/ssr/render.ts deleted file mode 100644 index f3fcb41ff11c..000000000000 --- a/ssr/render.ts +++ /dev/null @@ -1,252 +0,0 @@ -import { renderToString } from "react-dom/server"; -import { HydrationData } from "../libs/types/hydration"; - -import { DEFAULT_LOCALE } from "../libs/constants/index"; -import { - ALWAYS_ALLOW_ROBOTS, - BASE_URL, - WEBFONT_TAGS, - GTAG_PATH, -} from "./include"; - -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -import HTML from "../client/build/index.html?raw"; -import { getMetaDescription } from "./meta-description"; - -// When there are multiple options for a given language, this gives the -// preferred locale for that language (language => preferred locale). -const PREFERRED_LOCALE = { - pt: "pt-PT", - zh: "zh-CN", -}; - -// We should use the language tag (e.g. "zh-Hans") instead of the locale. -// This is a map of locale => language tag. -// See https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry -const LANGUAGE_TAGS = Object.freeze({ - "zh-CN": "zh-Hans", - "zh-TW": "zh-Hant", -}); - -function htmlEscape(s: string) { - if (!s) { - return s; - } - return s - .replace(/&/gim, "&") - .replace(/"/gim, """) - .replace(//gim, ">") - .replace(/'/gim, "'"); -} - -function getHrefLang(locale: string, allLocales: Array) { - // In most cases, just return the language code, removing the country - // code if present (so, for example, 'en-US' becomes 'en'). - const hreflang = locale.split("-")[0]; - - // Suppose the locale is one that is ambiguous, we need to fall back on a - // a preferred one. For example, if the document is available in 'zh-CN' and - // in 'zh-TW', we need to output something like this: - // - // - // - // But other bother if both ambigious locale-to-hreflang are present. - const preferred = PREFERRED_LOCALE[hreflang]; - if (preferred) { - // e.g. `preferred===zh-CN` if hreflang was `zh` - if (locale !== preferred) { - // e.g. `locale===zh-TW` - if (allLocales.includes(preferred)) { - // If the more preferred one was there, use the locale + region format. - return LANGUAGE_TAGS[locale] ?? locale; - } - } - } - return hreflang; -} - -const lazy = (creator) => { - let res; - let processed = false; - return (...args) => { - if (processed) return res; - res = creator.apply(this, ...args); - processed = true; - return res; - }; -}; - -// Path strings are preferred over URLs here to mitigate Webpack resolution - -const readBuildHTML = lazy(() => { - if (!HTML.includes('
')) { - throw new Error( - 'The render depends on being able to inject into
' - ); - } - const scripts: string[] = []; - const gaScriptPathName = GTAG_PATH; - if (gaScriptPathName) { - scripts.push(``); - } - - const html = HTML.replace('', () => - scripts.join("") - ); - return html; -}); - -export default function render( - renderApp, - url: string, - { - doc = null, - pageNotFound = false, - hyData = null, - pageTitle = null, - pageDescription = "", - possibleLocales = null, - locale = null, - noIndexing = false, - onlyFollow = false, - image = null, - blogMeta = null, - }: HydrationData = { url } -) { - const buildHtml = readBuildHTML(); - const rendered = renderToString(renderApp); - - const canonicalURL = `${BASE_URL}${url}`; - - let escapedPageTitle = htmlEscape(pageTitle); - let metaDescription = pageDescription; - - const hydrationData: HydrationData = { url }; - const translations: string[] = []; - if (blogMeta) { - hydrationData.blogMeta = blogMeta; - } - if (pageNotFound) { - escapedPageTitle = `🤷🏽‍♀️ Page not found | ${ - escapedPageTitle || "MDN Web Docs" - }`; - hydrationData.pageNotFound = true; - } else if (hyData) { - hydrationData.hyData = hyData; - } else if (doc) { - // Use the doc's title instead - escapedPageTitle = htmlEscape(doc.pageTitle); - - metaDescription = htmlEscape(getMetaDescription(doc)); - if (doc.summary) { - pageDescription = htmlEscape(doc.summary); - } - - hydrationData.doc = doc; - - if (doc.other_translations) { - // Note, we also always include "self" as a locale. That's why we concat - // this doc's locale plus doc.other_translations. - const thisLocale = { - locale: doc.locale, - title: doc.title, - url: doc.mdn_url, - }; - - const allTranslations = [...doc.other_translations, thisLocale]; - const allLocales = allTranslations.map((t) => t.locale); - - for (const translation of allTranslations) { - const translationURL = doc.mdn_url.replace( - `/${doc.locale}/`, - () => `/${translation.locale}/` - ); - // The locale used in `` needs to be the ISO-639-1 - // code. For example, it's "en", not "en-US". And it's "sv" not "sv-SE". - // See https://developers.google.com/search/docs/specialty/international/localized-versions#language-codes - translations.push( - `` - ); - } - } - } - - if (possibleLocales) { - hydrationData.possibleLocales = possibleLocales; - } - - const titleTag = `${escapedPageTitle || "MDN Web Docs"}`; - - // Open Graph protocol expects `language_TERRITORY` format. - const ogLocale = (locale || (doc && doc.locale) || DEFAULT_LOCALE).replace( - "-", - "_" - ); - - const og = new Map([ - ["title", escapedPageTitle], - ["url", canonicalURL], - ["locale", ogLocale], - ]); - - if (pageDescription) { - og.set("description", pageDescription); - } - - if (image) { - og.set("image", image); - } - - const root = `
${rendered}
`; - - const robotsContent = - !ALWAYS_ALLOW_ROBOTS || (doc && doc.noIndexing) || noIndexing - ? "noindex, nofollow" - : onlyFollow - ? "noindex, follow" - : ""; - const robotsMeta = robotsContent - ? `` - : ""; - const rssLink = ``; - const ssr_data = [...translations, ...WEBFONT_TAGS, rssLink, robotsMeta]; - let html = buildHtml; - html = html.replace( - ' `/g, - (_, typ, content) => { - return ``; - } - ); - if (metaDescription) { - html = html.replace(//g, () => { - return ``; - }); - } - html = html.replace("MDN Web Docs", () => `${titleTag}`); - - html = html.replace( - '', - () => (pageNotFound ? "" : ``) - ); - - html = html.replace('', () => ssr_data.join("")); - html = html.replace('
', () => root); - return html; -} diff --git a/ssr/render.tsx b/ssr/render.tsx new file mode 100644 index 000000000000..cd8164e256fd --- /dev/null +++ b/ssr/render.tsx @@ -0,0 +1,271 @@ +import { renderToString } from "react-dom/server"; +import { HydrationData } from "../libs/types/hydration"; + +import { DEFAULT_LOCALE } from "../libs/constants/index"; +import { + ALWAYS_ALLOW_ROBOTS, + BASE_URL, + WEBFONT_URLS, + GTAG_PATH, + ASSET_MANIFEST, +} from "./include"; +import { getMetaDescription } from "./meta-description"; + +import favicon from "../client/public/favicon-48x48.png?public"; +import appleIcon from "../client/public/apple-touch-icon.png?public"; +import manifest from "../client/public/manifest.json?public"; +import ogImage from "../client/public/mdn-social-share.png?public"; +import printCSS from "./print.css?inline"; +import themeJS from "./theme.js?inline"; + +// When there are multiple options for a given language, this gives the +// preferred locale for that language (language => preferred locale). +const PREFERRED_LOCALE = { + pt: "pt-PT", + zh: "zh-CN", +}; + +// We should use the language tag (e.g. "zh-Hans") instead of the locale. +// This is a map of locale => language tag. +// See https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry +const LANGUAGE_TAGS = Object.freeze({ + "zh-CN": "zh-Hans", + "zh-TW": "zh-Hant", +}); + +function getHrefLang(locale: string, allLocales: Array) { + // In most cases, just return the language code, removing the country + // code if present (so, for example, 'en-US' becomes 'en'). + const hreflang = locale.split("-")[0]; + + // Suppose the locale is one that is ambiguous, we need to fall back on a + // a preferred one. For example, if the document is available in 'zh-CN' and + // in 'zh-TW', we need to output something like this: + // + // + // + // But other bother if both ambigious locale-to-hreflang are present. + const preferred = PREFERRED_LOCALE[hreflang]; + if (preferred) { + // e.g. `preferred===zh-CN` if hreflang was `zh` + if (locale !== preferred) { + // e.g. `locale===zh-TW` + if (allLocales.includes(preferred)) { + // If the more preferred one was there, use the locale + region format. + return LANGUAGE_TAGS[locale] ?? locale; + } + } + } + return hreflang; +} + +export default function render( + renderApp, + url: string, + { + doc = null, + pageNotFound = false, + hyData = null, + pageTitle = null, + pageDescription = "", + possibleLocales = null, + locale = null, + noIndexing = false, + onlyFollow = false, + image = null, + blogMeta = null, + }: HydrationData = { url } +) { + const canonicalURL = `${BASE_URL}${url}`; + + let realPageTitle = pageTitle; + let metaDescription = pageDescription; + + const hydrationData: HydrationData = { url }; + const translations: JSX.Element[] = []; + if (blogMeta) { + hydrationData.blogMeta = blogMeta; + } + if (pageNotFound) { + realPageTitle = `🤷🏽‍♀️ Page not found | ${realPageTitle || "MDN Web Docs"}`; + hydrationData.pageNotFound = true; + } else if (hyData) { + hydrationData.hyData = hyData; + } else if (doc) { + // Use the doc's title instead + realPageTitle = doc.pageTitle; + + metaDescription = getMetaDescription(doc); + if (doc.summary) { + pageDescription = doc.summary; + } + + hydrationData.doc = doc; + + if (doc.other_translations) { + // Note, we also always include "self" as a locale. That's why we concat + // this doc's locale plus doc.other_translations. + const thisLocale = { + locale: doc.locale, + title: doc.title, + url: doc.mdn_url, + }; + + const allTranslations = [...doc.other_translations, thisLocale]; + const allLocales = allTranslations.map((t) => t.locale); + + for (const translation of allTranslations) { + const translationURL = doc.mdn_url.replace( + `/${doc.locale}/`, + () => `/${translation.locale}/` + ); + // The locale used in `` needs to be the ISO-639-1 + // code. For example, it's "en", not "en-US". And it's "sv" not "sv-SE". + // See https://developers.google.com/search/docs/specialty/international/localized-versions#language-codes + translations.push( + + ); + } + } + } + + if (possibleLocales) { + hydrationData.possibleLocales = possibleLocales; + } + + // Open Graph protocol expects `language_TERRITORY` format. + const ogLocale = (locale || (doc && doc.locale) || DEFAULT_LOCALE).replace( + "-", + "_" + ); + + const robotsContent = + !ALWAYS_ALLOW_ROBOTS || (doc && doc.noIndexing) || noIndexing + ? "noindex, nofollow" + : onlyFollow + ? "noindex, follow" + : ""; + + return ( + "" + + renderToString( + + + + + + + + + + + + + + + + {realPageTitle || "MDN Web Docs"} + {translations} + {WEBFONT_URLS.map((url) => ( + + ))} + + {robotsContent && } + + + + + + + + + + + + + + + + + {!pageNotFound && } +