Skip to content

Commit d44039b

Browse files
authored
feat(core): add support for pnpm catalogs (#1989)
* feat(core): add support for pnpm catalogs * fix(core): add pnpm-workspace for testing
1 parent 4319274 commit d44039b

File tree

2 files changed

+95
-3
lines changed

2 files changed

+95
-3
lines changed

packages/orval/src/utils/package-json.ts

+78-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import { PackageJson } from '@orval/core';
1+
import { log, PackageJson } from '@orval/core';
2+
import chalk from 'chalk';
23
import findUp from 'find-up';
34
import fs from 'fs-extra';
5+
import yaml from 'js-yaml';
46
import { normalizePath } from './options';
57

68
export const loadPackageJson = async (
@@ -13,7 +15,7 @@ export const loadPackageJson = async (
1315
});
1416
if (pkgPath) {
1517
const pkg = await import(pkgPath);
16-
return pkg;
18+
return await maybeReplaceCatalog(pkg, workspace);
1719
}
1820
return;
1921
}
@@ -22,7 +24,80 @@ export const loadPackageJson = async (
2224
if (fs.existsSync(normalizedPath)) {
2325
const pkg = await import(normalizedPath);
2426

25-
return pkg;
27+
return await maybeReplaceCatalog(pkg, workspace);
2628
}
2729
return;
2830
};
31+
32+
const maybeReplaceCatalog = async (
33+
pkg: PackageJson,
34+
workspace: string,
35+
): Promise<PackageJson> => {
36+
if (
37+
![
38+
...Object.entries(pkg.dependencies ?? {}),
39+
...Object.entries(pkg.devDependencies ?? {}),
40+
...Object.entries(pkg.peerDependencies ?? {}),
41+
].some(([key]) => key.startsWith('catalog:'))
42+
) {
43+
return pkg;
44+
}
45+
46+
const filePath = await findUp('pnpm-workspace.yaml', { cwd: workspace });
47+
if (!filePath) {
48+
log(
49+
`⚠️ ${chalk.yellow('package.json contains pnpm catalog: in dependencies, but no pnpm-workspace.yaml was found.')}`,
50+
);
51+
return pkg;
52+
}
53+
const file = await fs.readFile(filePath, 'utf8');
54+
55+
const pnpmWorkspaceFile = yaml.load(file) as Record<string, any>;
56+
performSubstitution(pkg.dependencies, pnpmWorkspaceFile);
57+
performSubstitution(pkg.devDependencies, pnpmWorkspaceFile);
58+
performSubstitution(pkg.peerDependencies, pnpmWorkspaceFile);
59+
60+
return pkg;
61+
};
62+
63+
const performSubstitution = (
64+
dependencies: Record<string, string> | undefined,
65+
pnpmWorkspaceFile: Record<string, any>,
66+
) => {
67+
if (!dependencies) return;
68+
for (const [packageName, version] of Object.entries(dependencies)) {
69+
if (version === 'catalog:' || version === 'catalog:default') {
70+
if (!pnpmWorkspaceFile.catalog) {
71+
log(
72+
`⚠️ ${chalk.yellow(`when reading from pnpm-workspace.yaml, catalog: substitution for the package '${packageName}' failed as there were no default catalog.`)}`,
73+
);
74+
continue;
75+
}
76+
const sub = pnpmWorkspaceFile.catalog[packageName];
77+
if (!sub) {
78+
log(
79+
`⚠️ ${chalk.yellow(`when reading from pnpm-workspace.yaml, catalog: substitution for the package '${packageName}' failed as there were no matching package in the default catalog.`)}`,
80+
);
81+
continue;
82+
}
83+
dependencies[packageName] = sub;
84+
} else if (version.startsWith('catalog:')) {
85+
const catalogName = version.substring('catalog:'.length);
86+
const catalog = pnpmWorkspaceFile.catalogs?.[catalogName];
87+
if (!catalog) {
88+
log(
89+
`⚠️ ${chalk.yellow(`when reading from pnpm-workspace.yaml, '${version}' substitution for the package '${packageName}' failed as there were no matching catalog named '${catalogName}'. (available named catalogs are: ${Object.keys(pnpmWorkspaceFile.catalogs ?? {}).join(', ')})`)}`,
90+
);
91+
continue;
92+
}
93+
const sub = catalog[packageName];
94+
if (!sub) {
95+
log(
96+
`⚠️ ${chalk.yellow(`when reading from pnpm-workspace.yaml, '${version}' substitution for the package '${packageName}' failed as there were no package in the catalog named '${catalogName}'. (packages in the catalog are: ${Object.keys(catalog).join(', ')})`)}`,
97+
);
98+
continue;
99+
}
100+
dependencies[packageName] = sub;
101+
}
102+
}
103+
};

pnpm-workspace.yaml

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# This is just for testing purposes, pnpm is not used in this project.
2+
# We do however read package.json to figure out which version of different
3+
# packages the user has, and some users use pnpm with the catalog feature.
4+
# We support this by reading the pnpm-workspace.yaml file they have instead
5+
# of package.json.
6+
catalog:
7+
typescript: ^5.2.0
8+
'@tanstack/react-query': '^5.67.2'
9+
10+
catalogs:
11+
# Can be referenced through "catalog:ts4"
12+
ts4:
13+
typescript: ^4.4.0
14+
15+
# Can be referenced through "catalog:ts5"
16+
ts5:
17+
typescript: ^5.2.0

0 commit comments

Comments
 (0)