diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..f0c2aa24 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,158 @@ +version: 2 + +# Minor and patch bumps inside each app's existing ranges are handled by the weekly +# refresh workflow (deps-weekly-refresh.yml), which updates every lockfile in one PR. +# Dependabot only opens what the refresh can't do: majors, and security fixes. + +updates: + # Node and Next.js apps + - package-ecosystem: npm + directories: + - "/claims-source-app" + - "/interaction-hook-app" + - "/manual-revocation-check" + - "/verifier-web-tutorial" + - "/webhook-app" + schedule: + interval: weekly + day: monday + time: "09:00" + timezone: Pacific/Auckland + open-pull-requests-limit: 8 + commit-message: + prefix: "build(deps)" + prefix-development: "build(deps-dev)" + groups: + security: + applies-to: security-updates + patterns: + - "*" + # Version-locked to each other, so they move together. + next-react-major: + patterns: + - "next" + - "eslint-config-next" + - "react" + - "react-dom" + - "@types/react" + - "@types/react-dom" + update-types: + - major + # Same dependency across several apps lands in one PR. + majors: + group-by: dependency-name + patterns: + - "*" + update-types: + - major + ignore: + - dependency-name: "*" + update-types: + - version-update:semver-minor + - version-update:semver-patch + + # React Native (Expo) tutorials, starter and complete + - package-ecosystem: npm + directories: + - "/react-native-in-person-verifier-tutorial/react-native-in-person-verifier-tutorial-complete" + - "/react-native-in-person-verifier-tutorial/react-native-in-person-verifier-tutorial-starter" + - "/react-native-mdocs-holder-tutorial/react-native-mdocs-holder-tutorial-complete" + - "/react-native-mdocs-holder-tutorial/react-native-mdocs-holder-tutorial-starter" + - "/react-native-remote-verification-tutorial/react-native-remote-verification-tutorial-complete" + - "/react-native-remote-verification-tutorial/react-native-remote-verification-tutorial-starter" + schedule: + interval: weekly + day: monday + time: "09:00" + timezone: Pacific/Auckland + open-pull-requests-limit: 5 + commit-message: + prefix: "build(deps)" + prefix-development: "build(deps-dev)" + groups: + security: + applies-to: security-updates + patterns: + - "*" + rn-tooling-major: + group-by: dependency-name + patterns: + - "*" + update-types: + - major + ignore: + - dependency-name: "*" + update-types: + - version-update:semver-minor + - version-update:semver-patch + # Expo SDK, React Native and MATTR SDK majors are upgraded deliberately, together + # with the matching Learn tutorial (`npx expo install --fix`), never one at a time. + - dependency-name: "expo" + update-types: ["version-update:semver-major"] + - dependency-name: "expo-*" + update-types: ["version-update:semver-major"] + - dependency-name: "@expo/*" + update-types: ["version-update:semver-major"] + - dependency-name: "react" + update-types: ["version-update:semver-major"] + - dependency-name: "react-dom" + update-types: ["version-update:semver-major"] + - dependency-name: "react-native" + update-types: ["version-update:semver-major"] + - dependency-name: "@mattrglobal/*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: gradle + directories: + - "/android-holder-tutorial-sample-app" + - "/android-in-person-verifier-tutorial-sample-app" + - "/android-remote-verification-tutorial-sample-app" + schedule: + interval: weekly + day: monday + time: "09:00" + timezone: Pacific/Auckland + open-pull-requests-limit: 3 + commit-message: + prefix: "build(deps)" + groups: + android: + group-by: dependency-name + patterns: + - "*" + ignore: + # Resolved from each app's local `repo/` folder, which isn't committed. + - dependency-name: "global.mattr.mobilecredential:*" + + - package-ecosystem: docker + directories: + - "/claims-source-app" + - "/webhook-app" + schedule: + interval: weekly + day: monday + time: "09:00" + timezone: Pacific/Auckland + open-pull-requests-limit: 2 + commit-message: + prefix: "build(deps)" + groups: + docker: + group-by: dependency-name + patterns: + - "*" + + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: weekly + day: monday + time: "09:00" + timezone: Pacific/Auckland + open-pull-requests-limit: 3 + commit-message: + prefix: "build(ci)" + groups: + github-actions: + patterns: + - "*" diff --git a/.github/workflows/ci-apps.yml b/.github/workflows/ci-apps.yml new file mode 100644 index 00000000..ab969acb --- /dev/null +++ b/.github/workflows/ci-apps.yml @@ -0,0 +1,86 @@ +name: CI apps + +# Installs each changed JS app from its lockfile, then builds the Next.js apps and +# type-checks the complete React Native apps. The native Android and iOS apps aren't +# built: their MATTR SDKs are added locally and aren't available in CI. The React Native +# apps install a private MATTR SDK, so they're only checked when an NPM_TOKEN secret exists. + +on: + pull_request: + branches: [master] + # Started by deps-weekly-refresh.yml for its PR branch. + workflow_dispatch: + +permissions: + contents: read + +jobs: + changed: + runs-on: ubuntu-latest + outputs: + apps: ${{ steps.apps.outputs.apps }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + + - name: List changed apps + id: apps + env: + BASE_SHA: ${{ github.event.pull_request.base.sha }} + run: | + BASE_SHA=${BASE_SHA:-$(git merge-base origin/master HEAD)} + apps=$(git ls-files '*package-lock.json' '*yarn.lock' | xargs -n1 dirname | while read -r dir; do + if ! git diff --quiet "$BASE_SHA"...HEAD -- "$dir"; then echo "$dir"; fi + done | jq -R . | jq -sc .) + echo "apps=$apps" >> "$GITHUB_OUTPUT" + echo "Changed apps: $apps" + + check: + needs: changed + if: needs.changed.outputs.apps != '[]' + runs-on: ubuntu-latest + timeout-minutes: 20 + strategy: + fail-fast: false + matrix: + app: ${{ fromJSON(needs.changed.outputs.apps) }} + defaults: + run: + working-directory: ${{ matrix.app }} + env: + COREPACK_ENABLE_DOWNLOAD_PROMPT: "0" + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + NEXT_TELEMETRY_DISABLED: "1" + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: 24 + + - name: Skip without registry access + id: access + if: startsWith(matrix.app, 'react-native-') && env.NPM_TOKEN == '' + run: echo "::notice::No NPM_TOKEN secret for the private MATTR SDK, skipping ${{ matrix.app }}" + + - name: Install + if: steps.access.conclusion == 'skipped' + run: | + if [ -n "$NPM_TOKEN" ]; then export YARN_NPM_AUTH_TOKEN="$NPM_TOKEN"; fi + if [ -f yarn.lock ]; then + corepack yarn install --immutable --mode=skip-build + else + npm ci --ignore-scripts --no-audit --no-fund + fi + + - name: Build + if: steps.access.conclusion == 'skipped' && hashFiles(format('{0}/package-lock.json', matrix.app)) != '' + run: npm run build --if-present + + - name: Type-check + if: steps.access.conclusion == 'skipped' && startsWith(matrix.app, 'react-native-') && endsWith(matrix.app, '-complete') + run: corepack yarn tsc --noEmit diff --git a/.github/workflows/deps-weekly-refresh.yml b/.github/workflows/deps-weekly-refresh.yml new file mode 100644 index 00000000..5f38d204 --- /dev/null +++ b/.github/workflows/deps-weekly-refresh.yml @@ -0,0 +1,63 @@ +name: Weekly dependency refresh + +# Re-resolves every app's lockfile to the newest versions its package.json ranges allow, +# and opens (or updates) a single PR listing what changed. + +on: + schedule: + # Monday 09:00 NZDT (08:00 NZST) + - cron: "0 20 * * 0" + workflow_dispatch: + +concurrency: + group: deps-weekly-refresh + cancel-in-progress: false + +permissions: + contents: write + pull-requests: write + actions: write + +jobs: + refresh: + if: github.repository == 'mattrglobal/sample-apps' + runs-on: ubuntu-latest + timeout-minutes: 45 + env: + COREPACK_ENABLE_DOWNLOAD_PROMPT: "0" + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: 24 + + - name: Refresh lockfiles + id: refresh + run: node scripts/deps/refresh.mjs --report "$RUNNER_TEMP/deps-report.md" + + - name: Open or update the PR + id: pr + if: steps.refresh.outputs.changed == 'true' + uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 + with: + branch: deps/weekly-refresh + base: master + add-paths: | + **/package-lock.json + **/yarn.lock + commit-message: "build(deps): weekly in-range dependency refresh" + title: "build(deps): weekly in-range dependency refresh" + body-path: ${{ runner.temp }}/deps-report.md + sign-commits: true + delete-branch: true + + # Pushes made with GITHUB_TOKEN don't trigger pull_request workflows, so start the + # app checks on the PR branch directly. They report on the PR's head commit. + - name: Run app checks on the PR + if: steps.pr.outputs.pull-request-operation == 'created' || steps.pr.outputs.pull-request-operation == 'updated' + env: + GH_TOKEN: ${{ github.token }} + run: gh workflow run ci-apps.yml --repo "$GITHUB_REPOSITORY" --ref deps/weekly-refresh diff --git a/README.md b/README.md index 7c45fcd9..93826ba9 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,17 @@ A collection of developer apps and API tooling that you can use to interface wit * [Credential templates](/credential-templates/README.md) * [Postman Collection](/postman/README.md) +## Dependency updates + +Every Monday morning (NZ time) two things run: + +* **Weekly refresh** (`.github/workflows/deps-weekly-refresh.yml`) re-resolves every JS app's lockfile to the newest versions its `package.json` ranges allow, transitive dependencies included, and opens or updates one PR from `deps/weekly-refresh`. It never edits `package.json`, and skips releases newer than 3 days. Merging it clears the chain of transitive bumps that would otherwise arrive one PR at a time. +* **Dependabot** (`.github/dependabot.yml`) only opens what the refresh can't do: major upgrades, grouped by dependency across apps, and security fixes. It also covers the Android Gradle apps, the Dockerfiles and GitHub Actions. + +Expo, React Native and MATTR SDK majors in the React Native tutorials are excluded from automation, and the refresh leaves the MATTR SDK lock entries as they are. Upgrade them together with the matching Learn tutorial using `npx expo install --fix`. If a refresh would move a package that has a `patches/` file, that app's lockfile is left alone and the PR says so. Regenerate the patch against the new version to unblock it. + +To run the refresh locally: `node scripts/deps/refresh.mjs --only `. + ---

Copyright © MATTR Limited. Some rights reserved.
“MATTR” is a trademark of MATTR Limited, registered in New Zealand and other countries.

diff --git a/interaction-hook-app/package-lock.json b/interaction-hook-app/package-lock.json index e5eb70c6..a6460f0a 100644 --- a/interaction-hook-app/package-lock.json +++ b/interaction-hook-app/package-lock.json @@ -17,10 +17,10 @@ "zod": "^3.22.4" }, "devDependencies": { + "@tailwindcss/postcss": "^4.3.3", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", - "autoprefixer": "^10.0.1", "baseline-browser-mapping": "^2.10.8", "concurrently": "^9.2.1", "eslint": "^9", @@ -30,6 +30,19 @@ "typescript": "^5" } }, + "node_modules/@alloc/quick-lru": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.3.0.tgz", + "integrity": "sha512-U4+70Pc5ZS9osnCBCE5Jha/ciHM+Yp+CNMNC/7HvYbNRk1Ldd+f7qO65W5qfhu/TCv+/ozljlXXe9Nj8419DMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@babel/code-frame": { "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", @@ -641,9 +654,6 @@ "cpu": [ "arm" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -660,9 +670,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -679,9 +686,6 @@ "cpu": [ "ppc64" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -698,9 +702,6 @@ "cpu": [ "riscv64" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -717,9 +718,6 @@ "cpu": [ "s390x" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -736,9 +734,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -755,9 +750,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -774,9 +766,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -793,9 +782,6 @@ "cpu": [ "arm" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -818,9 +804,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -843,9 +826,6 @@ "cpu": [ "ppc64" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -868,9 +848,6 @@ "cpu": [ "riscv64" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -893,9 +870,6 @@ "cpu": [ "s390x" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -918,9 +892,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -943,9 +914,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -968,9 +936,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1226,9 +1191,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1245,9 +1207,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1264,9 +1223,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1283,9 +1239,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1620,6 +1573,277 @@ "tslib": "^2.8.0" } }, + "node_modules/@tailwindcss/node": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz", + "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.24.1", + "jiti": "^2.7.0", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.3" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz", + "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-x64": "4.3.3", + "@tailwindcss/oxide-freebsd-x64": "4.3.3", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-x64-musl": "4.3.3", + "@tailwindcss/oxide-wasm32-wasi": "4.3.3", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz", + "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz", + "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz", + "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz", + "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz", + "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz", + "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz", + "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz", + "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz", + "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz", + "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.11.1", + "@emnapi/runtime": "^1.11.1", + "@emnapi/wasi-threads": "^1.2.2", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz", + "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz", + "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.3.3.tgz", + "integrity": "sha512-JTSZZGQi1AyKirbLN3azmjVzef92tcX7h+iSqPdaeStyFpGpDlKvvpxeOE8njhbUanbRwr3z8DyzhICWnMtQeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.3.3", + "@tailwindcss/oxide": "4.3.3", + "postcss": "^8.5.16", + "tailwindcss": "4.3.3" + } + }, "node_modules/@tybys/wasm-util": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", @@ -2501,44 +2725,6 @@ "node": ">= 0.4" } }, - "node_modules/autoprefixer": { - "version": "10.4.21", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", - "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "browserslist": "^4.24.4", - "caniuse-lite": "^1.0.30001702", - "fraction.js": "^4.3.7", - "normalize-range": "^0.1.2", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -3033,8 +3219,8 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "devOptional": true, "license": "Apache-2.0", - "optional": true, "engines": { "node": ">=8" } @@ -3080,6 +3266,20 @@ "dev": true, "license": "MIT" }, + "node_modules/enhanced-resolve": { + "version": "5.26.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.26.0.tgz", + "integrity": "sha512-9vhedylFonb2YGogzUKX6+Ja72gOJbN1QHAqdrvqLwhdl/QWbKopzoUC9EbQNVsAns/bx/4uyqalrQAoy1IByw==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/es-abstract": { "version": "1.24.0", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", @@ -3887,20 +4087,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "patreon", - "url": "https://github.com/sponsors/rawify" - } - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -4088,6 +4274,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, "node_modules/has-bigints": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", @@ -4694,6 +4887,16 @@ "node": ">= 0.4" } }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, "node_modules/jose": { "version": "5.10.0", "resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz", @@ -4839,6 +5042,267 @@ "node": ">= 0.8.0" } }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -4874,6 +5338,16 @@ "loose-envify": "cli.js" } }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -5039,16 +5513,6 @@ "dev": true, "license": "MIT" }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -5337,13 +5801,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, - "license": "MIT" - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -6092,6 +6549,20 @@ "dev": true, "license": "MIT" }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", diff --git a/interaction-hook-app/package.json b/interaction-hook-app/package.json index cc32c623..7700f6d0 100644 --- a/interaction-hook-app/package.json +++ b/interaction-hook-app/package.json @@ -22,10 +22,10 @@ "zod": "^3.22.4" }, "devDependencies": { + "@tailwindcss/postcss": "^4.3.3", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", - "autoprefixer": "^10.0.1", "baseline-browser-mapping": "^2.10.8", "concurrently": "^9.2.1", "eslint": "^9", diff --git a/interaction-hook-app/postcss.config.js b/interaction-hook-app/postcss.config.js deleted file mode 100644 index 33ad091d..00000000 --- a/interaction-hook-app/postcss.config.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -} diff --git a/interaction-hook-app/postcss.config.mjs b/interaction-hook-app/postcss.config.mjs new file mode 100644 index 00000000..c7bcb4b1 --- /dev/null +++ b/interaction-hook-app/postcss.config.mjs @@ -0,0 +1,5 @@ +const config = { + plugins: ["@tailwindcss/postcss"], +}; + +export default config; diff --git a/interaction-hook-app/src/app/globals.css b/interaction-hook-app/src/app/globals.css index 161a9bf6..24c4637f 100644 --- a/interaction-hook-app/src/app/globals.css +++ b/interaction-hook-app/src/app/globals.css @@ -1,6 +1,4 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; +@import "tailwindcss"; :root { --foreground-rgb: 0, 0, 0; diff --git a/interaction-hook-app/src/app/layout.tsx b/interaction-hook-app/src/app/layout.tsx index bf44f1b5..6366b8e0 100644 --- a/interaction-hook-app/src/app/layout.tsx +++ b/interaction-hook-app/src/app/layout.tsx @@ -1,5 +1,6 @@ import type { Metadata } from "next"; import { Inter } from "next/font/google"; +import { Suspense } from "react"; import "./globals.css"; const inter = Inter({ subsets: ["latin"] }); @@ -12,7 +13,10 @@ export const metadata: Metadata = { export default function RootLayout({ children }: { children: React.ReactNode }) { return ( - {children} + + {/* The page reads the interaction hook params with useSearchParams(). */} + {children} + ); } diff --git a/interaction-hook-app/tailwind.config.ts b/interaction-hook-app/tailwind.config.ts deleted file mode 100644 index 1af3b8f0..00000000 --- a/interaction-hook-app/tailwind.config.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { Config } from 'tailwindcss' - -const config: Config = { - content: [ - './src/pages/**/*.{js,ts,jsx,tsx,mdx}', - './src/components/**/*.{js,ts,jsx,tsx,mdx}', - './src/app/**/*.{js,ts,jsx,tsx,mdx}', - ], - theme: { - extend: { - backgroundImage: { - 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', - 'gradient-conic': - 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', - }, - }, - }, - plugins: [], -} -export default config diff --git a/scripts/deps/lockfile.mjs b/scripts/deps/lockfile.mjs new file mode 100644 index 00000000..b01f464b --- /dev/null +++ b/scripts/deps/lockfile.mjs @@ -0,0 +1,89 @@ +// Lockfile parsing for npm (lockfileVersion 3) and Yarn Berry (__metadata version 10). +// Each parser returns Map>. + +export function parseNpmLock(text) { + const lock = JSON.parse(text); + const versions = new Map(); + for (const [path, entry] of Object.entries(lock.packages ?? {})) { + if (!path || !entry.version || entry.link) continue; + const name = entry.name ?? path.slice(path.lastIndexOf("node_modules/") + "node_modules/".length); + add(versions, name, entry.version); + } + return versions; +} + +export function parseYarnLock(text) { + const versions = new Map(); + for (const block of yarnBlocks(text)) { + if (block.key === "__metadata" || !block.version) continue; + const name = descriptorName(block.descriptors[0]); + if (block.descriptors[0].includes("@workspace:")) continue; + add(versions, name, block.version); + } + return versions; +} + +// Drop every Yarn lock entry except __metadata and the named packages, so the next +// `yarn install` re-resolves everything else to the newest version its range allows. +export function pruneYarnLock(text, keep) { + const lines = text.split("\n"); + const header = []; + for (const line of lines) { + if (line && !line.startsWith("#")) break; + header.push(line); + } + const kept = yarnBlocks(text) + .filter((b) => b.key === "__metadata" || keep(descriptorName(b.descriptors[0]))) + .map((b) => b.raw); + return `${header.join("\n")}\n${kept.join("\n\n")}\n`; +} + +export function diffVersions(before, after) { + const changed = []; + for (const name of new Set([...before.keys(), ...after.keys()])) { + const from = [...(before.get(name) ?? [])].sort(compareVersions); + const to = [...(after.get(name) ?? [])].sort(compareVersions); + if (from.join() !== to.join()) changed.push({ name, from, to }); + } + return changed.sort((a, b) => a.name.localeCompare(b.name)); +} + +function compareVersions(a, b) { + const pa = parseVersion(a); + const pb = parseVersion(b); + for (let i = 0; i < 3; i++) if (pa[i] !== pb[i]) return pa[i] - pb[i]; + return 0; +} + +function parseVersion(v) { + return String(v) + .split("-")[0] + .split(".") + .map((n) => Number.parseInt(n, 10) || 0); +} + +function add(map, name, version) { + if (!map.has(name)) map.set(name, new Set()); + map.get(name).add(version); +} + +function descriptorName(descriptor) { + const at = descriptor.indexOf("@", 1); + return at === -1 ? descriptor : descriptor.slice(0, at); +} + +function yarnBlocks(text) { + const blocks = []; + for (const raw of text.split(/\n\s*\n/)) { + const lines = raw.split("\n").filter((l) => l && !l.startsWith("#")); + if (!lines.length || lines[0].startsWith(" ")) continue; + const key = lines[0].replace(/:$/, ""); + const descriptors = key + .replace(/^"|"$/g, "") + .split(/",\s*"|,\s*/) + .map((d) => d.trim()); + const version = lines.find((l) => l.startsWith(" version:"))?.split(":")[1].trim(); + blocks.push({ key, descriptors, version, raw: lines.join("\n") }); + } + return blocks; +} diff --git a/scripts/deps/lockfile.test.mjs b/scripts/deps/lockfile.test.mjs new file mode 100644 index 00000000..b6be5d8b --- /dev/null +++ b/scripts/deps/lockfile.test.mjs @@ -0,0 +1,84 @@ +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { diffVersions, parseNpmLock, parseYarnLock, pruneYarnLock } from "./lockfile.mjs"; + +const npmLock = JSON.stringify({ + lockfileVersion: 3, + packages: { + "": { name: "app", dependencies: { next: "16.3.8" } }, + "node_modules/next": { version: "16.3.8", dependencies: { "@next/env": "16.3.8" } }, + "node_modules/@next/env": { version: "16.3.8" }, + "node_modules/a/node_modules/qs": { version: "6.13.0" }, + "node_modules/qs": { version: "6.14.0" }, + "node_modules/local": { resolved: "../local", link: true }, + }, +}); + +const yarnLock = `# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 10 + cacheKey: 10c0 + +"@expo/cli@npm:55.0.32": + version: 55.0.32 + resolution: "@expo/cli@npm:55.0.32" + languageName: node + linkType: hard + +"expo@npm:^55": + version: 55.0.10 + resolution: "expo@npm:55.0.10" + dependencies: + "@expo/cli": "npm:55.0.32" + languageName: node + linkType: hard + +"glob@npm:^10.5.0, glob@npm:^10.2.2": + version: 10.5.0 + resolution: "glob@npm:10.5.0" + languageName: node + linkType: hard + +"app@workspace:.": + version: 0.0.0-use.local + resolution: "app@workspace:." + languageName: unknown + linkType: soft +`; + +test("parseNpmLock collects every installed copy and skips links and the root", () => { + const v = parseNpmLock(npmLock); + assert.deepEqual([...v.get("qs")].sort(), ["6.13.0", "6.14.0"]); + assert.deepEqual([...v.get("@next/env")], ["16.3.8"]); + assert.equal(v.has("local"), false); + assert.equal(v.has(""), false); +}); + +test("parseYarnLock reads scoped and multi-descriptor entries and skips the workspace", () => { + const v = parseYarnLock(yarnLock); + assert.deepEqual([...v.get("@expo/cli")], ["55.0.32"]); + assert.deepEqual([...v.get("glob")], ["10.5.0"]); + assert.equal(v.has("app"), false); + assert.equal(v.has("__metadata"), false); +}); + +test("pruneYarnLock keeps the header, metadata and held packages only", () => { + const pruned = pruneYarnLock(yarnLock, (name) => name === "@expo/cli"); + assert.match(pruned, /^# This file is generated/); + assert.match(pruned, /__metadata:\n {2}version: 10/); + assert.match(pruned, /"@expo\/cli@npm:55.0.32":/); + assert.doesNotMatch(pruned, /expo@npm:\^55|glob@npm|workspace/); + assert.deepEqual([...parseYarnLock(pruned).keys()], ["@expo/cli"]); +}); + +test("diffVersions reports changed, added and removed packages", () => { + const before = new Map([["a", new Set(["1.0.0"])], ["b", new Set(["2.0.0"])]]); + const after = new Map([["a", new Set(["1.1.0"])], ["c", new Set(["3.0.0"])]]); + assert.deepEqual(diffVersions(before, after), [ + { name: "a", from: ["1.0.0"], to: ["1.1.0"] }, + { name: "b", from: ["2.0.0"], to: [] }, + { name: "c", from: [], to: ["3.0.0"] }, + ]); +}); diff --git a/scripts/deps/refresh.mjs b/scripts/deps/refresh.mjs new file mode 100644 index 00000000..382775ee --- /dev/null +++ b/scripts/deps/refresh.mjs @@ -0,0 +1,147 @@ +#!/usr/bin/env node +// Weekly in-range dependency refresh across every JS app in the repo. +// +// Usage: node scripts/deps/refresh.mjs [--report ] [--only [,]] +// +// For each app, re-resolve the whole lockfile (transitive dependencies included) to the +// newest versions its package.json ranges allow. package.json is never changed. + +import { execFileSync, spawnSync } from "node:child_process"; +import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import { diffVersions, parseNpmLock, parseYarnLock, pruneYarnLock } from "./lockfile.mjs"; + +// Matches the 3-day npmMinimalAgeGate in each Yarn app's .yarnrc.yml. +const MIN_AGE_DAYS = 3; + +const args = parseArgs(process.argv.slice(2)); +const root = execFileSync("git", ["rev-parse", "--show-toplevel"], { encoding: "utf8" }).trim(); + +const results = discoverApps() + .filter((a) => !args.only || args.only.includes(a.dir)) + .map((app) => { + console.log(`\n== ${app.dir} (${app.pm})`); + return processApp(app); + }); + +writeFileSync(args.report, renderReport(results)); +const changed = results.some((r) => r.changes.length); +if (process.env.GITHUB_OUTPUT) writeFileSync(process.env.GITHUB_OUTPUT, `changed=${changed}\n`, { flag: "a" }); +console.log(`\nReport written to ${args.report} (lockfiles changed: ${changed})`); + +function processApp(app) { + const cwd = join(root, app.dir); + const lockPath = join(cwd, app.lockfile); + const pkgPath = join(cwd, "package.json"); + const pkgBefore = readFileSync(pkgPath, "utf8"); + const lockBefore = readFileSync(lockPath, "utf8"); + const parse = app.pm === "npm" ? parseNpmLock : parseYarnLock; + const held = patchedPackages(cwd); + const notes = []; + + const refresh = app.pm === "npm" ? refreshNpm(cwd) : refreshYarn(cwd, lockPath, lockBefore, held); + if (readFileSync(pkgPath, "utf8") !== pkgBefore) writeFileSync(pkgPath, pkgBefore); + if (!refresh.ok) { + writeFileSync(lockPath, lockBefore); + notes.push(`Refresh failed, lockfile left unchanged:\n\n\`\`\`\n${tail(refresh.output)}\n\`\`\``); + } + + const moved = [...held].filter(([name, version]) => !parse(readFileSync(lockPath, "utf8")).get(name)?.has(version)); + if (moved.length) { + writeFileSync(lockPath, lockBefore); + const list = moved.map(([n, v]) => `\`${n}@${v}\``).join(", "); + notes.push(`Refresh reverted: it would move ${list}, which \`patches/\` pins. Regenerate the patch against the new version to unblock this app.`); + } + + const pkg = JSON.parse(pkgBefore); + const direct = { ...pkg.dependencies, ...pkg.devDependencies }; + const changes = diffVersions(parse(lockBefore), parse(readFileSync(lockPath, "utf8"))); + return { ...app, changes, directChanges: changes.filter((c) => c.name in direct), notes }; +} + +function refreshNpm(cwd) { + const before = new Date(Date.now() - MIN_AGE_DAYS * 86_400_000).toISOString(); + return run("npm", ["update", "--package-lock-only", "--ignore-scripts", "--no-audit", "--no-fund", `--before=${before}`], cwd); +} + +// Lock entries for MATTR's private SDK packages are kept as they are, so the refresh +// needs no registry credentials. SDK upgrades are made deliberately with the tutorials. +function refreshYarn(cwd, lockPath, lockText, held) { + const keep = (name) => held.has(name) || name.startsWith("@mattrglobal/"); + writeFileSync(lockPath, pruneYarnLock(lockText, keep)); + return run("corepack", ["yarn", "install", "--mode=update-lockfile"], cwd, { YARN_ENABLE_IMMUTABLE_INSTALLS: "false" }); +} + +// patch-package patches are named +.patch, with "/" in scoped names as "+". +function patchedPackages(cwd) { + const dir = join(cwd, "patches"); + const held = new Map(); + if (!existsSync(dir)) return held; + for (const file of readdirSync(dir)) { + const match = file.match(/^(.+)\+([^+]+)\.patch$/); + if (match) held.set(match[1].replace("+", "/"), match[2]); + } + return held; +} + +function renderReport(results) { + const line = (c) => `- \`${c.name}\` ${c.from.join(", ") || "(new)"} → ${c.to.join(", ") || "(removed)"}`; + const out = [ + "Weekly in-range refresh: every lockfile is re-resolved to the newest versions its `package.json` ranges allow, transitive dependencies included. No `package.json` changes.", + "", + "| App | Direct | Transitive |", + "| --- | --- | --- |", + ]; + for (const r of results) { + const flag = r.notes.length ? " (see notes)" : ""; + out.push(`| \`${r.dir}\`${flag} | ${r.directChanges.length} | ${r.changes.length - r.directChanges.length} |`); + } + out.push(""); + for (const r of results) { + if (!r.changes.length && !r.notes.length) continue; + out.push(`### \`${r.dir}\``, "", ...r.notes.flatMap((n) => [`> ${n.replaceAll("\n", "\n> ")}`, ""])); + out.push(...r.directChanges.map(line)); + const transitive = r.changes.filter((c) => !r.directChanges.includes(c)); + if (transitive.length) { + out.push("", `
${transitive.length} transitive`, "", ...transitive.map(line), "", "
"); + } + out.push(""); + } + return `${out.join("\n")}\n`; +} + +function discoverApps() { + const files = execFileSync("git", ["ls-files", "*package.json"], { cwd: root, encoding: "utf8" }).split("\n"); + return files + .filter((f) => f.endsWith("/package.json")) + .map((f) => f.slice(0, -"/package.json".length)) + .flatMap((dir) => { + const lockfile = ["package-lock.json", "yarn.lock"].find((l) => existsSync(join(root, dir, l))); + return lockfile ? [{ dir, lockfile, pm: lockfile === "yarn.lock" ? "yarn" : "npm" }] : []; + }) + .sort((a, b) => a.dir.localeCompare(b.dir)); +} + +function run(cmd, cmdArgs, cwd, env = {}) { + console.log(`$ ${cmd} ${cmdArgs.join(" ")}`); + const res = spawnSync(cmd, cmdArgs, { cwd, encoding: "utf8", maxBuffer: 256 * 1024 * 1024, env: { ...process.env, ...env } }); + return { ok: res.status === 0, output: `${res.stdout ?? ""}${res.stderr ?? ""}` }; +} + +function tail(text, lines = 15) { + return text + .trim() + .split("\n") + .filter((l) => !/^\s+at /.test(l)) + .slice(-lines) + .join("\n"); +} + +function parseArgs(argv) { + const parsed = { report: "deps-report.md", only: undefined }; + for (let i = 0; i < argv.length; i++) { + if (argv[i] === "--report") parsed.report = argv[++i]; + else if (argv[i] === "--only") parsed.only = argv[++i].split(","); + } + return parsed; +}