Repository navigation
build: opt-in pnpm global store makes dev worktrees install in ~1s and delete instantly #22765
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,21 +1,65 @@ | ||
| // Windows WSL dev mode runs the server under Linux from the same node_modules | ||
| // as the Windows client, so Windows installs also need Linux x64 glibc native | ||
| // modules. Other platforms install only their own binaries; setting this | ||
| // everywhere added ~2.2 GB of foreign binaries to every macOS install. | ||
| // Release scripts stage their own installs with explicit architectures. | ||
| // oxlint-disable t3code/no-global-process-runtime -- pnpm loads this file before any Effect runtime exists. | ||
| module.exports = { | ||
| hooks: { | ||
| updateConfig(config) { | ||
| // oxlint-disable-next-line t3code/no-global-process-runtime -- pnpm loads this file before any Effect runtime exists. | ||
| if (process.platform !== "win32") return config; | ||
| // Many React libraries ship types that import "react" without declaring | ||
| // @types/react. TypeScript then finds @types/react only by walking up | ||
| // node_modules from the library's real path. The global virtual store | ||
| // has no hoisted copy on that path, so the types become `any`. An | ||
| // optional peer links the consumer's @types/react next to the library. | ||
| // Our own packages are private (npm never publishes those) and are left | ||
| // alone: pnpm checks local `file:` packages against the lockfile without | ||
| // this hook. | ||
| readPackage(manifest) { | ||
| if (manifest.private) return manifest; | ||
| const declaresReact = | ||
| manifest.peerDependencies?.react !== undefined || | ||
| manifest.dependencies?.react !== undefined; | ||
| if (!declaresReact || manifest.name === "@types/react") return manifest; | ||
| if ( | ||
| manifest.dependencies?.["@types/react"] !== undefined || | ||
| manifest.peerDependencies?.["@types/react"] !== undefined | ||
| ) { | ||
| return manifest; | ||
| } | ||
| return { | ||
| ...config, | ||
| supportedArchitectures: { | ||
| ...manifest, | ||
| peerDependencies: { ...manifest.peerDependencies, "@types/react": "*" }, | ||
| peerDependenciesMeta: { | ||
| ...manifest.peerDependenciesMeta, | ||
| "@types/react": { optional: true }, | ||
| }, | ||
| }; | ||
| }, | ||
| updateConfig(config) { | ||
| const next = { ...config }; | ||
| // Dev machines link packages from one shared copy in <storeDir>/links, | ||
| // so a worktree install takes seconds and a worktree delete is instant. | ||
| // CI keeps per-project stores: pnpm 12 honors an explicit setting there | ||
| // too, and a fresh runner has nothing to share. Windows is left out: | ||
| // WSL dev runs the server from the same node_modules, and store links | ||
| // across that boundary are untested. | ||
| // T3CODE_PNPM_GLOBAL_VIRTUAL_STORE=0 opts a dev machine out. | ||
| if ( | ||
| !process.env.CI && | ||
| process.platform !== "win32" && | ||
| process.env.T3CODE_PNPM_GLOBAL_VIRTUAL_STORE !== "0" | ||
| ) { | ||
| next.virtualStoreType = "global"; | ||
| } | ||
| // Windows WSL dev mode runs the server under Linux from the same | ||
| // node_modules as the Windows client, so Windows installs also need | ||
| // Linux x64 glibc native modules. Other platforms install only their own | ||
| // binaries; setting this everywhere added ~2.2 GB of foreign binaries to | ||
| // every macOS install. Release scripts stage their own installs with | ||
| // explicit architectures. | ||
| if (process.platform === "win32") { | ||
| next.supportedArchitectures = { | ||
| os: ["current", "linux"], | ||
| cpu: ["current", "x64"], | ||
| libc: ["current", "glibc"], | ||
| }, | ||
| }; | ||
| }; | ||
| } | ||
| return next; | ||
| }, | ||
| }, | ||
| }; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -34,7 +34,77 @@ const resolveShikiDependencyRoot = (packageName) => { | |||||||||||||||||||||||||||||||||||||
| return currentDir; | ||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| config.watchFolders = [...new Set([...(config.watchFolders ?? []), workspaceRoot])]; | ||||||||||||||||||||||||||||||||||||||
| // With pnpm's global virtual store (see .pnpmfile.cjs), package files live in | ||||||||||||||||||||||||||||||||||||||
| // the shared store, outside the workspace, and Metro only reads files inside | ||||||||||||||||||||||||||||||||||||||
| // watchFolders. Watch the store slot of each package reachable from this app, | ||||||||||||||||||||||||||||||||||||||
| // not the whole store: it holds every project's packages. A slot's | ||||||||||||||||||||||||||||||||||||||
| // node_modules holds the package and links to its dependencies. | ||||||||||||||||||||||||||||||||||||||
| const modulesState = JSON.parse( | ||||||||||||||||||||||||||||||||||||||
| fs.readFileSync(path.join(workspaceRoot, "node_modules", ".modules.yaml"), "utf8"), | ||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||
| const isInWorkspace = (dir) => !path.relative(workspaceRoot, dir).startsWith(".."); | ||||||||||||||||||||||||||||||||||||||
| const usesGlobalStore = !isInWorkspace( | ||||||||||||||||||||||||||||||||||||||
| path.resolve(workspaceRoot, "node_modules", modulesState.virtualStoreDir), | ||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+42
to
+48
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: sed -n '1,135p' apps/mobile/metro.config.js
if test -f node_modules/.modules.yaml; then rg -n 'virtualStoreDir|storeDir' node_modules/.modules.yaml; fiRepository: pingdotgg/t3code Length of output: 5427 🏁 Script executed: set -o pipefail
printf '%s\n' '--- package-manager declarations ---'
rg -n -F --glob 'package.json' -e '"packageManager"' -e '"pnpm"' -e '"engines"' . || test "$?" -eq 1
rg -n -F --glob '.npmrc' -e 'virtual-store' -e 'node-linker' -e 'shared-workspace-lockfile' . || test "$?" -eq 1
printf '%s\n' '--- setup-worktree references ---'
rg -n -F -- 'virtualStoreDir' scripts apps package.json pnpm-workspace.yaml .pnpmfile.cjs .npmrc 2>/dev/null || test "$?" -eq 1
printf '%s\n' '--- setup-worktree block ---'
python3 - <<'PY'
from pathlib import Path
p = Path('scripts/setup-worktree.ts')
if p.exists():
lines = p.read_text().splitlines()
for i, line in enumerate(lines, 1):
if 'virtualStoreDir' in line or 'modules.yaml' in line or 'global' in line:
lo=max(1,i-20); hi=min(len(lines),i+25)
print(f'--- {p}:{lo}-{hi} ---')
for n in range(lo,hi+1):
print(f'{n}: {lines[n-1]}')
else:
print('scripts/setup-worktree.ts: absent')
PY
printf '%s\n' '--- pnpm configuration files ---'
for f in .pnpmfile.cjs .npmrc pnpm-workspace.yaml; do
if test -f "$f"; then
echo "--- $f ---"
sed -n '1,240p' "$f"
fi
doneRepository: pingdotgg/t3code Length of output: 26863 🌐 Web query:
💡 Result: 🏁 Script executed: set -o pipefail
printf '%s\n' '--- Metro startup entrypoints and install prerequisites ---'
rg -n -F --glob 'package.json' -e 'apps/mobile' -e 'expo start' -e 'metro' -e 'setup-worktree' -e 'pnpm install' .
rg -n -F --glob '*.ts' --glob '*.js' --glob '*.mjs' --glob '*.cjs' -e 'metro.config.js' -e 'apps/mobile' -e 'getDefaultConfig' scripts apps package.json .github 2>/dev/null || test "$?" -eq 1
printf '%s\n' '--- root package scripts and mobile scripts ---'
python3 - <<'PY'
import json
from pathlib import Path
for name in ('package.json', 'apps/mobile/package.json'):
p=Path(name)
if not p.exists():
continue
data=json.loads(p.read_text())
print(f'--- {name} scripts ---')
for k,v in data.get('scripts',{}).items():
print(f'{k}: {v}')
PY
printf '%s\n' '--- setup-worktree install lifecycle ---'
sed -n '1,90p' scripts/setup-worktree.ts
printf '%s\n' '--- relevant documentation ---'
rg -n -i -F --glob '*.md' -e 'setup-worktree' -e 'pnpm install' -e 'mobile' -e 'metro' docs README.md apps 2>/dev/null | head -160 || trueRepository: pingdotgg/t3code Length of output: 40114 🏁 Script executed: printf '%s\n' '--- mobile startup instructions ---'
sed -n '1,70p' apps/mobile/README.md
printf '%s\n' '--- config-loading test context ---'
sed -n '1,80p' apps/mobile/src/reactCompilerCoverage.test.ts
printf '%s\n' '--- setup documentation context ---'
sed -n '1,80p' docs/operations/development.mdRepository: pingdotgg/t3code Length of output: 9853 🌐 Web query:
💡 Result: 🌐 Web query:
💡 Result: 🏁 Script executed: python3 - <<'PY'
from urllib.request import Request, urlopen
urls = [
'https://raw.githubusercontent.com/pnpm/pnpm/v12.14.0/pkg-manager/modules-yaml/src/index.ts',
'https://raw.githubusercontent.com/pnpm/pnpm/v12.14.0/pkg-manager/modules-yaml/src/extendModulesOptions.ts',
'https://raw.githubusercontent.com/pnpm/pnpm/v12.14.0/pkg-manager/modules-yaml/src/typedSchema.ts',
]
for url in urls:
print(f'--- {url} ---')
try:
req = Request(url, headers={'User-Agent': 'review-inspection'})
text = urlopen(req, timeout=10).read().decode()
except Exception as e:
print(f'UNAVAILABLE: {e}')
continue
for n, line in enumerate(text.splitlines(), 1):
if 'virtualStoreDir' in line or 'Modules' in line or 'write' in line or 'read' in line:
print(f'{n}: {line}')
PYRepository: pingdotgg/t3code Length of output: 589 🏁 Script executed: python3 - <<'PY'
import json
from urllib.request import Request, urlopen
base='https://api.github.com/repos/pnpm/pnpm/git/trees/v12.14.0?recursive=1'
req=Request(base, headers={'User-Agent':'review-inspection','Accept':'application/vnd.github+json'})
try:
data=json.load(urlopen(req, timeout=20))
except Exception as e:
print(f'UNAVAILABLE tree: {e}')
raise SystemExit
paths=[x['path'] for x in data.get('tree',[]) if 'modules-yaml' in x['path']]
print('matching paths:')
for p in paths:
print(p)
for p in paths:
if p.endswith(('.ts','.d.ts')) and any(k in p for k in ('index','types','schema')):
url='https://raw.githubusercontent.com/pnpm/pnpm/v12.14.0/'+p
print(f'--- {url} ---')
try:
text=urlopen(Request(url,headers={'User-Agent':'review-inspection'}),timeout=10).read().decode()
except Exception as e:
print(f'UNAVAILABLE: {e}')
continue
for n,line in enumerate(text.splitlines(),1):
if 'virtualStoreDir' in line or 'ModulesRaw' in line or 'Modules' in line:
print(f'{n}: {line}')
PYRepository: pingdotgg/t3code Length of output: 6674 🏁 Script executed: python3 - <<'PY'
from urllib.request import Request, urlopen
base='https://raw.githubusercontent.com/pnpm/pnpm/v12.14.0/'
paths=[
'pnpm11/installing/modules-yaml/src/index.ts',
'pnpm11/installing/modules-yaml/test/fixtures/old-no-shamefully-hoist/.modules.yaml',
'pnpm11/installing/modules-yaml/test/fixtures/old-shamefully-hoist/.modules.yaml',
'pnpm11/installing/modules-yaml/test/fixtures/empty-modules-yaml/.modules.yaml',
]
for path in paths:
url=base+path
print(f'--- {url} ---')
try:
text=urlopen(Request(url,headers={'User-Agent':'review-inspection'}),timeout=10).read().decode()
except Exception as e:
print(f'UNAVAILABLE: {e}')
continue
for n,line in enumerate(text.splitlines(),1):
if path.endswith('index.ts') and not (15 <= n <= 160):
continue
print(f'{n}: {line}')
PYRepository: pingdotgg/t3code Length of output: 7756 Handle a manifest without Fresh pnpm 12 installs write this field as a string. However, pnpm's compatibility reader defaults a missing field to A missing Suggested fix const usesGlobalStore = !isInWorkspace(
- path.resolve(workspaceRoot, "node_modules", modulesState.virtualStoreDir),
+ path.resolve(
+ workspaceRoot,
+ "node_modules",
+ typeof modulesState.virtualStoreDir === "string" ? modulesState.virtualStoreDir : ".pnpm",
+ ),
);📝 Committable suggestion
Suggested change
🧰 Tools🪛 ast-grep (0.45.3)[warning] 43-43: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use. (detect-non-literal-fs-filename) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
| // pnpm still hoists every package here. Store packages are not below it, so | ||||||||||||||||||||||||||||||||||||||
| // Node reaches it through NODE_PATH, and Metro through nodeModulesPaths. Some | ||||||||||||||||||||||||||||||||||||||
| // packages import dependencies they never declare. | ||||||||||||||||||||||||||||||||||||||
| const hoistedModulesDir = path.join(workspaceRoot, "node_modules", ".pnpm", "node_modules"); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| function globalStoreSlotDirs() { | ||||||||||||||||||||||||||||||||||||||
| const slotDirs = new Set(); | ||||||||||||||||||||||||||||||||||||||
| const visited = new Set(); | ||||||||||||||||||||||||||||||||||||||
| const pending = [ | ||||||||||||||||||||||||||||||||||||||
| path.join(__dirname, "node_modules"), | ||||||||||||||||||||||||||||||||||||||
| path.join(workspaceRoot, "node_modules"), | ||||||||||||||||||||||||||||||||||||||
| hoistedModulesDir, | ||||||||||||||||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||||||||||||||||
| while (pending.length > 0) { | ||||||||||||||||||||||||||||||||||||||
| const modulesDir = pending.pop(); | ||||||||||||||||||||||||||||||||||||||
| if (visited.has(modulesDir)) continue; | ||||||||||||||||||||||||||||||||||||||
| visited.add(modulesDir); | ||||||||||||||||||||||||||||||||||||||
| let entries; | ||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||
| entries = fs.readdirSync(modulesDir, { withFileTypes: true }); | ||||||||||||||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||||||||||||||
| continue; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| const scoped = path.basename(modulesDir).startsWith("@"); | ||||||||||||||||||||||||||||||||||||||
| for (const entry of entries) { | ||||||||||||||||||||||||||||||||||||||
| if (entry.name.startsWith(".")) continue; | ||||||||||||||||||||||||||||||||||||||
| const entryPath = path.join(modulesDir, entry.name); | ||||||||||||||||||||||||||||||||||||||
| if (!scoped && entry.name.startsWith("@")) { | ||||||||||||||||||||||||||||||||||||||
| pending.push(entryPath); | ||||||||||||||||||||||||||||||||||||||
| continue; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| let realDir; | ||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||
| realDir = fs.realpathSync(entryPath); | ||||||||||||||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||||||||||||||
| continue; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| if (isInWorkspace(realDir)) { | ||||||||||||||||||||||||||||||||||||||
| // A workspace package: its own node_modules links into the store. | ||||||||||||||||||||||||||||||||||||||
| pending.push(path.join(realDir, "node_modules")); | ||||||||||||||||||||||||||||||||||||||
| continue; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| const slotDir = scoped ? path.dirname(path.dirname(realDir)) : path.dirname(realDir); | ||||||||||||||||||||||||||||||||||||||
| if (!slotDirs.has(slotDir)) { | ||||||||||||||||||||||||||||||||||||||
| slotDirs.add(slotDir); | ||||||||||||||||||||||||||||||||||||||
| pending.push(slotDir); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| return [...slotDirs]; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| config.watchFolders = [ | ||||||||||||||||||||||||||||||||||||||
| ...new Set([ | ||||||||||||||||||||||||||||||||||||||
| ...(config.watchFolders ?? []), | ||||||||||||||||||||||||||||||||||||||
| workspaceRoot, | ||||||||||||||||||||||||||||||||||||||
| ...(usesGlobalStore ? globalStoreSlotDirs() : []), | ||||||||||||||||||||||||||||||||||||||
| ]), | ||||||||||||||||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||||||||||||||||
| config.resolver = { | ||||||||||||||||||||||||||||||||||||||
| ...config.resolver, | ||||||||||||||||||||||||||||||||||||||
| blockList: [ | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -45,6 +115,9 @@ config.resolver = { | |||||||||||||||||||||||||||||||||||||
| : []), | ||||||||||||||||||||||||||||||||||||||
| new RegExp(`${escapedWorkspaceRoot}[/\\\\]\\.t3[/\\\\].*`), | ||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||
| ...(usesGlobalStore && { | ||||||||||||||||||||||||||||||||||||||
| nodeModulesPaths: [...(config.resolver?.nodeModulesPaths ?? []), hoistedModulesDir], | ||||||||||||||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||||||||||||||
| extraNodeModules: { | ||||||||||||||||||||||||||||||||||||||
| ...config.resolver?.extraNodeModules, | ||||||||||||||||||||||||||||||||||||||
| "@t3tools/mobile-third-party-licenses": generatedLicenseModuleRoot, | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.