Files
renovate/lib/modules/manager/deno/compat.ts
Udit Karode 8a926e8cc7 fix(deno): only collect workspace members when workspaces are declared (#45394)
* fix(deno): only collect workspace members when workspaces are declared

A package.json with no workspaces field has no members to find, but the member
scan ran anyway, and find-packages with no patterns returns every package.json
in the tree. Standalone subprojects were picked up as members of the root lock,
which is what breaks lockFileMaintenance.

The scan now runs only when workspaces are declared.

* test(deno): use await expect().resolves

Required by the `vitest(prefer-expect-resolves)` oxlint rule enabled on
main since this branch was created.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Michael Kriese <michael.kriese@mend.io>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 12:43:29 +00:00

119 lines
3.7 KiB
TypeScript

import { isNonEmptyArray } from '@sindresorhus/is';
import { findPackages } from 'find-packages';
import upath from 'upath';
import { GlobalConfig } from '../../../config/global.ts';
import { logger } from '../../../logger/index.ts';
import { parseJson } from '../../../util/common.ts';
import { getSiblingFileName, readLocalFile } from '../../../util/fs/index.ts';
import { ensureLocalPath } from '../../../util/fs/util.ts';
import { extractPackageJson } from '../npm/extract/common/package-file.ts';
import type { NpmPackage } from '../npm/extract/types.ts';
import type { PackageFile } from '../types.ts';
import type { DenoManagerData } from './types.ts';
export async function extractDenoCompatiblePackageJson(
packageFile: string,
): Promise<PackageFile<DenoManagerData> | null> {
const packageFileContent = await readLocalFile(packageFile, 'utf8');
if (!packageFileContent) {
logger.debug({ packageFile }, 'Deno: No package.json found');
return null;
}
let packageJson: NpmPackage;
try {
packageJson = parseJson(packageFileContent, packageFile) as NpmPackage;
} catch (err) {
logger.error({ err, packageFile }, 'Error parsing package.json');
return null;
}
const extracted = extractPackageJson(packageJson, packageFile);
if (!extracted) {
return null;
}
const result = extracted as PackageFile<DenoManagerData>;
result.managerData = {
packageName: extracted.managerData?.packageJsonName,
workspaces: extracted.managerData?.workspaces,
};
result.packageFile = packageFile;
return result;
}
// referring to lib/modules/manager/npm/extract/pnpm.ts detectPnpmWorkspaces()
export async function detectNodeCompatWorkspaces({
managerData,
packageFile,
}: Pick<PackageFile<DenoManagerData>, 'managerData' | 'packageFile'>): Promise<{
workspaces?: string[];
packagePaths: string[];
}> {
logger.debug(`Detecting deno's node compat Workspaces`);
let filters: string[] | undefined;
// npm workspace
if (isNonEmptyArray(managerData?.workspaces)) {
filters = managerData?.workspaces;
}
// findPackages() matches every package.json below the directory when it is given no patterns
if (!filters) {
return { packagePaths: [] };
}
const localDir = GlobalConfig.get('localDir');
const packages = await findPackages(
upath.dirname(ensureLocalPath(packageFile)),
{
patterns: filters,
// Match the ignores used in @pnpm/find-workspace-packages
ignore: ['**/node_modules/**', '**/bower_components/**'],
},
);
const packagePaths = packages.map((pkg) => {
const pkgPath = upath.join(pkg.dir, 'package.json');
return upath.relative(localDir, pkgPath);
});
return {
workspaces: filters,
packagePaths,
};
}
export async function collectPackageJson(
lockFile: string,
): Promise<PackageFile<DenoManagerData>[] | null> {
const lockFiles = [lockFile];
const packageFiles: PackageFile<DenoManagerData>[] = [];
const rootPackageJson = getSiblingFileName(lockFile, 'package.json');
const rootPackageFile =
await extractDenoCompatiblePackageJson(rootPackageJson);
if (rootPackageFile) {
rootPackageFile.lockFiles = lockFiles;
// detect node compat workspaces
const result = await detectNodeCompatWorkspaces(rootPackageFile);
const { workspaces, packagePaths } = result;
rootPackageFile.managerData = {
packageName: rootPackageFile.managerData?.packageName,
// override workspace
workspaces,
};
packageFiles.push(rootPackageFile);
for (const packagePath of packagePaths) {
const packageFile = await extractDenoCompatiblePackageJson(packagePath);
if (packageFile) {
packageFile.lockFiles = lockFiles;
packageFiles.push(packageFile);
}
}
}
return packageFiles;
}