Acf pnp migration master (#34040)

GitOrigin-RevId: 418eab9bc733f27a6e0f8f04a2a8e21c2f5a491f
This commit is contained in:
Anna Claire Fields
2026-06-17 08:07:16 +00:00
committed by Copybot
parent d44e862a0c
commit 8f07308342
93 changed files with 1490 additions and 209 deletions
+1
View File
@@ -22,6 +22,7 @@ cypress/screenshots/
cypress/videos/
cypress/downloads/
cypress/results/
cypress/reporter/
# Ace themes for conversion
frontend/js/features/source-editor/themes/ace/
+3
View File
@@ -127,6 +127,9 @@ export default defineMain({
rootDir,
'modules/writefull/frontend/js/integration/src/'
),
// Allow all packages to resolve core-js (injected by Babel's
// useBuiltIns: 'usage') even under Yarn PnP strict resolution.
'core-js': path.dirname(require.resolve('core-js/package.json')),
},
},
module: {
-1
View File
@@ -4,7 +4,6 @@
FROM node:24.14.1 AS base
# Corepack setup, shared between all the images.
ENV PATH="/overleaf/node_modules/.bin:$PATH"
ENV COREPACK_HOME=/opt/corepack
RUN corepack enable && corepack install -g yarn@4.14.1
ENV COREPACK_ENABLE_NETWORK=0
+4 -3
View File
@@ -499,9 +499,9 @@ RUN_LINTING_MONOREPO = ../../bin/run monorepo yarn run --silent
ORG_PATH = /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
RUN_LINT_FORMAT ?= \
docker run --rm --env BRANCH_NAME --env CI --env COMMIT_SHA --env MONOREPO --env PATH=/overleaf/node_modules/.bin:$(ORG_PATH) --volume $(MONOREPO)/.prettierignore:/overleaf/.prettierignore --volume $(MONOREPO)/.prettierrc.cjs:/overleaf/.prettierrc.cjs --volume ${PWD}/data/reports:/overleaf/services/web/data/reports --volume $(MONOREPO)/.cache/:/overleaf/.cache/ ${IMAGE_CI}
docker run --rm --env BRANCH_NAME --env CI --env COMMIT_SHA --env MONOREPO --volume $(MONOREPO)/.prettierignore:/overleaf/.prettierignore --volume $(MONOREPO)/.prettierrc.cjs:/overleaf/.prettierrc.cjs --volume ${PWD}/data/reports:/overleaf/services/web/data/reports --volume $(MONOREPO)/.cache/:/overleaf/.cache/ ${IMAGE_CI}
NODE_MODULES_PATH := ${PATH}:${PWD}/node_modules/.bin:/overleaf/services/web/node_modules/.bin
NODE_MODULES_PATH := ${PATH}
WITH_NODE_MODULES_PATH = \
format_backend \
format_frontend \
@@ -583,7 +583,7 @@ lint_flag_res_send_usage:
lint: lint_overleafModuleImports
lint_overleafModuleImports:
node scripts/check_overleafModuleImports.mjs
yarn node scripts/check_overleafModuleImports.mjs
lint: typecheck_frontend
typecheck_frontend:
@@ -668,6 +668,7 @@ IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \
$(MONOREPO)/package.json \
$(MONOREPO)/yarn.lock \
$(MONOREPO)/libraries/access-token-encryptor/package.json \
$(MONOREPO)/libraries/eslint-config/package.json \
$(MONOREPO)/libraries/eslint-plugin/package.json \
$(MONOREPO)/libraries/fetch-utils/package.json \
$(MONOREPO)/libraries/logger/package.json \
@@ -5,7 +5,7 @@ set -e
# Ensure all locales used in the frontend are tracked
OUTPUT=data/dumpFolder/i18next-scanner
trap 'rm -rf "$OUTPUT"' EXIT
npx i18next-scanner --output "$OUTPUT"
yarn exec i18next-scanner --output "$OUTPUT"
ACTUAL=frontend/extracted-translations.json
EXPECTED="$OUTPUT/frontend/extracted-translations.json"
if ! diff "$ACTUAL" "$EXPECTED"; then
+4 -1
View File
@@ -1,10 +1,13 @@
import { defineConfig } from 'cypress'
import { webpackConfig } from './cypress/support/webpack.cypress'
// Cypress's Electron process uses PackherdModuleLoader which doesn't go through
// PnP hooks. Reporter packages are materialized into /tmp/cypress-reporter by
// scripts/materialize-cypress-reporter.js at container startup.
let reporterOptions = {}
if (process.env.CI) {
reporterOptions = {
reporter: 'cypress-multi-reporters',
reporter: '/tmp/cypress-reporter/node_modules/cypress-multi-reporters',
reporterOptions: {
configFile: 'cypress/cypress-multi-reporters.json',
},
+22 -9
View File
@@ -9,7 +9,9 @@ services:
target: base
volumes:
- .:/overleaf/services/web
- ../../node_modules:/overleaf/node_modules
- ../../.pnp.cjs:/overleaf/.pnp.cjs
- ../../.pnp.loader.mjs:/overleaf/.pnp.loader.mjs
- ../../.pnp.register.mjs:/overleaf/.pnp.register.mjs
- ../../libraries:/overleaf/libraries
- ../../bin/shared/wait_for_it:/overleaf/bin/shared/wait_for_it
- ../../tools/migrations:/overleaf/tools/migrations
@@ -30,7 +32,7 @@ services:
MOCHA_GREP: ${MOCHA_GREP:-}
LOG_LEVEL: ${LOG_LEVEL:-}
NODE_ENV: test
NODE_OPTIONS: "--unhandled-rejections=strict"
NODE_OPTIONS: "--require /overleaf/.pnp.cjs --import /overleaf/.pnp.register.mjs --unhandled-rejections=strict"
REDIS_HOST: redis_test
MONOREPO:
entrypoint: /overleaf/bin/shared/wait_for_it mongo:27017 --timeout=60 --
@@ -47,7 +49,9 @@ services:
target: base
volumes:
- .:/overleaf/services/web
- ../../node_modules:/overleaf/node_modules
- ../../.pnp.cjs:/overleaf/.pnp.cjs
- ../../.pnp.loader.mjs:/overleaf/.pnp.loader.mjs
- ../../.pnp.register.mjs:/overleaf/.pnp.register.mjs
- ../../libraries:/overleaf/libraries
- ../../bin/shared/wait_for_it:/overleaf/bin/shared/wait_for_it
- ../../tools/migrations:/overleaf/tools/migrations
@@ -69,6 +73,7 @@ services:
LOG_LEVEL: ${LOG_LEVEL:-}
MONGO_SERVER_SELECTION_TIMEOUT: 600000
MONGO_SOCKET_TIMEOUT: 300000
NODE_OPTIONS: "--require /overleaf/.pnp.cjs --import /overleaf/.pnp.register.mjs --unhandled-rejections=strict"
REDIS_HOST: redis_test
MONOREPO:
RETRIES:
@@ -91,7 +96,9 @@ services:
target: base
volumes:
- .:/overleaf/services/web
- ../../node_modules:/overleaf/node_modules
- ../../.pnp.cjs:/overleaf/.pnp.cjs
- ../../.pnp.loader.mjs:/overleaf/.pnp.loader.mjs
- ../../.pnp.register.mjs:/overleaf/.pnp.register.mjs
- ../../libraries:/overleaf/libraries
- ../../package.json:/overleaf/package.json
- ../../.yarnrc.yml:/overleaf/.yarnrc.yml
@@ -102,7 +109,7 @@ services:
CI:
JUNIT_ROOT_SUITE_NAME:
MOCHA_GREP: ${MOCHA_GREP}
NODE_OPTIONS: "--unhandled-rejections=strict"
NODE_OPTIONS: "--require /overleaf/.pnp.cjs --import /overleaf/.pnp.register.mjs --unhandled-rejections=strict"
VERBOSE_LOGGING:
command: yarn run --silent test:frontend
user: node
@@ -115,7 +122,10 @@ services:
- USER_GID
volumes:
- .:/overleaf/services/web
- ../../node_modules:/overleaf/node_modules
- ../../scripts:/overleaf/scripts:ro
- ../../.pnp.cjs:/overleaf/.pnp.cjs
- ../../.pnp.loader.mjs:/overleaf/.pnp.loader.mjs
- ../../.pnp.register.mjs:/overleaf/.pnp.register.mjs
- ../../libraries:/overleaf/libraries
- ../../package.json:/overleaf/package.json
- ../../.yarnrc.yml:/overleaf/.yarnrc.yml
@@ -128,7 +138,8 @@ services:
JUNIT_ROOT_SUITE_NAME:
CYPRESS_SPEC_PATTERN: ${CYPRESS_SPEC_PATTERN:-}
CYPRESS_EXCLUDE_SPEC_PATTERN: ${CYPRESS_EXCLUDE_SPEC_PATTERN:-}
entrypoint: npm
NODE_OPTIONS: "--require /overleaf/.pnp.cjs --import /overleaf/.pnp.register.mjs"
entrypoint: yarn
command:
- "run"
- "cypress:run-ct"
@@ -140,7 +151,9 @@ services:
target: base
volumes:
- .:/overleaf/services/web
- ../../node_modules:/overleaf/node_modules
- ../../.pnp.cjs:/overleaf/.pnp.cjs
- ../../.pnp.loader.mjs:/overleaf/.pnp.loader.mjs
- ../../.pnp.register.mjs:/overleaf/.pnp.register.mjs
- ../../libraries:/overleaf/libraries
- ../../package.json:/overleaf/package.json
- ../../.yarnrc.yml:/overleaf/.yarnrc.yml
@@ -150,7 +163,7 @@ services:
environment:
CI:
JUNIT_ROOT_SUITE_NAME:
NODE_OPTIONS: "--unhandled-rejections=strict"
NODE_OPTIONS: "--require /overleaf/.pnp.cjs --import /overleaf/.pnp.register.mjs --unhandled-rejections=strict"
VERBOSE_LOGGING:
command: yarn run --silent test:writefull
user: node
+26 -3
View File
@@ -3,6 +3,10 @@
"description": "The HTTP front end for Overleaf",
"private": true,
"main": "app.mjs",
"exports": {
".": "./app.mjs",
"./*": "./*"
},
"directories": {
"public": "./public"
},
@@ -36,8 +40,9 @@
"type-check:backend": "tsc -p tsconfig.backend.json --noEmit",
"extract-translations": "i18next-scanner",
"convert-themes": "node frontend/js/features/source-editor/themes/scripts/convert.js",
"cypress:open-ct": "OVERLEAF_CONFIG=$(pwd)/config/settings.webpack.js cypress open --component",
"cypress:run-ct": "OVERLEAF_CONFIG=$(pwd)/config/settings.webpack.js cypress run --component --browser chrome",
"materialize-reporter": "node scripts/materialize-cypress-reporter.js /tmp/cypress-reporter",
"cypress:open-ct": "yarn run materialize-reporter && OVERLEAF_CONFIG=$(pwd)/config/settings.webpack.js cypress open --component",
"cypress:run-ct": "yarn run materialize-reporter && OVERLEAF_CONFIG=$(pwd)/config/settings.webpack.js cypress run --component --browser chrome",
"cypress:docker:open-ct": "USER_UID=$(id -u) USER_GID=$(id -g) docker compose -f docker-compose.cypress.yml run --rm cypress yarn run cypress:open-ct",
"cypress:docker:run-ct": "USER_UID=$(id -u) USER_GID=$(id -g) docker compose -f docker-compose.cypress.yml run --rm cypress yarn run cypress:run-ct --browser chrome",
"lezer-latex:generate": "node scripts/lezer-latex/generate.mjs",
@@ -152,6 +157,7 @@
"minimist": "^1.2.7",
"mmmagic": "^0.5.3",
"moment": "^2.29.4",
"mongodb": "6.12.0",
"mongodb-legacy": "6.1.3",
"mongoose": "8.22.1",
"multer": "2.1.1",
@@ -198,6 +204,7 @@
"@ai-sdk/react": "^3.0.172",
"@babel/core": "^7.28.5",
"@babel/plugin-proposal-decorators": "^7.28.0",
"@babel/plugin-transform-typescript": "^7.28.0",
"@babel/preset-env": "^7.28.5",
"@babel/preset-react": "^7.28.5",
"@babel/preset-typescript": "^7.28.5",
@@ -235,6 +242,7 @@
"@replit/codemirror-indentation-markers": "overleaf/codemirror-indentation-markers#371ce3b56f453a392eb0d3b85ab019c185c68e1f",
"@replit/codemirror-vim": "overleaf/codemirror-vim#1bef138382d948018f3f9b8a4d7a70ab61774e4b",
"@sentry/browser": "7.46.0",
"@sentry/types": "7.46.0",
"@storybook/addon-a11y": "10.3.5",
"@storybook/addon-designs": "^11.1.3",
"@storybook/addon-docs": "10.3.5",
@@ -256,15 +264,19 @@
"@types/async": "^3.2.25",
"@types/bootstrap": "^5.2.10",
"@types/chai": "^4.3.0",
"@types/chai-as-promised": "^8.0.2",
"@types/dateformat": "^5.0.3",
"@types/diff": "^5.2.3",
"@types/dom-speech-recognition": "^0.0.7",
"@types/events": "^3.0.3",
"@types/express": "^4.17.23",
"@types/lodash": "^4.17.24",
"@types/mdast": "^4.0.4",
"@types/minimist": "^1.2.5",
"@types/mocha": "^9.1.0",
"@types/mocha-each": "^2.0.4",
"@types/node": "^24.5.2",
"@types/path-browserify": "^1.0.3",
"@types/react": "^18.3.20",
"@types/react-color": "^3.0.13",
"@types/react-dom": "^18.3.6",
@@ -273,15 +285,19 @@
"@types/react-syntax-highlighter": "^15.5.11",
"@types/recurly__recurly-js": "^4.38.0",
"@types/sanitize-html": "^2.14.0",
"@types/sinon": "^21.0.1",
"@types/sinon-chai": "^3.2.12",
"@types/utf-8-validate": "^5.0.2",
"@types/uuid": "^10.0.0",
"@typescript-eslint/eslint-plugin": "^8.59.4",
"@typescript-eslint/parser": "^8.59.4",
"@uppy/core": "^3.8.0",
"@uppy/dashboard": "3.7.1",
"@uppy/drag-drop": "3.0.3",
"@uppy/file-input": "3.0.4",
"@uppy/progress-bar": "3.0.4",
"@uppy/react": "3.2.1",
"@uppy/status-bar": "^3.2.5",
"@uppy/utils": "^5.7.0",
"@uppy/xhr-upload": "3.6.0",
"@vitest/coverage-istanbul": "^4.0.17",
@@ -316,6 +332,8 @@
"core-js": "~3.46.0",
"css-loader": "^7.1.4",
"css-minimizer-webpack-plugin": "^8.0.0",
"csv-parse": "^6.2.1",
"csv-stringify": "^6.7.0",
"cypress": "15.12.0",
"cypress-multi-reporters": "^2.0.5",
"cypress-plugin-tab": "^1.0.5",
@@ -326,9 +344,12 @@
"downshift": "^9.0.9",
"es6-promise": "^4.2.8",
"escodegen": "^2.0.0",
"eslint": "^10.4.0",
"eslint-config-prettier": "^10.1.8",
"eslint-formatter-unix": "^8.40.0",
"eslint-plugin-chai-expect": "^4.1.0",
"eslint-plugin-chai-friendly": "^1.2.0",
"eslint-plugin-cypress": "^4.1.0",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-mocha": "^11.3.0",
@@ -344,6 +365,7 @@
"fake-indexeddb": "^6.0.0",
"fast-fuzzy": "^1.12.0",
"fetch-mock": "^12.6.0",
"focus-trap": "^8.2.1",
"formik": "^2.2.9",
"glob": "^12.0.0",
"globals": "^17.6.0",
@@ -419,8 +441,9 @@
"timekeeper": "^2.2.0",
"to-string-loader": "^1.2.0",
"ts-loader": "^9.5.7",
"tslib": "^2.8.1",
"tty-browserify": "^0.0.1",
"typescript": "^5.9.3",
"typescript": "^5.0.4",
"unified": "^11.0.5",
"unist-util-visit": "^5.0.0",
"use-stick-to-bottom": "^1.1.1",
@@ -3,7 +3,7 @@ import fs from 'node:fs'
import Path from 'node:path'
import cheerio from 'cheerio'
// checkSanitizeOptions is only used in dev env
// eslint-disable-next-line import/no-extraneous-dependencies
// eslint-disable-next-line import/no-extraneous-dependencies, import/no-unresolved
import * as prettier from 'prettier'
import sanitizeHtml from 'sanitize-html'
import { sanitizeOptions } from '../../../modules/learn/app/src/sanitizeOptions.mjs'
@@ -0,0 +1,113 @@
#!/usr/bin/env node
'use strict'
/**
* Materialize Cypress reporter packages (and their transitive dependencies)
* into a real node_modules tree so that Cypress's Electron process (which uses
* PackherdModuleLoader, NOT Yarn PnP hooks) can load them.
*
* Builds a NESTED node_modules structure (each package gets its own
* node_modules/ for its deps) to avoid version conflicts from flat dedup.
*
* The target-dir must be OUTSIDE the PnP workspace root — otherwise PnP
* intercepts requires from the materialized packages and blocks undeclared deps.
*
* Usage: yarn node scripts/materialize-cypress-reporter.js <target-dir>
*/
const path = require('path')
const fs = require('fs')
const targetDir = process.argv[2]
if (!targetDir) {
console.error(
'Usage: yarn node scripts/materialize-cypress-reporter.js <target-dir>'
)
process.exit(1)
}
const pnp = require('module').findPnpApi(__filename)
if (!pnp) {
console.log('Not running under PnP — nothing to do.')
process.exit(0)
}
let totalCopied = 0
// PnP patches readFileSync/readdirSync/statSync but NOT cpSync.
// Manual recursive copy to read from virtual/zip paths.
function copyRecursive(src, dest) {
const stat = fs.statSync(src)
if (stat.isDirectory()) {
fs.mkdirSync(dest, { recursive: true })
for (const entry of fs.readdirSync(src)) {
copyRecursive(path.join(src, entry), path.join(dest, entry))
}
} else {
fs.mkdirSync(path.dirname(dest), { recursive: true })
fs.writeFileSync(dest, fs.readFileSync(src))
}
}
function resolvePkgDir(pkgName, issuer) {
// Use resolveToUnqualified to get the package directory without going
// through the package's exports map (which may not expose package.json).
let pkgDir = pnp.resolveToUnqualified(pkgName, issuer + '/')
if (pkgDir.endsWith('/')) pkgDir = pkgDir.slice(0, -1)
return pkgDir
}
/**
* Materialize a package and its deps into destDir/node_modules/pkgName.
* Each package's own deps get nested under its own node_modules/.
*/
function materializeRecursive(pkgName, issuer, destNodeModules, seen) {
// Dedupe by destination path to avoid infinite loops, but allow the same
// package to appear in multiple locations (nested node_modules).
const key = `${destNodeModules}/${pkgName}`
if (seen.has(key)) return
seen.add(key)
let pkgDir
try {
pkgDir = resolvePkgDir(pkgName, issuer)
} catch {
return // optional/missing dep
}
const dest = path.join(destNodeModules, pkgName)
copyRecursive(pkgDir, dest)
totalCopied++
const pkg = JSON.parse(
fs.readFileSync(path.join(pkgDir, 'package.json'), 'utf8')
)
const deps = [
...Object.keys(pkg.dependencies || {}),
...Object.keys(pkg.peerDependencies || {}),
]
if (deps.length > 0) {
const nestedNodeModules = path.join(dest, 'node_modules')
for (const dep of deps) {
materializeRecursive(dep, pkgDir, nestedNodeModules, seen)
}
}
}
const rootNodeModules = path.resolve(targetDir, 'node_modules')
const seen = new Set()
const topLevel = [
'cypress-multi-reporters',
'mocha-junit-reporter',
'typescript',
]
for (const pkg of topLevel) {
try {
materializeRecursive(pkg, process.cwd(), rootNodeModules, seen)
} catch (e) {
console.warn(`Warning: could not materialize ${pkg}: ${e.message}`)
}
}
console.log(`Materialized ${totalCopied} packages into ${rootNodeModules}`)
+12
View File
@@ -1,3 +1,15 @@
// Register PnP ESM loader via module.register() for synchronous ESM resolution
// (--experimental-loader from yarn only works for async resolution)
const { register } = require('node:module')
const { pathToFileURL } = require('node:url')
const path = require('node:path')
const pnpRegister = path.resolve(__dirname, '../../.pnp.register.mjs')
try {
register(pathToFileURL(pnpRegister), pathToFileURL(__filename))
} catch {
// .pnp.register.mjs may not exist in non-PnP environments
}
const chai = require('chai')
chai.should()
chai.use(require('chai-as-promised'))
@@ -119,7 +119,8 @@ describe('DeleteOrphanedDocsOnlineCheck', function () {
// Hide verbose log messages `calling destroy for project in docstore`
'LOG_LEVEL=error',
// Hide deprecation warnings for calling `db.collection.count`
'NODE_OPTIONS=--no-deprecation',
// Preserve existing NODE_OPTIONS (e.g. PnP hooks) while adding --no-deprecation
`NODE_OPTIONS='${process.env.NODE_OPTIONS || ''} --no-deprecation'`,
])
.concat(['node', 'scripts/delete_orphaned_docs_online_check.mjs'])
.join(' ')
@@ -22,7 +22,7 @@ export default {
this.timeout(60_000)
await promisify(exec)(
`cd ../../tools/migrations && npm run migrations -- migrate -t ${Settings.env || DEFAULT_ENV}`
`cd ../../tools/migrations && yarn run migrations migrate -t ${Settings.env || DEFAULT_ENV}`
)
})
@@ -10,7 +10,9 @@ function clearSettingsCache() {
const settingsDeps = Object.keys(require.cache).filter(
x =>
x.includes('/@overleaf/settings') ||
x.includes('/@overleaf-settings-virtual') ||
x.includes(`${monorepoPath}/libraries/settings`) ||
x.includes('/libraries/settings/') ||
x.includes(`${monorepoPath}/services/web/config`)
)
settingsDeps.forEach(dep => delete require.cache[dep])
+3
View File
@@ -20,6 +20,9 @@
"@modules/*": ["./modules/*"],
"@overleaf/o-error": ["../../libraries/o-error"],
"@overleaf/ranges-tracker": ["../../libraries/ranges-tracker"],
"@overleaf/validation-tools": ["../../libraries/validation-tools"],
"overleaf-editor-core": ["../../libraries/overleaf-editor-core"],
"overleaf-editor-core/*": ["../../libraries/overleaf-editor-core/*"],
"@ol-types/*": ["./types/*"],
"@ol-storybook/*": ["./.storybook/*"],
+11
View File
@@ -0,0 +1,11 @@
// Ambient module declaration for @uppy/utils subpath imports.
// Under Yarn PnP, TypeScript cannot find the ambient declarations in
// @uppy/utils/types/index.d.ts for deep imports like
// @uppy/utils/lib/getDroppedFiles. Re-declare the subset we use here.
declare module '@uppy/utils/lib/getDroppedFiles' {
function getDroppedFiles(
dataTransfer: DataTransfer,
options?: Record<string, unknown>
): Promise<File[]>
export default getDroppedFiles
}
+19 -6
View File
@@ -50,13 +50,11 @@ globSync(
})
function getModuleDirectory(moduleName) {
const entrypointPath = require.resolve(moduleName)
const suffix = `node_modules/${moduleName}`
const idx = entrypointPath.indexOf(suffix)
if (idx === -1) {
throw new Error(`could not find Node module: ${moduleName}`)
try {
return path.dirname(require.resolve(`${moduleName}/package.json`))
} catch (err) {
throw new Error(`could not find Node module: ${moduleName}`, { cause: err })
}
return entrypointPath.slice(0, idx + suffix.length)
}
const mathjaxDir = getModuleDirectory('mathjax')
@@ -312,6 +310,21 @@ module.exports = {
// Ensure all packages use the same jQuery instance (prevents duplicate
// copies from Yarn hoisting breaking jQuery plugins like daterangepicker)
jquery: require.resolve('jquery'),
// Under Yarn PnP, babel-injected core-js polyfill imports cannot be
// resolved from third-party packages (e.g. @uppy, pdfjs-dist) because
// they don't declare core-js as a dependency. Alias to the web
// workspace's copy so all packages can resolve it.
'core-js': path.dirname(require.resolve('core-js/package.json')),
// writefull's tsconfig uses importHelpers: true, which emits tslib
// imports that must be resolvable from the web workspace.
tslib: path.dirname(require.resolve('tslib/package.json')),
// Under PnP, packages like cypress/react that import react/react-dom
// can't resolve them from their own scope. Alias to web's copies.
react: path.dirname(require.resolve('react/package.json')),
'react-dom': path.dirname(require.resolve('react-dom/package.json')),
// ai-sdk packages declare zod as a peer dep but PnP can't resolve it
// from their scope in the Docker build.
zod: path.dirname(require.resolve('zod/package.json')),
},
// symlinks: false, // enable this while using `npm link`
extensions: ['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', '.json'],