From ecd3ce07f8b722d70d0e9a10adca28713dedd22a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannes=20H=C3=B6ke?= Date: Mon, 7 Sep 2026 18:28:54 +0200 Subject: [PATCH 1/7] chore(dev): configure node & yarn versions, update docs accordingly --- .nvmrc | 1 + CONTRIBUTING.md | 4 ++++ cdk/.nvmrc | 1 + cdk/package-lock.json | 10 +++++++++- package.json | 3 ++- 5 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 .nvmrc create mode 100644 cdk/.nvmrc diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 000000000..a45fd52cc --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +24 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3483ee99e..9ad779c3e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,12 +2,16 @@ ## Initial Setup +Use the Node version from `.nvmrc` (`nvm use`). Yarn 1 is pinned via `packageManager` in `package.json`, so corepack picks it up automatically. + To install the dependencies, run: ```sh yarn install ``` +Build failures for the optional native modules `printer` and `snappy` can be ignored. `printer` is only needed for `yarn start:printer` and requires `libcups2-dev`. + Copy the file `.env.example` to `.env`: ```sh diff --git a/cdk/.nvmrc b/cdk/.nvmrc new file mode 100644 index 000000000..a45fd52cc --- /dev/null +++ b/cdk/.nvmrc @@ -0,0 +1 @@ +24 diff --git a/cdk/package-lock.json b/cdk/package-lock.json index ed8e8c7ed..3086f3941 100644 --- a/cdk/package-lock.json +++ b/cdk/package-lock.json @@ -48,6 +48,7 @@ "semver" ], "license": "Apache-2.0", + "peer": true, "dependencies": { "jsonschema": "~1.4.1", "semver": "^7.7.4" @@ -106,6 +107,7 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -1256,6 +1258,7 @@ "integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -2288,6 +2291,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -2608,7 +2612,8 @@ "version": "10.6.0", "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.6.0.tgz", "integrity": "sha512-TxHOnBO5zMo/G76ykzGF/wMpEHu257TbWiIxP9K0Yv/+t70UzgBQiTqjkAsWOPC6jW91DzJI0+ehQV6xDRNBuQ==", - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/convert-source-map": { "version": "2.0.0", @@ -3526,6 +3531,7 @@ "integrity": "sha512-AkXIIFcaazymvey2i/+F94XRnM6TsVLZDhBMLsd1Sf/W0wzsvvpjeyUrCZD6HGG4SDYPgDJDBKeiJTBb10WzMg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/core": "30.3.0", "@jest/types": "30.3.0", @@ -5226,6 +5232,7 @@ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -5301,6 +5308,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/package.json b/package.json index 2e60909d7..648d39250 100644 --- a/package.json +++ b/package.json @@ -212,5 +212,6 @@ "last 3 versions", "Safari >= 8", "iOS >= 8" - ] + ], + "packageManager": "yarn@1.22.22" } From cddc3a4c8ef5b6260f42a1f80a52c16260b49203 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannes=20H=C3=B6ke?= Date: Tue, 22 Sep 2026 14:18:08 +0200 Subject: [PATCH 2/7] Update `.eslintignore` --- .eslintignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.eslintignore b/.eslintignore index cc924c6b4..aa0777f45 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,6 @@ build/* dist/* lambda/* +**/*.ts +cdk/node_modules/* +cdk/cdk.out/* From f572a038a3d041af4daae68877d9afee97f66064 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannes=20H=C3=B6ke?= Date: Tue, 22 Sep 2026 14:16:56 +0200 Subject: [PATCH 3/7] Align app TS version with cdk TS version When opening the project root in VSCode, this could cause conflicts. In the app, Typescript is only used for the sprite importer script. --- package.json | 6 +-- tsconfig.json | 18 +++------ yarn.lock | 106 ++++++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 98 insertions(+), 32 deletions(-) diff --git a/package.json b/package.json index 648d39250..e1ffd98aa 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "@babel/register": "^7.4.4", "@storybook/react": "^6.0.0", "@svgr/webpack": "^8.1.0", - "@types/node": "^11.11.3", + "@types/node": "^24.10.1", "arraybuffer-loader": "^1.0.3", "autoprefixer": "^9.5.0", "aws-sdk": "^2.423.0", @@ -140,8 +140,8 @@ "svg-to-image": "^1.1.3", "svgo-loader": "^4.0.0", "text-encoding": "^0.7.0", - "ts-node": "^8.0.2", - "typescript": "^3.2.4", + "ts-node": "^10.9.2", + "typescript": "~5.9.3", "wav-encoder": "^1.3.0", "web-audio-test-api": "^0.5.2", "webpack": "^5.67.0", diff --git a/tsconfig.json b/tsconfig.json index c0a5567b1..f7a3d7094 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,17 +1,11 @@ { "compilerOptions": { "module": "commonjs", - "target": "es2016", - "experimentalDecorators": true, - "lib": [ - "esnext", - ], + "target": "es2022", + "lib": ["esnext"], + "types": ["node"], + "skipLibCheck": true, + "experimentalDecorators": true }, - "exclude": [ - "node_modules", - "**/node_modules/*" - ], - "include": [ - "scripts/" - ], + "include": ["scripts/**/*.ts"] } diff --git a/yarn.lock b/yarn.lock index ca22d2ce4..84f2c174c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1259,6 +1259,13 @@ resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.6.0.tgz#ec6cd237440700bc23ca23087f513c75508958b0" integrity sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA== +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + "@discoveryjs/json-ext@^0.5.0", "@discoveryjs/json-ext@^0.5.3": version "0.5.7" resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" @@ -2032,7 +2039,7 @@ "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping" "^0.3.24" -"@jridgewell/resolve-uri@^3.1.0": +"@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0": version "3.1.2" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== @@ -2055,6 +2062,14 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.13", "@jridgewell/trace-mapping@^0.3.20", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": version "0.3.25" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" @@ -3289,6 +3304,26 @@ resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== +"@tsconfig/node10@^1.0.7": + version "1.0.13" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.13.tgz#56105a9a8c786e8f15e35746879cf2d52275485b" + integrity sha512-gcLdvR9HO1ZJBypsOGqaP6TFEzb6vIta0KSTLt9NAQ6pXQO3cRgSVyCN6pzYqI9DlJgY71XKO0dpDhCf08b3pg== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" + integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== + "@types/babel__core@*", "@types/babel__core@^7.1.14", "@types/babel__core@^7.1.7": version "7.20.5" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" @@ -3597,16 +3632,18 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.45.tgz#2c0fafd78705e7a18b7906b5201a522719dc5190" integrity sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw== -"@types/node@^11.11.3": - version "11.15.54" - resolved "https://registry.yarnpkg.com/@types/node/-/node-11.15.54.tgz#59ed60e7b0d56905a654292e8d73275034eb6283" - integrity sha512-1RWYiq+5UfozGsU6MwJyFX6BtktcT10XRjvcAQmskCtMcW3tPske88lM/nHv7BQG1w9KBXI1zPGuu5PnNCX14g== - "@types/node@^14.0.10 || ^16.0.0", "@types/node@^14.14.20 || ^16.0.0": version "16.18.105" resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.105.tgz#7147176852774ec4d6dd626803888adf6b999feb" integrity sha512-w2d0Z9yMk07uH3+Cx0N8lqFyi3yjXZxlbYappPj+AsOlT02OyxyiuNoNHdGt6EuiSm8Wtgp2YV7vWg+GMFrvFA== +"@types/node@^24.10.1": + version "24.19.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-24.19.1.tgz#3a1c3ed9e9fc94fe48d4a72d83dfd0bfcf379c62" + integrity sha512-aS3/DG0oM05K0RIXXP+hKjinGG5IgSSVGzswZxW3O0sS3pH4/fycXundUC9XsszgKCk4gHXylTEK6hyFxVxnoQ== + dependencies: + undici-types ">=7.24.0 <7.24.7" + "@types/normalize-package-data@^2.4.0": version "2.4.4" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz#56e2cc26c397c038fab0e3a917a12d5c5909e901" @@ -4135,6 +4172,13 @@ acorn-walk@^7.2.0: resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== +acorn-walk@^8.1.1: + version "8.3.5" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.5.tgz#8a6b8ca8fc5b34685af15dabb44118663c296496" + integrity sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw== + dependencies: + acorn "^8.11.0" + acorn@^5.2.1: version "5.7.4" resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e" @@ -4150,6 +4194,11 @@ acorn@^7.1.0, acorn@^7.1.1, acorn@^7.4.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== +acorn@^8.11.0, acorn@^8.4.1: + version "8.19.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.19.0.tgz#6b26fd97ce1cc9dc50bdf7ad305e9f1384d4f28a" + integrity sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw== + acorn@^8.7.1, acorn@^8.8.2: version "8.12.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" @@ -6692,6 +6741,11 @@ create-react-context@^0.2.2: fbjs "^0.8.0" gud "^1.0.0" +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + cron-parser@^4.2.0: version "4.9.0" resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-4.9.0.tgz#0340694af3e46a0894978c6f52a6dbb5c0f11ad5" @@ -16893,7 +16947,7 @@ source-map-support@0.5.13: buffer-from "^1.0.0" source-map "^0.6.0" -source-map-support@^0.5.16, source-map-support@^0.5.17, source-map-support@~0.5.12, source-map-support@~0.5.20: +source-map-support@^0.5.16, source-map-support@~0.5.12, source-map-support@~0.5.20: version "0.5.21" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== @@ -18130,15 +18184,23 @@ ts-dedent@^2.0.0: resolved "https://registry.yarnpkg.com/ts-dedent/-/ts-dedent-2.2.0.tgz#39e4bd297cd036292ae2394eb3412be63f563bb5" integrity sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ== -ts-node@^8.0.2: - version "8.10.2" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.10.2.tgz#eee03764633b1234ddd37f8db9ec10b75ec7fb8d" - integrity sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA== - dependencies: +ts-node@^10.9.2: + version "10.9.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" + integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" arg "^4.1.0" + create-require "^1.1.0" diff "^4.0.1" make-error "^1.1.1" - source-map-support "^0.5.17" + v8-compile-cache-lib "^3.0.1" yn "3.1.1" ts-pnp@^1.1.6: @@ -18316,10 +18378,10 @@ typescript@5: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba" integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q== -typescript@^3.2.4: - version "3.9.10" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.10.tgz#70f3910ac7a51ed6bef79da7800690b19bf778b8" - integrity sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q== +typescript@~5.9.3: + version "5.9.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f" + integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== ua-parser-js@^0.7.30, ua-parser-js@^0.7.9: version "0.7.38" @@ -18354,6 +18416,11 @@ unbzip2-stream@^1.0.9: buffer "^5.2.1" through "^2.3.8" +"undici-types@>=7.24.0 <7.24.7": + version "7.24.6" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.24.6.tgz#61275b485d7fd4e9d269c7cf04ec2873c9cc0f91" + integrity sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg== + undici-types@~6.19.2: version "6.19.8" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" @@ -18722,6 +18789,11 @@ uuid@^8.3.1, uuid@^8.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + v8-compile-cache@^2.0.3: version "2.4.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz#cdada8bec61e15865f05d097c5f4fd30e94dc128" From 7fffaea7a1ee787abae4f95ca319b12c893af67a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannes=20H=C3=B6ke?= Date: Wed, 9 Sep 2026 17:37:18 +0200 Subject: [PATCH 4/7] fix(build): exclude ffmpeg-worker from Terser minification this would fail the production build, trying to minify a 13mb (already minified) js file --- package.json | 1 + src/components/sharing-toolbox/sharing-toolbox.jsx | 2 +- webpack.config.js | 14 ++++++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index e1ffd98aa..90c90b1af 100644 --- a/package.json +++ b/package.json @@ -139,6 +139,7 @@ "style-loader": "^0.23.1", "svg-to-image": "^1.1.3", "svgo-loader": "^4.0.0", + "terser-webpack-plugin": "^5.3.10", "text-encoding": "^0.7.0", "ts-node": "^10.9.2", "typescript": "~5.9.3", diff --git a/src/components/sharing-toolbox/sharing-toolbox.jsx b/src/components/sharing-toolbox/sharing-toolbox.jsx index e506c5cf7..9597d7563 100644 --- a/src/components/sharing-toolbox/sharing-toolbox.jsx +++ b/src/components/sharing-toolbox/sharing-toolbox.jsx @@ -12,7 +12,7 @@ import VM from 'scratch-vm' import QRCode from 'qrcode.react' import html2canvas from 'html2canvas' import JsPDF from 'jspdf' -import ffmpegWorkerPath from 'file-loader!ffmpeg.js/ffmpeg-worker-mp4' // eslint-disable-line import/no-unresolved +import ffmpegWorkerPath from 'ffmpeg.js/ffmpeg-worker-mp4' import InlineSvg from '../inline-svg/inline-svg.jsx' import Modal from '../modal/modal.jsx' diff --git a/webpack.config.js b/webpack.config.js index 9b3205a4f..0a32a8734 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -5,6 +5,7 @@ const webpack = require('webpack') // Plugins const CopyWebpackPlugin = require('copy-webpack-plugin') +const TerserPlugin = require('terser-webpack-plugin') const { GenerateSW } = require('workbox-webpack-plugin') // Custom Plugins @@ -161,6 +162,14 @@ module.exports = { include: /node_modules/, type: 'javascript/auto', }, + { + // Prebuilt emscripten worker, shipped as-is and excluded from minification. + test: /ffmpeg\.js[\\/]ffmpeg-worker-mp4\.js$/, + type: 'asset/resource', + generator: { + filename: 'static/ffmpeg-worker-mp4.[contenthash][ext]', + }, + }, { test: require.resolve('zepto'), use: [ @@ -174,6 +183,11 @@ module.exports = { }, optimization: { runtimeChunk: 'single', + minimizer: [ + new TerserPlugin({ + exclude: /ffmpeg-worker-mp4/, + }), + ], }, plugins: [ new webpack.DefinePlugin({ From d7e5ae4d27938f6d1b194355e0658eb8e3cf5615 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannes=20H=C3=B6ke?= Date: Tue, 6 Oct 2026 16:44:40 +0200 Subject: [PATCH 5/7] Port service worker precaching of project media to webpack 5 The production service worker precaches the library and game media from /data/assets/ for offline mode. The webpack 4 plugin that added them was disabled in the Node 16 update (bc3c6a12). A Workbox manifest transform now adds the same entries; filenames are content hashes, so they need no revision. --- package.json | 2 - .../generate-s3-sw-precache-plugin/index.js | 51 ------------------- webpack.config.js | 16 +++++- yarn.lock | 7 +-- 4 files changed, 16 insertions(+), 60 deletions(-) delete mode 100644 scripts/generate-s3-sw-precache-plugin/index.js diff --git a/package.json b/package.json index 90c90b1af..ec8bf3957 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,6 @@ "@babel/polyfill": "^7.2.5", "@babel/preset-env": "^7.3.1", "@babel/preset-react": "^7.0.0", - "@babel/register": "^7.4.4", "@storybook/react": "^6.0.0", "@svgr/webpack": "^8.1.0", "@types/node": "^24.10.1", @@ -50,7 +49,6 @@ "aws-sdk": "^2.423.0", "babel-eslint": "^10.1.0", "babel-loader": "^8.0.5", - "babel-plugin-import-noop": "^1.0.1", "bowser": "^1.9.3", "chromedriver": "^2.39.0", "classnames": "^2.2.5", diff --git a/scripts/generate-s3-sw-precache-plugin/index.js b/scripts/generate-s3-sw-precache-plugin/index.js deleted file mode 100644 index d313840a3..000000000 --- a/scripts/generate-s3-sw-precache-plugin/index.js +++ /dev/null @@ -1,51 +0,0 @@ -require('@babel/polyfill') -require('@babel/register')({ - configFile: false, - babelrc: false, - presets: [['@babel/env', { targets: { node: 8 } }]], - plugins: [['import-noop', { extensions: ['mp3', 'svg', 'jpg'] }]], -}) - -const crypto = require('crypto') -const { getAllAssets } = require('../lib/assets') -const defaultAssets = require('../../src/lib/default-project/').default - -const defaultAssetIDs = defaultAssets.map( - (asset) => `${asset.id}.${asset.dataFormat.toLowerCase()}` -) - -function GenerateS3SWPrecachePlugin(options) { - this.filename = options.filename || 's3-manifest.[hash].js' -} - -GenerateS3SWPrecachePlugin.prototype.apply = function (compiler) { - let filenameTemplate = this.filename - compiler.plugin('emit', function (compilation, callback) { - getAllAssets() - .then((files) => files.filter((file) => !defaultAssetIDs.includes(file))) - .then((files) => { - const list = JSON.stringify(files.map((file) => '/data/assets/' + file)) - const hash = crypto.createHash('md5').update(list, 'utf8').digest('hex') - const filename = filenameTemplate.replace('[hash]', hash) - const absUrl = '/' + filename - - compilation.assets[filename] = { - source: function () { - return `self.__precacheManifest = (self.__precacheManifest || []).concat(${list});` - }, - size: function () { - return this.source().length - }, - } - - const swSource = compilation.assets['service-worker.js'].source - compilation.assets['service-worker.js'].source = function () { - return swSource().replace(filenameTemplate, absUrl) - } - callback() - }) - .catch((e) => callback(e)) - }) -} - -module.exports = GenerateS3SWPrecachePlugin diff --git a/webpack.config.js b/webpack.config.js index 0a32a8734..e2b3d3d98 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,5 +1,6 @@ require('dotenv').config() +const fs = require('fs') const path = require('path') const webpack = require('webpack') @@ -10,7 +11,7 @@ const { GenerateSW } = require('workbox-webpack-plugin') // Custom Plugins const customHtmlPlugin = require('./scripts/custom-html-plugin') -// const { getAssetsList } = require('./scripts/generate-s3-sw-precache-plugin') +const { getAllAssets } = require('./scripts/lib/assets') const branch = process.env.BRANCH || process.env.TRAVIS_BRANCH const bucketSuffix = branch === 'production' ? 'prod' : 'staging' @@ -24,6 +25,18 @@ const enableServiceWorker = 'ENABLE_SERVICE_WORKER' in process.env || process.env.NODE_ENV === 'production' +// Precache project media loaded from /data/assets/ for offline mode. +// Default project media is bundled and excluded. +const precacheProjectMedia = async (entries) => { + const bundled = new Set( + fs.readdirSync(path.join(__dirname, 'assets/project-assets')) + ) + const media = (await getAllAssets()) + .filter((name) => !bundled.has(name)) + .map((name) => ({ url: `/data/assets/${name}`, revision: null, size: 0 })) + return { manifest: entries.concat(media), warnings: [] } +} + // fix for Netlify, where we cannot define AWS_REGION in the environment if ('FUNCTIONS_AWS_REGION' in process.env) { process.env.AWS_REGION = process.env.FUNCTIONS_AWS_REGION @@ -284,6 +297,7 @@ module.exports = { cleanupOutdatedCaches: true, excludeChunks: ['settings', 'sharingpage', 'mobile-screen'], maximumFileSizeToCacheInBytes: 19 * 1024 * 1024, + manifestTransforms: [precacheProjectMedia], }), ] : [] diff --git a/yarn.lock b/yarn.lock index 84f2c174c..6b6b1888f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1178,7 +1178,7 @@ "@babel/plugin-transform-modules-commonjs" "^7.24.7" "@babel/plugin-transform-typescript" "^7.24.7" -"@babel/register@^7.12.1", "@babel/register@^7.4.4": +"@babel/register@^7.12.1": version "7.24.6" resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.24.6.tgz#59e21dcc79e1d04eed5377633b0f88029a6bef9e" integrity sha512-WSuFCc2wCqMeXkz/i3yfAAsxwWflEgbVkZzivgAmXl/MxrXeoYFZOOPllbC8R8WTF7u61wSRQtDVZ1879cdu6w== @@ -5026,11 +5026,6 @@ babel-plugin-formatjs@^10.5.16: "@types/babel__traverse" "^7.1.7" tslib "^2.4.0" -babel-plugin-import-noop@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/babel-plugin-import-noop/-/babel-plugin-import-noop-1.0.1.tgz#ceb9afa4006de68579fe676afc77dc1899a146eb" - integrity sha512-LcEOxEZ+/QZvO0BqTuDfOg6dISFEZjIJ2kep+kaVSl/D4MtiYy/TvZgqe5P19atn6nG0HPpvVz6n8Gl5Ab/XXw== - babel-plugin-istanbul@^6.1.1: version "6.1.1" resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" From 4210c6c5c72f8e3e548e407e043843a1a85eab16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannes=20H=C3=B6ke?= Date: Tue, 6 Oct 2026 18:19:20 +0200 Subject: [PATCH 6/7] Build the service worker from a source file with InjectManifest static/sw-helper.js relied on the global workbox object from Workbox 4's workbox-sw loader. Workbox 5's GenerateSW bundles the runtime as a module instead, so the helper threw on install and offline mode failed. src/service-worker.js now imports the Workbox modules and contains the former GenerateSW config and the helper: video requests are served from the precache with range request support (registered before the precache route, as in production), and quota errors are reported to the page. Because the router's fetch listener now runs first, the runtime assets route skips precached URLs. Otherwise it would answer them from its own, initially empty cache instead of the precache. --- cdk/DEPLOYMENT.md | 2 +- cdk/functions/entrypoint-rewrite.js | 2 +- package.json | 6 +++ src/service-worker.js | 70 +++++++++++++++++++++++++++++ static/sw-helper.js | 21 --------- webpack.config.js | 40 +++-------------- 6 files changed, 83 insertions(+), 58 deletions(-) create mode 100644 src/service-worker.js delete mode 100644 static/sw-helper.js diff --git a/cdk/DEPLOYMENT.md b/cdk/DEPLOYMENT.md index ae12a9273..d93ac9746 100644 --- a/cdk/DEPLOYMENT.md +++ b/cdk/DEPLOYMENT.md @@ -57,7 +57,7 @@ Die URL im Browser bleibt dabei unverändert. Die Liste der Dateien ist eine All Bewusst **kein** `errorResponses` an der Distribution: Das hätte für alle Behaviors gegolten und auch Fehler von `/api/*` und `/data/*` in „200 + `index.html`" verwandelt. Fehlende Assets und API-Fehler kommen so als echte Fehler beim Frontend an. -Kommt eine weitere eigenständige Seite dazu, muss sie an drei Stellen eingetragen werden: `customHtmlPlugin` in `webpack.config.js`, die Function in `functions/entrypoint-rewrite.js` und die `navigateFallbackDenylist` des Service Workers. +Kommt eine weitere eigenständige Seite dazu, muss sie an drei Stellen eingetragen werden: `customHtmlPlugin` in `webpack.config.js`, die Function in `functions/entrypoint-rewrite.js` und die `denylist` der `NavigationRoute` in `src/service-worker.js`. ### TLS-Zertifikate diff --git a/cdk/functions/entrypoint-rewrite.js b/cdk/functions/entrypoint-rewrite.js index edd991f5c..f8d0503be 100644 --- a/cdk/functions/entrypoint-rewrite.js +++ b/cdk/functions/entrypoint-rewrite.js @@ -23,7 +23,7 @@ function handler(event) { // Eigenständige Seiten mit eigener index.html (webpack.config.js, // customHtmlPlugin mit `filename`). Neue Seiten hier, in webpack.config.js - // und in der navigateFallbackDenylist des Service Workers ergänzen. + // und in der denylist der NavigationRoute in src/service-worker.js ergänzen. var pages = { '/teilen': '/teilen/index.html', '/teilen/': '/teilen/index.html', diff --git a/package.json b/package.json index ec8bf3957..c8854840c 100644 --- a/package.json +++ b/package.json @@ -147,6 +147,12 @@ "webpack-cli": "^5.0.0", "webpack-dev-server": "^4.0.10", "webpack-visualizer-plugin": "^0.1.11", + "workbox-cacheable-response": "^5.1.4", + "workbox-core": "^5.1.4", + "workbox-precaching": "^5.1.4", + "workbox-range-requests": "^5.1.4", + "workbox-routing": "^5.1.4", + "workbox-strategies": "^5.1.4", "workbox-webpack-plugin": "^5.1.3", "xhr": "^2.5.0", "yarn": "^1.9.4" diff --git a/src/service-worker.js b/src/service-worker.js new file mode 100644 index 000000000..979c9b133 --- /dev/null +++ b/src/service-worker.js @@ -0,0 +1,70 @@ +/* eslint-env serviceworker */ +import { + cacheNames, + clientsClaim, + registerQuotaErrorCallback, + skipWaiting, +} from 'workbox-core' +import { + cleanupOutdatedCaches, + createHandlerBoundToURL, + getCacheKeyForURL, + precacheAndRoute, +} from 'workbox-precaching' +import { NavigationRoute, registerRoute } from 'workbox-routing' +import { CacheFirst, NetworkFirst } from 'workbox-strategies' +import { CacheableResponsePlugin } from 'workbox-cacheable-response' +import { RangeRequestsPlugin } from 'workbox-range-requests' + +skipWaiting() +clientsClaim() + +// Safari requests videos with Range headers, which the precache route cannot +// answer. Registered before it; ignoreSearch matches the revisioned cache keys. +registerRoute( + /\.mp4$/, + new CacheFirst({ + cacheName: cacheNames.precache, + matchOptions: { ignoreSearch: true }, + plugins: [new RangeRequestsPlugin()], + }) +) + +precacheAndRoute(self.__WB_MANIFEST) +cleanupOutdatedCaches() + +// /teilen und /settings sind eigene Seiten, nicht Teil der Haupt-SPA +// Neu hinzukommende Seiten müssen hier, in webpack.config.js und in +// cdk/functions/entrypoint-rewrite.js gepflegt werden! +registerRoute( + new NavigationRoute(createHandlerBoundToURL('/index.html'), { + denylist: [/^\/data\//, /^\/teilen/, /^\/settings/], + }) +) + +// Media that is not precached, e.g. assets uploaded by users. The router's +// fetch listener runs before the precache listener, so skip precached URLs. +registerRoute( + ({ url }) => + (url.pathname.startsWith('/data/assets/') || + url.pathname.startsWith('/static/assets')) && + !getCacheKeyForURL(url.href), + new CacheFirst({ + cacheName: 'assets', + plugins: [new CacheableResponsePlugin({ statuses: [0, 200] })], + }) +) + +registerRoute( + /data\/projects\/[^/]+\/index\.json$/, + new NetworkFirst({ cacheName: 'projects' }) +) + +// Report quota errors +self.addEventListener('message', (event) => { + if (event.data && event.data.type === 'GET_QUOTA_ERRORS') { + registerQuotaErrorCallback(() => { + event.ports[0].postMessage({ type: 'QUOTA_ERROR' }) + }) + } +}) diff --git a/static/sw-helper.js b/static/sw-helper.js deleted file mode 100644 index 381429e75..000000000 --- a/static/sw-helper.js +++ /dev/null @@ -1,21 +0,0 @@ -/* global workbox */ -// Handle Range requests from Safari correctly -workbox.routing.registerRoute( - /\.mp4$/, - new workbox.strategies.CacheFirst({ - cacheName: workbox.core.cacheNames.precache, - plugins: [new workbox.rangeRequests.Plugin()], - }) -) - -// Report quota errors -self.addEventListener('message', (event) => { - if (event.data && event.data.type === 'GET_QUOTA_ERRORS') { - // eslint-disable-next-line require-await - workbox.core.registerQuotaErrorCallback(async (_e) => { - event.ports[0].postMessage({ - type: 'QUOTA_ERROR', - }) - }) - } -}) diff --git a/webpack.config.js b/webpack.config.js index e2b3d3d98..da3f87380 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -7,7 +7,7 @@ const webpack = require('webpack') // Plugins const CopyWebpackPlugin = require('copy-webpack-plugin') const TerserPlugin = require('terser-webpack-plugin') -const { GenerateSW } = require('workbox-webpack-plugin') +const { InjectManifest } = require('workbox-webpack-plugin') // Custom Plugins const customHtmlPlugin = require('./scripts/custom-html-plugin') @@ -216,7 +216,7 @@ module.exports = { }), // Eigenständige Seiten neben der Haupt-App. Neue Einträge auch in der // CloudFront Function (cdk/functions/entrypoint-rewrite.js) und in der - // navigateFallbackDenylist des Service Workers unten ergänzen. + // denylist der NavigationRoute in src/service-worker.js ergänzen. customHtmlPlugin({ entrypoint: 'sharingpage', filename: 'teilen/index.html', @@ -256,45 +256,15 @@ module.exports = { ].concat( enableServiceWorker ? [ - new GenerateSW({ - navigateFallback: '/index.html', - // /teilen und /settings sind eigene Seiten, nicht Teil der Haupt-SPA - // Neu hinzukommende Seiten müssen hier und in entrypoint-rewrite.js gepflegt werden! - navigateFallbackDenylist: [/^\/data\//, /^\/teilen/, /^\/settings/], + new InjectManifest({ + swSrc: './src/service-worker.js', + swDest: 'service-worker.js', exclude: [ /\.map$/, /^manifest.*\.js$/, /\/1x1\.gif$/, /^static\/assets\/edu\/beispiel/, ], - runtimeCaching: [ - { - urlPattern: ({ url }) => { - return ( - url.pathname.startsWith('/data/assets/') || - url.pathname.startsWith('/static/assets') - ) - }, - handler: 'CacheFirst', - options: { - cacheName: 'assets', - cacheableResponse: { - statuses: [0, 200], - }, - }, - }, - { - urlPattern: new RegExp(/data\/projects\/[^/]+\/index\.json$/), - handler: 'NetworkFirst', - options: { - cacheName: 'projects', - }, - }, - ], - clientsClaim: true, - skipWaiting: true, - importScripts: ['/static/sw-helper.js'], - cleanupOutdatedCaches: true, excludeChunks: ['settings', 'sharingpage', 'mobile-screen'], maximumFileSizeToCacheInBytes: 19 * 1024 * 1024, manifestTransforms: [precacheProjectMedia], From 7435eba3d4bf99914a2cb47785664d5f67da7ee9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannes=20H=C3=B6ke?= Date: Mon, 12 Oct 2026 17:52:09 +0200 Subject: [PATCH 7/7] Precache the settings page for offline use The settings bundle was excluded from the precache since 67854fff, while settings/index.html was still precached. Offline, /settings/ loaded the HTML but not its script, and /settings did not match any route. The page only toggles local feature flags, so precache its bundle and serve it for both paths. The sharing page shows media from /data/sharing/, which cannot work offline. Exclude its HTML so it fails like any other offline page instead of rendering blank. --- src/service-worker.js | 8 ++++++++ webpack.config.js | 4 +++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/service-worker.js b/src/service-worker.js index 979c9b133..73b4df406 100644 --- a/src/service-worker.js +++ b/src/service-worker.js @@ -33,6 +33,14 @@ registerRoute( precacheAndRoute(self.__WB_MANIFEST) cleanupOutdatedCaches() +// Serve the precached settings page for /settings too. The precache route +// only appends index.html to URLs ending in a slash. +registerRoute( + new NavigationRoute(createHandlerBoundToURL('/settings/index.html'), { + allowlist: [/^\/settings\/?$/], + }) +) + // /teilen und /settings sind eigene Seiten, nicht Teil der Haupt-SPA // Neu hinzukommende Seiten müssen hier, in webpack.config.js und in // cdk/functions/entrypoint-rewrite.js gepflegt werden! diff --git a/webpack.config.js b/webpack.config.js index da3f87380..716bd33c7 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -264,8 +264,10 @@ module.exports = { /^manifest.*\.js$/, /\/1x1\.gif$/, /^static\/assets\/edu\/beispiel/, + // Shows shared media from /data/sharing/, useless offline + /^teilen\//, ], - excludeChunks: ['settings', 'sharingpage', 'mobile-screen'], + excludeChunks: ['sharingpage', 'mobile-screen'], maximumFileSizeToCacheInBytes: 19 * 1024 * 1024, manifestTransforms: [precacheProjectMedia], }),