🌳
pt0/deployF/pnpmF/recPkgJsonsPathAF.mts
1import * as _ from 'lodash-es'
2import fs from 'fs'
3import yaml from 'js-yaml'
12import { tsAbsPath } from '../../ptDirF.mts'
14let cachedWorkspaceH: any = null
15let cachedWorkspaceKey = ''
16let inFlightWorkspaceP: Promise<any> | null = null
18const getPnpmWorkspaceKey = () => {
19 try {
20 const wsStat = fs.statSync(`${ptDir}/pnpm-workspace.yaml`)
21 const lockStat = fs.statSync(`${ptDir}/pnpm-lock.yaml`)
22 return `${wsStat.mtimeMs}:${lockStat.mtimeMs}:${lockStat.size}`
23 } catch { return '' }
26// Memoized + in-flight-deduped. Workspace list is process-stable; invalidate on pnpm-workspace.yaml/pnpm-lock.yaml mtime.
27// Without this, recPkgJsonsPathA's parallel recursion spawned 60+ concurrent `pnpm m ls` processes per call.
28export const getPnpmWorkspaceH = async () => {
29 const key = getPnpmWorkspaceKey()
30 if (cachedWorkspaceH && key === cachedWorkspaceKey) return cachedWorkspaceH
31 if (inFlightWorkspaceP) return inFlightWorkspaceP
32 inFlightWorkspaceP = (async () => {
33 const {stdout} = await execFileThrow({cmdA: 'corepack pnpm m ls --depth -1 --json'.split(' ')})
34 const h = _.chain(json1Parse(stdout as string) as any[]).map(({name, ...row}: any) => [name, row]).fromPairs().value()
35 cachedWorkspaceH = h
36 cachedWorkspaceKey = key
37 inFlightWorkspaceP = null
38 return h
39 })()
40 return inFlightWorkspaceP
43// seenSet is shared across the whole traversal: workspace dep graphs may contain cycles
44// (mutual workspace: deps), and per-path ancestor guards explode combinatorially (EMFILE/multi-min hangs).
45export const recPkgJsonsPathA = async ({packageJsonPath, seenSet = new Set<string>()}: {packageJsonPath: string, seenSet?: Set<string>}): Promise<{packageJsonPath: string, pkgJson: any}[]> => {
46 if (seenSet.has(packageJsonPath)) return []
47 seenSet.add(packageJsonPath)
49 const pkgJson = json1Parse(await read1File(tsAbsPath(packageJsonPath))) as any
51 const pkgName = pkgJson.name
52 if (pkgName) {
53 const pnpmWorkspaceH = await getPnpmWorkspaceH()
54 if (!pnpmWorkspaceH[pkgName]) {
55 const ptPath = absPathToPtPath(tsAbsPath(packageJsonPath)).replace('/package.json', '')
56 const yesno = promptSync(
57 `Package "${pkgName}" at ${ptPath} not in pnpm workspace. Add and run pnpm install? y/n: `
58 )
59 if (yesno === 'y') {
60 const workspacePath = `${ptDir}/pnpm-workspace.yaml`
61 const content = fs.readFileSync(workspacePath, 'utf8')
62 const workspaceYaml = yaml.load(content) as {packages: string[]}
63 workspaceYaml.packages.push(ptPath)
64 fs.writeFileSync(workspacePath, yaml.dump(workspaceYaml, {lineWidth: -1}))
65 console.log(`Added ${ptPath} to pnpm-workspace.yaml`)
67 console.log('Running pnpm install...')
68 await execFileThrow({cmdA: ['corepack', 'pnpm', 'install']})
70 console.log('Committing pnpm-workspace.yaml and pnpm-lock.yaml...')
71 await execFileThrow({cmdA: ['git', 'add', 'pnpm-workspace.yaml', 'pnpm-lock.yaml']})
72 await execFileThrow({cmdA: ['git', 'commit', '-m', `Add ${pkgName} package to pnpm workspace`]})
74 console.log('Done. Please re-run your command.')
75 process.exit(0)
76 } else {
77 throw new Error(
78 `Package "${pkgName}" not in pnpm workspace.\n` +
79 `Add this line to pnpm-workspace.yaml:\n - "${ptPath}"\n` +
80 `Then run: pnpm install`
81 )
82 }
83 }
84 }
86 const depWorkspaceA = _.chain(pkgJson.dependencies).map((versionStr, depName) => {
87 if (_.startsWith(versionStr, 'workspace:')) return depName
88 return
89 }).compact().value()
91 const pnpmWorkspaceH = await getPnpmWorkspaceH()
93 return [{packageJsonPath, pkgJson}, ..._.flatten(await allPromCalls(depWorkspaceA, async (workspaceName) => {
94 const {path} = pnpmWorkspaceH[workspaceName]
95 return await recPkgJsonsPathA({
96 seenSet,
97 packageJsonPath: path + '/package.json',
98 })
99 }))]
102export const getPkgJsonPtPathsA = async ({appPath}: {appPath: string}) => {
103 const packageJsonPath = `${ptDir}/${appPath}/package.json`
104 const pkgJsonA = await recPkgJsonsPathA({packageJsonPath})
105 return _.map(_.map(pkgJsonA, 'packageJsonPath'), absPathToPtPath)