Skip to content
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

feat(core): add support for pnpm catalogs #1989

Merged
merged 2 commits into from
Apr 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 78 additions & 3 deletions packages/orval/src/utils/package-json.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { PackageJson } from '@orval/core';
import { log, PackageJson } from '@orval/core';
import chalk from 'chalk';
import findUp from 'find-up';
import fs from 'fs-extra';
import yaml from 'js-yaml';
import { normalizePath } from './options';

export const loadPackageJson = async (
Expand All @@ -13,7 +15,7 @@ export const loadPackageJson = async (
});
if (pkgPath) {
const pkg = await import(pkgPath);
return pkg;
return await maybeReplaceCatalog(pkg, workspace);
}
return;
}
Expand All @@ -22,7 +24,80 @@ export const loadPackageJson = async (
if (fs.existsSync(normalizedPath)) {
const pkg = await import(normalizedPath);

return pkg;
return await maybeReplaceCatalog(pkg, workspace);
}
return;
};

const maybeReplaceCatalog = async (
pkg: PackageJson,
workspace: string,
): Promise<PackageJson> => {
if (
![
...Object.entries(pkg.dependencies ?? {}),
...Object.entries(pkg.devDependencies ?? {}),
...Object.entries(pkg.peerDependencies ?? {}),
].some(([key]) => key.startsWith('catalog:'))
) {
return pkg;
}

const filePath = await findUp('pnpm-workspace.yaml', { cwd: workspace });
if (!filePath) {
log(
`⚠️ ${chalk.yellow('package.json contains pnpm catalog: in dependencies, but no pnpm-workspace.yaml was found.')}`,
);
return pkg;
}
const file = await fs.readFile(filePath, 'utf8');

const pnpmWorkspaceFile = yaml.load(file) as Record<string, any>;
performSubstitution(pkg.dependencies, pnpmWorkspaceFile);
performSubstitution(pkg.devDependencies, pnpmWorkspaceFile);
performSubstitution(pkg.peerDependencies, pnpmWorkspaceFile);

return pkg;
};

const performSubstitution = (
dependencies: Record<string, string> | undefined,
pnpmWorkspaceFile: Record<string, any>,
) => {
if (!dependencies) return;
for (const [packageName, version] of Object.entries(dependencies)) {
if (version === 'catalog:' || version === 'catalog:default') {
if (!pnpmWorkspaceFile.catalog) {
log(
`⚠️ ${chalk.yellow(`when reading from pnpm-workspace.yaml, catalog: substitution for the package '${packageName}' failed as there were no default catalog.`)}`,
);
continue;
}
const sub = pnpmWorkspaceFile.catalog[packageName];
if (!sub) {
log(
`⚠️ ${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.`)}`,
);
continue;
}
dependencies[packageName] = sub;
} else if (version.startsWith('catalog:')) {
const catalogName = version.substring('catalog:'.length);
const catalog = pnpmWorkspaceFile.catalogs?.[catalogName];
if (!catalog) {
log(
`⚠️ ${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(', ')})`)}`,
);
continue;
}
const sub = catalog[packageName];
if (!sub) {
log(
`⚠️ ${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(', ')})`)}`,
);
continue;
}
dependencies[packageName] = sub;
}
}
};
17 changes: 17 additions & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# This is just for testing purposes, pnpm is not used in this project.
# We do however read package.json to figure out which version of different
# packages the user has, and some users use pnpm with the catalog feature.
# We support this by reading the pnpm-workspace.yaml file they have instead
# of package.json.
catalog:
typescript: ^5.2.0
'@tanstack/react-query': '^5.67.2'

catalogs:
# Can be referenced through "catalog:ts4"
ts4:
typescript: ^4.4.0

# Can be referenced through "catalog:ts5"
ts5:
typescript: ^5.2.0