mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
build: declare dependencies that only hoisting supplied (#30225)
* build: declare dependencies that only hoisting supplied Under pnpm's global virtual store each package sees only its declared dependencies. Several published packages relied on hoisting: - jest-dom's vitest matcher augmentation could not find vitest (1645 TS2339 errors in tc:web), and react libraries' .d.ts could not find @types/react, so lucide-react, dnd-kit, cmdk, sonner and others typed as `any`. Other .d.ts phantoms (electron, vfile, prosemirror-model, d3-selection, type-fest) degraded silently the same way. - electron-vite resolves electron/package.json and the binary itself. packageExtensions declares these, which also holds in the hoisted layout. The markdown alias now resolves decode-named-character-reference through mdast-util-from-markdown, the package that actually declares it. * build: let addMissingPeerTypes supply @types/react to react peers pnpm 12.12 links a peer's @types package next to every package that peer-depends on it, which replaces the hand-listed @types/react peers. The regression test now reads pnpm-workspace.yaml, so it checks every packageExtensions entry instead of a hand-picked seven.
This commit is contained in:
@@ -1,11 +1,16 @@
|
||||
import { createRequire } from 'node:module'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
const parserRequire = createRequire(require.resolve('remark-parse'))
|
||||
// Why: walk the declared dependency chain; isolated installs don't hoist decode-named-character-reference next to remark-parse.
|
||||
const fromMarkdownRequire = createRequire(
|
||||
createRequire(require.resolve('remark-parse')).resolve('mdast-util-from-markdown')
|
||||
)
|
||||
const mathRequire = createRequire(require.resolve('rehype-katex'))
|
||||
|
||||
// Both preview paths use DOM-free parsing so their dependencies also run in workers.
|
||||
export const markdownParserAliases = {
|
||||
'decode-named-character-reference': parserRequire.resolve('decode-named-character-reference'),
|
||||
'decode-named-character-reference': fromMarkdownRequire.resolve(
|
||||
'decode-named-character-reference'
|
||||
),
|
||||
'hast-util-from-html-isomorphic': mathRequire.resolve('hast-util-from-html-isomorphic')
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import { existsSync, readFileSync, realpathSync } from 'node:fs'
|
||||
import { createRequire } from 'node:module'
|
||||
import { join } from 'node:path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { parse } from 'yaml'
|
||||
import { z } from 'zod'
|
||||
import { markdownParserAliases } from '../build-plugins/markdown-parser-exports'
|
||||
|
||||
const repoRoot = join(import.meta.dirname, '../..')
|
||||
const repoNodeModules = join(repoRoot, 'node_modules')
|
||||
|
||||
const dependencyMap = z.record(z.string(), z.string()).optional()
|
||||
const workspace = z
|
||||
.object({
|
||||
packageExtensions: z
|
||||
.record(
|
||||
z.string(),
|
||||
z.object({ dependencies: dependencyMap, peerDependencies: dependencyMap })
|
||||
)
|
||||
.optional(),
|
||||
addMissingPeerTypes: z.boolean().optional()
|
||||
})
|
||||
.parse(parse(readFileSync(join(repoRoot, 'pnpm-workspace.yaml'), 'utf8')))
|
||||
|
||||
// Every dependency pnpm-workspace.yaml adds, so a new entry is covered without editing this file.
|
||||
const extensionPairs = Object.entries(workspace.packageExtensions ?? {}).flatMap(
|
||||
([owner, extension]) =>
|
||||
[
|
||||
...Object.keys(extension.dependencies ?? {}),
|
||||
...Object.keys(extension.peerDependencies ?? {})
|
||||
].map((dependency) => [owner, dependency] as const)
|
||||
)
|
||||
|
||||
// These peer on react and ship .d.ts that import it; addMissingPeerTypes supplies @types/react.
|
||||
const peerTypePairs = [
|
||||
'@dnd-kit/core',
|
||||
'@dnd-kit/sortable',
|
||||
'@dnd-kit/utilities',
|
||||
'@monaco-editor/react',
|
||||
'cmdk',
|
||||
'emoji-picker-react',
|
||||
'lucide-react',
|
||||
'react-colorful',
|
||||
'react-i18next',
|
||||
'sonner'
|
||||
].map((owner) => [owner, '@types/react'] as const)
|
||||
|
||||
// Node's own lookup from the package's real location, without tripping over "exports" maps.
|
||||
function isVisibleFrom(owner: string, dependency: string): boolean {
|
||||
const ownerDir = realpathSync(join(repoNodeModules, owner))
|
||||
const lookupDirs = createRequire(join(ownerDir, 'package.json')).resolve.paths(dependency) ?? []
|
||||
return lookupDirs.some((dir) => existsSync(join(dir, dependency, 'package.json')))
|
||||
}
|
||||
|
||||
describe('undeclared dependency resolution', () => {
|
||||
it('covers every packageExtensions entry', () => {
|
||||
const owners = new Set(extensionPairs.map(([owner]) => owner))
|
||||
expect([...owners].sort()).toEqual(Object.keys(workspace.packageExtensions ?? {}).sort())
|
||||
expect(workspace.addMissingPeerTypes).toBe(true)
|
||||
})
|
||||
|
||||
// Why: the global virtual store links only declared deps; pnpm-workspace.yaml supplies these.
|
||||
it.each([...extensionPairs, ...peerTypePairs])(
|
||||
'%s sees %s from its own install location',
|
||||
(owner, dependency) => {
|
||||
expect(isVisibleFrom(owner, dependency)).toBe(true)
|
||||
}
|
||||
)
|
||||
|
||||
it('aliases the DOM-free decode-named-character-reference entry', () => {
|
||||
expect(markdownParserAliases['decode-named-character-reference']).toMatch(/[\\/]index\.js$/)
|
||||
})
|
||||
})
|
||||
Generated
+1249
-531
File diff suppressed because it is too large
Load Diff
@@ -116,6 +116,50 @@ allowBuilds:
|
||||
'@orca/windows-registry': false
|
||||
'@orca/windows-security': false
|
||||
|
||||
# Links the project's @types/<peer> next to every package that peer-depends on <peer> (e.g.
|
||||
# @types/react beside lucide-react), so peer types resolve under the global virtual store.
|
||||
addMissingPeerTypes: true
|
||||
|
||||
# These packages' published .d.ts import modules they never declare. Hoisting hides that,
|
||||
# but under the global virtual store each package sees only its declared deps, so the types
|
||||
# silently became `any` or (jest-dom) the vitest matcher augmentation vanished.
|
||||
packageExtensions:
|
||||
'@testing-library/jest-dom':
|
||||
dependencies:
|
||||
'@types/aria-query': ^5.0.1
|
||||
peerDependencies:
|
||||
vitest: '*'
|
||||
peerDependenciesMeta:
|
||||
vitest:
|
||||
optional: true
|
||||
'@tiptap/extension-list':
|
||||
dependencies:
|
||||
prosemirror-model: ^1.0.0
|
||||
# electron-vite reads electron/package.json and the binary path at build and dev time.
|
||||
electron-vite:
|
||||
peerDependencies:
|
||||
electron: '*'
|
||||
electron-updater:
|
||||
peerDependencies:
|
||||
electron: '*'
|
||||
peerDependenciesMeta:
|
||||
electron:
|
||||
optional: true
|
||||
mermaid:
|
||||
dependencies:
|
||||
'@iconify/types': ^2.0.0
|
||||
'@types/d3-selection': ^3.0.0
|
||||
type-fest: ^5.0.0
|
||||
playwright-core:
|
||||
peerDependencies:
|
||||
electron: '*'
|
||||
peerDependenciesMeta:
|
||||
electron:
|
||||
optional: true
|
||||
remark-parse:
|
||||
dependencies:
|
||||
vfile: ^6.0.0
|
||||
|
||||
overrides:
|
||||
monaco-editor>dompurify: 3.4.16
|
||||
# Upstream markdown/math packages still require the vulnerable 0.16 line.
|
||||
|
||||
Reference in New Issue
Block a user