Skip to content
Open
Show file tree
Hide file tree
Changes from 50 commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
67da7b0
refactor: rename pnpm specific type to reuse it for yarn as well
MKruschke Jul 24, 2025
ab690ff
refactor: use extractCatalogDeps for both cases
MKruschke Jul 24, 2025
fcc0159
feat: extract dependencies for yarn catalogs
MKruschke Jul 24, 2025
d436370
feat: updating dependency
MKruschke Jul 25, 2025
8ca30d5
test: added more tests
MKruschke Aug 3, 2025
7a53772
fix: fix typo
MKruschke Aug 3, 2025
d1874cd
Merge branch 'main' into feat/yarn-plugin-catalogs-support
MKruschke Aug 3, 2025
350379e
Merge branch 'main' into feat/yarn-plugin-catalogs-support
MKruschke Aug 3, 2025
fd9bc29
Merge branch 'main' into feat/yarn-plugin-catalogs-support
MKruschke Aug 4, 2025
4cadc5c
Merge branch 'main' into feat/yarn-plugin-catalogs-support
MKruschke Aug 4, 2025
04cb16b
fix: remove debugger stmnt and undo line deletion
MKruschke Aug 5, 2025
3e254ee
Merge remote-tracking branch 'origin/main' into feat/yarn-plugin-cata…
MKruschke Aug 6, 2025
6cc85df
fix: remove core file
MKruschke Aug 6, 2025
5ab19aa
fix: fix lint issues
MKruschke Aug 6, 2025
72b6c13
fix: fix lint issues
MKruschke Aug 6, 2025
0c261e7
fix: fix lint issues
MKruschke Aug 6, 2025
8622e23
test: add more tests
MKruschke Aug 7, 2025
c654c2f
Merge branch 'main' into feat/yarn-plugin-catalogs-support
MKruschke Aug 7, 2025
90437ee
Merge branch 'main' into feat/yarn-plugin-catalogs-support
MKruschke Aug 7, 2025
bfc0732
test: add more tests
MKruschke Aug 7, 2025
6c77671
test: add test comments
MKruschke Aug 7, 2025
72849f0
Merge branch 'main' into feat/yarn-plugin-catalogs-support
MKruschke Aug 7, 2025
3ac804d
test: add more tests
MKruschke Aug 8, 2025
a918957
test: add more tests
MKruschke Aug 8, 2025
414f8c3
Merge branch 'main' into feat/yarn-plugin-catalogs-support
MKruschke Aug 8, 2025
7f20994
fix: remove unused variable
MKruschke Aug 8, 2025
7e38f93
test: add more tests
MKruschke Aug 8, 2025
39dea8d
fix: try to fix istanbul comments
MKruschke Aug 8, 2025
4211ea6
fix: try v8 instead of istanbul
MKruschke Aug 8, 2025
043fd00
Merge branch 'main' into feat/yarn-plugin-catalogs-support
MKruschke Aug 8, 2025
9a75e30
Merge branch 'main' into feat/yarn-plugin-catalogs-support
MKruschke Aug 8, 2025
1e69a45
Merge branch 'main' into feat/yarn-plugin-catalogs-support
viceice Aug 8, 2025
adca60c
Merge remote-tracking branch 'origin/main' into feat/yarn-plugin-cata…
MKruschke Aug 15, 2025
8ac44af
chore: remove debugger statement
MKruschke Aug 15, 2025
0046429
chore: fix v8 ignore statements
MKruschke Aug 15, 2025
eb70ee3
fix: remove core file
MKruschke Aug 15, 2025
02ca083
Merge branch 'main' into feat/yarn-plugin-catalogs-support
MKruschke Aug 25, 2025
ff63e13
fix: applied review suggestions
MKruschke Aug 25, 2025
90ccad6
fix: change to manageFilePattern
MKruschke Sep 14, 2025
693b441
Merge remote-tracking branch 'origin/main' into feat/yarn-plugin-cata…
MKruschke Sep 14, 2025
5f1425f
fix: remove unused import
MKruschke Sep 14, 2025
b95c58f
fix: add return type
MKruschke Sep 15, 2025
216b519
fix: fix tests
MKruschke Sep 15, 2025
91c287d
Merge branch 'main' into feat/yarn-plugin-catalogs-support
MKruschke Sep 15, 2025
3f1aadd
revert: unrelated tests changes
MKruschke Sep 15, 2025
0bfbc90
fix: remove unused code after change the strategy
MKruschke Sep 15, 2025
9ee3c7b
fix: apply review comments
MKruschke Sep 15, 2025
3619d5e
fix: remove imports
MKruschke Sep 15, 2025
bd8a66b
revert: unwanted changes
MKruschke Sep 15, 2025
2a61349
revert: unwanted changes
MKruschke Sep 15, 2025
91bcff6
Merge remote-tracking branch 'origin/main' into feat/yarn-plugin-cata…
MKruschke Sep 19, 2025
6c50ae5
refactore: use loadPackageJson
MKruschke Sep 19, 2025
e66caec
Update lib/modules/manager/npm/extract/common/package-file.spec.ts
MKruschke Sep 19, 2025
8aef009
fix: apply review comments
MKruschke Sep 20, 2025
c681481
fix: remove unused import
MKruschke Sep 20, 2025
7adbb9f
Apply suggestion from @RahulGautamSingh
MKruschke Sep 20, 2025
d73cf56
Apply suggestion from @RahulGautamSingh
MKruschke Sep 20, 2025
bf37bad
fix: apply review comments
MKruschke Sep 20, 2025
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
6 changes: 5 additions & 1 deletion lib/config/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,11 @@ describe('config/index', () => {
expect(config).toContainEntries([
[
'managerFilePatterns',
['/(^|/)package\\.json$/', '/(^|/)pnpm-workspace\\.yaml$/'],
[
'/(^|/)package\\.json$/',
'/(^|/)pnpm-workspace\\.yaml$/',
'/(^|/)\\.yarnrc\\.yml$/',
],
],
]);
expect(getManagerConfig(parentConfig, 'html')).toContainEntries([
Expand Down
49 changes: 49 additions & 0 deletions lib/modules/manager/npm/extract/common/package-file.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { logger } from '../../../../../logger';
import { hasPackageManager } from './package-file';

describe('modules/manager/npm/extract/common/package-file', () => {
beforeEach(() => {
vitest.clearAllMocks();
});

it('returns true for a valid packageManager with name@range (e.g. [email protected])', () => {
const content = JSON.stringify({ packageManager: '[email protected]' });
expect(hasPackageManager(content)).toBe(true);

expect(logger.trace).toHaveBeenCalledWith(
'npm.hasPackageManager from package.json',
);
expect(logger.debug).not.toHaveBeenCalled();
});

it('returns true for a valid range like npm@^9', () => {
const content = JSON.stringify({ packageManager: 'npm@^9' });
expect(hasPackageManager(content)).toBe(true);
});

it('returns true for yarn classic pin [email protected]', () => {
const content = JSON.stringify({ packageManager: '[email protected]' });
expect(hasPackageManager(content)).toBe(true);
});

it("returns false when packageManager does not contain '@' (e.g. 'npm')", () => {
const content = JSON.stringify({ packageManager: 'npm' });
expect(hasPackageManager(content)).toBe(false);
});

it('returns false when packageManager is missing', () => {
const content = JSON.stringify({ name: 'demo' });
expect(hasPackageManager(content)).toBe(false);
});

it("returns false and logs 'Invalid JSON' when content is not valid JSON", () => {
const bad = '{ not: valid json';
expect(hasPackageManager(bad)).toBe(false);
expect(logger.debug).toHaveBeenCalledWith('Invalid JSON');
});

it('returns false if packageManager is an empty string', () => {
const content = JSON.stringify({ packageManager: '' });
expect(hasPackageManager(content)).toBe(false);
});
});
16 changes: 16 additions & 0 deletions lib/modules/manager/npm/extract/common/package-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { CONFIG_VALIDATION } from '../../../../../constants/error-messages';
import { logger } from '../../../../../logger';
import { regEx } from '../../../../../util/regex';
import type { PackageDependency, PackageFileContent } from '../../../types';
import { PackageJson } from '../../schema';
import type { NpmManagerData } from '../../types';
import type { NpmPackage, NpmPackageDependency } from '../types';
import {
Expand Down Expand Up @@ -147,3 +148,18 @@ export function extractPackageJson(
},
};
}

export function hasPackageManager(content: string): boolean {
logger.trace(`npm.hasPackageManager from package.json`);
try {
const packageJsonResult = PackageJson.parse(content);

return (
is.nonEmptyString(packageJsonResult?.packageManager?.name) &&
is.nonEmptyString(packageJsonResult?.packageManager?.version)
);
} catch {
logger.debug(`Invalid JSON`);
}
return false;
}
138 changes: 137 additions & 1 deletion lib/modules/manager/npm/extract/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ const defaultExtractConfig = {

const input01Content = Fixtures.get('inputs/01.json', '..');
const input02Content = Fixtures.get('inputs/02.json', '..');
const input01PackageManager = Fixtures.get(
'inputs/01-package-manager.json',
'..',
);
const input01GlobContent = Fixtures.get('inputs/01-glob.json', '..');
const workspacesContent = Fixtures.get('inputs/workspaces.json', '..');
const vendorisedContent = Fixtures.get('is-object.json', '..');
Expand Down Expand Up @@ -927,7 +931,7 @@ describe('modules/manager/npm/extract/index', () => {
const pJsonStr = JSON.stringify(pJson);
const res = await npmExtract.extractPackageFile(
pJsonStr,
'package.json',
'.yarnrc.yml',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good spot I changed one test to much after the refactoring. Will revert it

defaultExtractConfig,
);
expect(res).toMatchObject({
Expand Down Expand Up @@ -1268,6 +1272,138 @@ describe('modules/manager/npm/extract/index', () => {
},
]);
});

it('extracts yarnrc.yml and adds it as packageFile', async () => {
const yarnrc = codeBlock`
nodeLinker: node-modules

plugins:
- checksum: 4cb9601cfc0c71e5b0ffd0a85b78e37430b62257040714c2558298ce1fc058f4e918903f0d1747a4fef3f58e15722c35bd76d27492d9d08aa5b04e235bf43b22
path: .yarn/plugins/@yarnpkg/plugin-catalogs.cjs
spec: 'https://raw.githubusercontent.com/toss/yarn-plugin-catalogs/main/bundles/%40yarnpkg/plugin-catalogs.js'

catalogs:
list:
is-positive: 1.0.0
`;
fs.readLocalFile.mockResolvedValueOnce(yarnrc);

fs.readLocalFile.mockResolvedValueOnce(input02Content);

const res = await extractAllPackageFiles(defaultExtractConfig, [
'.yarnrc.yml',
]);

expect(res).toEqual([
{
deps: [
{
currentValue: '1.0.0',
datasource: 'npm',
depName: 'is-positive',
depType: 'yarn.catalog.default',
prettyDepType: 'yarn.catalog.default',
},
],
managerData: {
hasPackageManager: false,
},
packageFile: '.yarnrc.yml',
},
]);
});

it('extracts yarnrc.yml and adds it as packageFile and packageManager to true', async () => {
const yarnrc = codeBlock`
nodeLinker: node-modules

plugins:
- checksum: 4cb9601cfc0c71e5b0ffd0a85b78e37430b62257040714c2558298ce1fc058f4e918903f0d1747a4fef3f58e15722c35bd76d27492d9d08aa5b04e235bf43b22
path: .yarn/plugins/@yarnpkg/plugin-catalogs.cjs
spec: 'https://raw.githubusercontent.com/toss/yarn-plugin-catalogs/main/bundles/%40yarnpkg/plugin-catalogs.js'

catalogs:
list:
is-positive: 1.0.0
`;
fs.findLocalSiblingOrParent.mockImplementation(
async (_, name) =>
await new Promise((resolve) =>
resolve(name === 'package.json' ? 'package.json' : null),
),
);
fs.findLocalSiblingOrParent.mockResolvedValue('package.json');

fs.readLocalFile.mockResolvedValueOnce(yarnrc);
fs.readLocalFile.mockResolvedValueOnce(input01PackageManager);

const res = await extractAllPackageFiles(defaultExtractConfig, [
'.yarnrc.yml',
]);

expect(res[0]).toEqual({
deps: [
{
currentValue: '1.0.0',
datasource: 'npm',
depName: 'is-positive',
depType: 'yarn.catalog.default',
prettyDepType: 'yarn.catalog.default',
},
],
managerData: {
hasPackageManager: true,
},
packageFile: '.yarnrc.yml',
});
});

it('extracts yarnrc.yml and adds it as packageFile and packageManager to false if no deps', async () => {
const yarnrc = codeBlock`
nodeLinker: node-modules

plugins:
- checksum: 4cb9601cfc0c71e5b0ffd0a85b78e37430b62257040714c2558298ce1fc058f4e918903f0d1747a4fef3f58e15722c35bd76d27492d9d08aa5b04e235bf43b22
path: .yarn/plugins/@yarnpkg/plugin-catalogs.cjs
spec: 'https://raw.githubusercontent.com/toss/yarn-plugin-catalogs/main/bundles/%40yarnpkg/plugin-catalogs.js'

catalogs:
list:
is-positive: 1.0.0
`;

fs.findLocalSiblingOrParent.mockImplementation(
async (_, name) =>
await new Promise((resolve) =>
resolve(name === 'package.json' ? 'package.json' : null),
),
);
fs.readLocalFile.mockResolvedValueOnce(yarnrc);

fs.readLocalFile.mockResolvedValueOnce(
'{"name": "simulate deps to be null", brokenJsonHere: }',
);

const res = await extractAllPackageFiles(defaultExtractConfig, [
'.yarnrc.yml',
]);

expect(res[0]).toEqual({
deps: [
{
currentValue: '1.0.0',
datasource: 'npm',
depName: 'is-positive',
depType: 'yarn.catalog.default',
prettyDepType: 'yarn.catalog.default',
},
],
managerData: {
hasPackageManager: false,
},
packageFile: '.yarnrc.yml',
});
});
});

describe('.postExtract()', () => {
Expand Down
56 changes: 47 additions & 9 deletions lib/modules/manager/npm/extract/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ import type {
import type { YarnConfig } from '../schema';
import type { NpmLockFiles, NpmManagerData } from '../types';
import { getExtractedConstraints } from './common/dependency';
import { extractPackageJson } from './common/package-file';
import { extractPackageJson, hasPackageManager } from './common/package-file';
import { extractPnpmWorkspaceFile, tryParsePnpmWorkspaceYaml } from './pnpm';
import { postExtract } from './post';
import type { NpmPackage } from './types';
import { isZeroInstall } from './yarn';
import { extractYarnCatalogs, isZeroInstall } from './yarn';
import {
loadConfigFromLegacyYarnrc,
loadConfigFromYarnrcYml,
Expand Down Expand Up @@ -254,13 +254,35 @@ export async function extractAllPackageFiles(
});
}
} else {
logger.trace({ packageFile }, `Extracting as a package.json file`);
const deps = await extractPackageFile(content, packageFile, config);
if (deps) {
npmFiles.push({
...deps,
packageFile,
});
if (packageFile.endsWith('json')) {
logger.trace({ packageFile }, `Extracting as a package.json file`);

const deps = await extractPackageFile(content, packageFile, config);
if (deps) {
npmFiles.push({
...deps,
packageFile,
});
}
} else {
logger.trace({ packageFile }, `Extracting as a .yarnrc.yml file`);

const yarnConfig = loadConfigFromYarnrcYml(content);

if (yarnConfig?.catalogs) {
const hasPackageManager = await resolvePackageManager(packageFile);
const catalogsDeps = await extractYarnCatalogs(
yarnConfig.catalogs,
packageFile,
hasPackageManager,
);
if (catalogsDeps) {
npmFiles.push({
...catalogsDeps,
packageFile,
});
}
}
}
}
} else {
Expand All @@ -271,3 +293,19 @@ export async function extractAllPackageFiles(
await postExtract(npmFiles);
return npmFiles;
}

async function resolvePackageManager(packageFile: string): Promise<boolean> {
let hasPkgManager = false;
const packageJson = await findLocalSiblingOrParent(
packageFile,
'package.json',
);

if (packageJson) {
const content = await readLocalFile(packageJson, 'utf8');

hasPkgManager = content ? hasPackageManager(content) : false;
}

return hasPkgManager;
}
Loading