Compare commits

...

21 commits

Author SHA1 Message Date
Fotis Papadogeorgopoulos
3d3430be5e
Merge 8a9d54e160 into 1d2c1a35e3 2025-01-09 13:00:36 +02:00
Pierre Cavin
1d2c1a35e3
feat(mix): add depType support (#33310)
Some checks are pending
Build / coverage-threshold (push) Blocked by required conditions
Build / setup (push) Waiting to run
Build / setup-build (push) Waiting to run
Build / prefetch (push) Blocked by required conditions
Build / lint-eslint (push) Blocked by required conditions
Build / lint-prettier (push) Blocked by required conditions
Build / lint-docs (push) Blocked by required conditions
Build / lint-other (push) Blocked by required conditions
Build / (push) Blocked by required conditions
Build / codecov (push) Blocked by required conditions
Build / test-success (push) Blocked by required conditions
Build / build (push) Blocked by required conditions
Build / build-docs (push) Blocked by required conditions
Build / test-e2e (push) Blocked by required conditions
Build / release (push) Blocked by required conditions
Code scanning / CodeQL-Build (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
whitesource-scan / WS_SCAN (push) Waiting to run
Co-authored-by: Michael Kriese <michael.kriese@gmx.de>
2025-01-09 10:38:35 +00:00
Maxime Brunet
84017e05cc
refactor: refactor Google Auth util (#33486) 2025-01-09 10:35:47 +00:00
Johannes Feichtner
6cf23f2bf1
refactor(gradle): extract redundant functions and remove snapshot (#33430) 2025-01-09 07:22:45 +00:00
Alessandro Vinciguerra
6bb68782b8
feat: extract artifactory timestamps from columns (#33187) 2025-01-09 07:21:56 +00:00
Fotis Papadogeorgopoulos
8a9d54e160
chore: tidy up pnpm extraction test and TODO 2025-01-02 18:34:51 +02:00
Fotis Papadogeorgopoulos
c598c6ffa1
test(npm): mirror specifier tests to catalog update 2025-01-02 18:26:43 +02:00
Fotis Papadogeorgopoulos
8e37c8f100
chore: remove done TODO 2025-01-02 11:27:26 +02:00
Fotis Papadogeorgopoulos
e50bc2f9b5
feat(npm): implement replacement for pnpm catalog updates 2025-01-02 11:26:33 +02:00
Fotis Papadogeorgopoulos
ea5c14f003
test(npm): add tests for pnpm catalog updates 2025-01-02 10:59:52 +02:00
Fotis Papadogeorgopoulos
c9657a0040
chore(npm): tidy up TODOs for review 2025-01-01 23:54:11 +02:00
Fotis Papadogeorgopoulos
8ba3bd6eee
chore: remove TODO 2025-01-01 23:44:58 +02:00
Fotis Papadogeorgopoulos
9f23483b4f
fix(npm): set either implicit or named default catalog 2025-01-01 23:39:12 +02:00
Fotis Papadogeorgopoulos
f40a42dd68
refactor(npm,utils): split out parseSingleYamlDocument
This keeps YAML parsing centralised, while allowing use of the
Document represenation, in addition to the JS one.
2025-01-01 23:17:03 +02:00
Fotis Papadogeorgopoulos
4da6b6daf4
chore: small test and typechecking fixups 2025-01-01 22:53:20 +02:00
Fotis Papadogeorgopoulos
794f042068
refactor(npm): move git and npm alias resolution to common module 2025-01-01 22:35:32 +02:00
Fotis Papadogeorgopoulos
17efd68390
refactor: move updatePnpmCatalogDependency to its own module 2025-01-01 22:06:34 +02:00
Fotis Papadogeorgopoulos
591b401368
Implement updatePnpmCatalogDependency in updateDependency 2025-01-01 22:01:22 +02:00
Fotis Papadogeorgopoulos
50b0d82fb5
Remove pnpmCatalog from api.ts 2025-01-01 15:50:44 +02:00
Fotis Papadogeorgopoulos
891256f71a
Move pnpm catalog extraction to npm manager 2025-01-01 11:04:00 +02:00
Fotis Papadogeorgopoulos
0702e3d85b
WIP: prototype extraction in its own manager 2024-12-31 20:41:26 +02:00
27 changed files with 998 additions and 237 deletions

View file

@ -11,9 +11,9 @@
<pre> <pre>
<a href="..">..</a> <a href="..">..</a>
<a href="1.0.0">1.0.0</a> 21-Jul-2021 20:08 - <a href="1.0.0">1.0.0</a> 21-Jul-2021 20:08 -
<a href="1.0.1">1.0.1</a> 23-Aug-2021 20:03 - <a href="1.0.1">1.0.1</a> 23-Aug-2021 20:03 12 MB
<a href="1.0.2">1.0.2</a> 21-Jul-2021 20:09 - <a href="1.0.2">1.0.2</a> 21-Jul-2021 20:09 123.45 GB
<a href="1.0.3">1.0.3</a> 06-Feb-2021 09:54 - <a href="1.0.3">1.0.3</a> 06-Feb-2021 09:54 9.0 KB
</pre> </pre>
<hr/> <hr/>
<address style="font-size:small;">Artifactory Port 8080</address> <address style="font-size:small;">Artifactory Port 8080</address>

View file

@ -114,6 +114,9 @@ export class ArtifactoryDatasource extends Datasource {
} }
private static parseReleaseTimestamp(rawText: string): string { private static parseReleaseTimestamp(rawText: string): string {
return rawText.trim().replace(regEx(/ ?-$/), '') + 'Z'; return (
rawText.split(regEx(/\s{2,}/)).filter((e) => !isNaN(Date.parse(e)))[0] +
'Z'
);
} }
} }

View file

@ -1,6 +1,7 @@
import is from '@sindresorhus/is'; import is from '@sindresorhus/is';
import { GoogleAuth } from 'google-auth-library'; import { GoogleAuth } from 'google-auth-library';
import { logger } from '../../logger'; import { logger } from '../../logger';
import type { HostRule } from '../../types';
import type { HttpResponse } from '../../util/http/types'; import type { HttpResponse } from '../../util/http/types';
import { addSecretForSanitizing } from '../../util/sanitize'; import { addSecretForSanitizing } from '../../util/sanitize';
@ -12,7 +13,7 @@ export function isArtifactoryServer<T = unknown>(
return is.string(res?.headers[JFROG_ARTIFACTORY_RES_HEADER]); return is.string(res?.headers[JFROG_ARTIFACTORY_RES_HEADER]);
} }
export async function getGoogleAuthTokenRaw(): Promise<string | null> { export async function getGoogleAuthHostRule(): Promise<HostRule | null> {
try { try {
const googleAuth: GoogleAuth = new GoogleAuth({ const googleAuth: GoogleAuth = new GoogleAuth({
scopes: 'https://www.googleapis.com/auth/cloud-platform', scopes: 'https://www.googleapis.com/auth/cloud-platform',
@ -21,7 +22,10 @@ export async function getGoogleAuthTokenRaw(): Promise<string | null> {
if (accessToken) { if (accessToken) {
// sanitize token // sanitize token
addSecretForSanitizing(accessToken); addSecretForSanitizing(accessToken);
return accessToken; return {
username: 'oauth2accesstoken',
password: accessToken,
};
} else { } else {
logger.warn( logger.warn(
'Could not retrieve access token using google-auth-library getAccessToken', 'Could not retrieve access token using google-auth-library getAccessToken',
@ -38,9 +42,13 @@ export async function getGoogleAuthTokenRaw(): Promise<string | null> {
} }
export async function getGoogleAuthToken(): Promise<string | null> { export async function getGoogleAuthToken(): Promise<string | null> {
const accessToken = await getGoogleAuthTokenRaw(); const rule = await getGoogleAuthHostRule();
if (accessToken) { if (rule) {
return Buffer.from(`oauth2accesstoken:${accessToken}`).toString('base64'); const token = Buffer.from(`${rule.username}:${rule.password}`).toString(
'base64',
);
addSecretForSanitizing(token);
return token;
} }
return null; return null;
} }

View file

@ -1,103 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`modules/manager/gradle/parser calculations parses fixture from "gradle" manager 1`] = `
[
{
"currentValue": "1.5.2.RELEASE",
"depName": "org.springframework.boot:spring-boot-gradle-plugin",
"groupName": "springBootVersion",
"managerData": {
"fileReplacePosition": 53,
"packageFile": "build.gradle",
},
},
{
"currentValue": "1.2.3",
"depName": "com.github.jengelman.gradle.plugins:shadow",
"managerData": {
"fileReplacePosition": 417,
"packageFile": "build.gradle",
},
},
{
"currentValue": "0.1",
"depName": "com.fkorotkov:gradle-libraries-plugin",
"managerData": {
"fileReplacePosition": 481,
"packageFile": "build.gradle",
},
},
{
"currentValue": "0.2.3",
"depName": "gradle.plugin.se.patrikerdes:gradle-use-latest-versions-plugin",
"managerData": {
"fileReplacePosition": 568,
"packageFile": "build.gradle",
},
},
{
"currentValue": "3.1.1",
"depName": "org.apache.openjpa:openjpa",
"managerData": {
"fileReplacePosition": 621,
"packageFile": "build.gradle",
},
},
{
"currentValue": "0.13.0",
"depName": "com.gradle.publish:plugin-publish-plugin",
"managerData": {
"fileReplacePosition": 688,
"packageFile": "build.gradle",
},
},
{
"currentValue": "6.0.9.RELEASE",
"depName": "org.grails:gorm-hibernate5-spring-boot",
"managerData": {
"fileReplacePosition": 1882,
"packageFile": "build.gradle",
},
},
{
"currentValue": "6.0.5",
"depName": "mysql:mysql-connector-java",
"managerData": {
"fileReplacePosition": 1938,
"packageFile": "build.gradle",
},
},
{
"currentValue": "1.0-groovy-2.4",
"depName": "org.spockframework:spock-spring",
"managerData": {
"fileReplacePosition": 1996,
"packageFile": "build.gradle",
},
},
{
"currentValue": "1.3",
"depName": "org.hamcrest:hamcrest-core",
"managerData": {
"fileReplacePosition": 2101,
"packageFile": "build.gradle",
},
},
{
"currentValue": "3.1",
"depName": "cglib:cglib-nodep",
"managerData": {
"fileReplacePosition": 2189,
"packageFile": "build.gradle",
},
},
{
"currentValue": "3.1.1",
"depName": "org.apache.openjpa:openjpa",
"managerData": {
"fileReplacePosition": 2295,
"packageFile": "build.gradle",
},
},
]
`;

View file

@ -803,7 +803,106 @@ describe('modules/manager/gradle/parser', () => {
content.slice(managerData!.fileReplacePosition).indexOf(currentValue!), content.slice(managerData!.fileReplacePosition).indexOf(currentValue!),
); );
expect(replacementIndices.every((idx) => idx === 0)).toBeTrue(); expect(replacementIndices.every((idx) => idx === 0)).toBeTrue();
expect(deps).toMatchSnapshot(); expect(deps).toMatchObject([
{
currentValue: '1.5.2.RELEASE',
depName: 'org.springframework.boot:spring-boot-gradle-plugin',
groupName: 'springBootVersion',
managerData: {
fileReplacePosition: 53,
packageFile: 'build.gradle',
},
},
{
currentValue: '1.2.3',
depName: 'com.github.jengelman.gradle.plugins:shadow',
managerData: {
fileReplacePosition: 417,
packageFile: 'build.gradle',
},
},
{
currentValue: '0.1',
depName: 'com.fkorotkov:gradle-libraries-plugin',
managerData: {
fileReplacePosition: 481,
packageFile: 'build.gradle',
},
},
{
currentValue: '0.2.3',
depName:
'gradle.plugin.se.patrikerdes:gradle-use-latest-versions-plugin',
managerData: {
fileReplacePosition: 568,
packageFile: 'build.gradle',
},
},
{
currentValue: '3.1.1',
depName: 'org.apache.openjpa:openjpa',
managerData: {
fileReplacePosition: 621,
packageFile: 'build.gradle',
},
},
{
currentValue: '0.13.0',
depName: 'com.gradle.publish:plugin-publish-plugin',
managerData: {
fileReplacePosition: 688,
packageFile: 'build.gradle',
},
},
{
currentValue: '6.0.9.RELEASE',
depName: 'org.grails:gorm-hibernate5-spring-boot',
managerData: {
fileReplacePosition: 1882,
packageFile: 'build.gradle',
},
},
{
currentValue: '6.0.5',
depName: 'mysql:mysql-connector-java',
managerData: {
fileReplacePosition: 1938,
packageFile: 'build.gradle',
},
},
{
currentValue: '1.0-groovy-2.4',
depName: 'org.spockframework:spock-spring',
managerData: {
fileReplacePosition: 1996,
packageFile: 'build.gradle',
},
},
{
currentValue: '1.3',
depName: 'org.hamcrest:hamcrest-core',
managerData: {
fileReplacePosition: 2101,
packageFile: 'build.gradle',
},
},
{
currentValue: '3.1',
depName: 'cglib:cglib-nodep',
managerData: {
fileReplacePosition: 2189,
packageFile: 'build.gradle',
},
},
{
currentValue: '3.1.1',
depName: 'org.apache.openjpa:openjpa',
managerData: {
fileReplacePosition: 2295,
packageFile: 'build.gradle',
},
},
]);
}); });
}); });

View file

@ -321,14 +321,24 @@ export const qDotOrBraceExpr = (
matcher: q.QueryBuilder<Ctx, parser.Node>, matcher: q.QueryBuilder<Ctx, parser.Node>,
): q.QueryBuilder<Ctx, parser.Node> => ): q.QueryBuilder<Ctx, parser.Node> =>
q.sym<Ctx>(symValue).alt( q.sym<Ctx>(symValue).alt(
q.alt<Ctx>( q.op<Ctx>('.').join(matcher),
q.op<Ctx>('.').join(matcher), q.tree({
q.tree({ type: 'wrapped-tree',
type: 'wrapped-tree', maxDepth: 1,
maxDepth: 1, startsWith: '{',
startsWith: '{', endsWith: '}',
endsWith: '}', search: matcher,
search: matcher, }),
}),
),
); );
export const qGroupId = qValueMatcher.handler((ctx) =>
storeInTokenMap(ctx, 'groupId'),
);
export const qArtifactId = qValueMatcher.handler((ctx) =>
storeInTokenMap(ctx, 'artifactId'),
);
export const qVersion = qValueMatcher.handler((ctx) =>
storeInTokenMap(ctx, 'version'),
);

View file

@ -4,9 +4,12 @@ import type { Ctx } from '../types';
import { import {
GRADLE_PLUGINS, GRADLE_PLUGINS,
cleanupTempVars, cleanupTempVars,
qArtifactId,
qDotOrBraceExpr, qDotOrBraceExpr,
qGroupId,
qTemplateString, qTemplateString,
qValueMatcher, qValueMatcher,
qVersion,
storeInTokenMap, storeInTokenMap,
storeVarToken, storeVarToken,
} from './common'; } from './common';
@ -17,18 +20,6 @@ import {
handleLongFormDep, handleLongFormDep,
} from './handlers'; } from './handlers';
const qGroupId = qValueMatcher.handler((ctx) =>
storeInTokenMap(ctx, 'groupId'),
);
const qArtifactId = qValueMatcher.handler((ctx) =>
storeInTokenMap(ctx, 'artifactId'),
);
const qVersion = qValueMatcher.handler((ctx) =>
storeInTokenMap(ctx, 'version'),
);
// "foo:bar:1.2.3" // "foo:bar:1.2.3"
// "foo:bar:$baz" // "foo:bar:$baz"
// "foo" + "${bar}" + baz // "foo" + "${bar}" + baz

View file

@ -4,16 +4,12 @@ import type { Ctx } from '../types';
import { import {
cleanupTempVars, cleanupTempVars,
qStringValue, qStringValue,
qValueMatcher, qVersion,
storeInTokenMap, storeInTokenMap,
storeVarToken, storeVarToken,
} from './common'; } from './common';
import { handlePlugin } from './handlers'; import { handlePlugin } from './handlers';
const qVersion = qValueMatcher.handler((ctx) =>
storeInTokenMap(ctx, 'version'),
);
export const qPlugins = q export const qPlugins = q
.sym(regEx(/^(?:id|kotlin)$/), storeVarToken) .sym(regEx(/^(?:id|kotlin)$/), storeVarToken)
.handler((ctx) => storeInTokenMap(ctx, 'methodName')) .handler((ctx) => storeInTokenMap(ctx, 'methodName'))

View file

@ -32,6 +32,7 @@ const qUri = q
// mavenCentral { ... } // mavenCentral { ... }
const qPredefinedRegistries = q const qPredefinedRegistries = q
.sym(regEx(`^(?:${Object.keys(REGISTRY_URLS).join('|')})$`), storeVarToken) .sym(regEx(`^(?:${Object.keys(REGISTRY_URLS).join('|')})$`), storeVarToken)
.handler((ctx) => storeInTokenMap(ctx, 'registryUrl'))
.alt( .alt(
q.tree({ q.tree({
type: 'wrapped-tree', type: 'wrapped-tree',
@ -45,10 +46,31 @@ const qPredefinedRegistries = q
endsWith: '}', endsWith: '}',
}), }),
) )
.handler((ctx) => storeInTokenMap(ctx, 'registryUrl'))
.handler(handlePredefinedRegistryUrl) .handler(handlePredefinedRegistryUrl)
.handler(cleanupTempVars); .handler(cleanupTempVars);
// { url = "https://some.repo" }
const qMavenArtifactRegistry = q.tree({
type: 'wrapped-tree',
maxDepth: 1,
startsWith: '{',
endsWith: '}',
search: q.alt(
q
.sym<Ctx>('name')
.opt(q.op('='))
.join(qValueMatcher)
.handler((ctx) => storeInTokenMap(ctx, 'name')),
q.sym<Ctx>('url').opt(q.op('=')).join(qUri),
q.sym<Ctx>('setUrl').tree({
maxDepth: 1,
startsWith: '(',
endsWith: ')',
search: q.begin<Ctx>().join(qUri).end(),
}),
),
});
// maven(url = uri("https://foo.bar/baz")) // maven(url = uri("https://foo.bar/baz"))
// maven { name = some; url = "https://foo.bar/${name}" } // maven { name = some; url = "https://foo.bar/${name}" }
const qCustomRegistryUrl = q const qCustomRegistryUrl = q
@ -61,26 +83,7 @@ const qCustomRegistryUrl = q
endsWith: ')', endsWith: ')',
search: q.begin<Ctx>().opt(q.sym<Ctx>('url').op('=')).join(qUri).end(), search: q.begin<Ctx>().opt(q.sym<Ctx>('url').op('=')).join(qUri).end(),
}), }),
q.tree({ qMavenArtifactRegistry,
type: 'wrapped-tree',
maxDepth: 1,
startsWith: '{',
endsWith: '}',
search: q.alt(
q
.sym<Ctx>('name')
.opt(q.op('='))
.join(qValueMatcher)
.handler((ctx) => storeInTokenMap(ctx, 'name')),
q.sym<Ctx>('url').opt(q.op('=')).join(qUri),
q.sym<Ctx>('setUrl').tree({
maxDepth: 1,
startsWith: '(',
endsWith: ')',
search: q.begin<Ctx>().join(qUri).end(),
}),
),
}),
) )
.handler(handleCustomRegistryUrl) .handler(handleCustomRegistryUrl)
.handler(cleanupTempVars); .handler(cleanupTempVars);

View file

@ -2,6 +2,8 @@ import { query as q } from 'good-enough-parser';
import type { Ctx } from '../types'; import type { Ctx } from '../types';
import { import {
cleanupTempVars, cleanupTempVars,
qArtifactId,
qGroupId,
qStringValue, qStringValue,
qStringValueAsSymbol, qStringValueAsSymbol,
qValueMatcher, qValueMatcher,
@ -10,14 +12,6 @@ import {
} from './common'; } from './common';
import { handleLibraryDep, handlePlugin } from './handlers'; import { handleLibraryDep, handlePlugin } from './handlers';
const qGroupId = qValueMatcher.handler((ctx) =>
storeInTokenMap(ctx, 'groupId'),
);
const qArtifactId = qValueMatcher.handler((ctx) =>
storeInTokenMap(ctx, 'artifactId'),
);
const qVersionCatalogVersion = q const qVersionCatalogVersion = q
.op<Ctx>('.') .op<Ctx>('.')
.alt( .alt(

View file

@ -27,13 +27,19 @@ defmodule MyProject.MixProject do
{:secret, "~> 1.0", organization: "acme"}, {:secret, "~> 1.0", organization: "acme"},
{:also_secret, "~> 1.0", only: [:dev, :test], organization: "acme", runtime: false}, {:also_secret, "~> 1.0", only: [:dev, :test], organization: "acme", runtime: false},
{:metrics, ">0.2.0 and <=1.0.0"}, {:metrics, ">0.2.0 and <=1.0.0"},
{:jason, ">= 1.0.0"}, {:jason, ">= 1.0.0", only: :prod},
{:hackney, "~> 1.0", {:hackney, "~> 1.0",
optional: true}, optional: true},
{:hammer_backend_redis, "~> 6.1"}, {:hammer_backend_redis, "~> 6.1", only: [:dev, :prod, :test]},
{:castore, "== 1.0.10"}, {:castore, "== 1.0.10"},
{:gun, "~> 2.0.0", hex: "grpc_gun"}, {:gun, "~> 2.0.0", hex: "grpc_gun"},
{:another_gun, "~> 0.4.0", hex: :raygun}, {:another_gun, "~> 0.4.0", hex: :raygun},
{:credo, "~> 1.7", only:
[:test,
# prod,
:dev],
runtime: false},
{:floki, "== 0.37.0", only: :test},
] ]
end end
end end

View file

@ -1,13 +1,17 @@
%{ %{
"another_gun": {:hex, :raygun, "0.4.0", "7744e99dd695f61e78ad5e047cce0affb3edfc6f93a92278598ab553b9c5091f", [:mix], [{:httpoison, "~> 0.8 or ~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}, {:plug, "~> 1.1", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "eee4b891e6e65c6a4b15386dc7b7a72b717f3c123cc0012cfd19e8f2ab21116d"}, "another_gun": {:hex, :raygun, "0.4.0", "7744e99dd695f61e78ad5e047cce0affb3edfc6f93a92278598ab553b9c5091f", [:mix], [{:httpoison, "~> 0.8 or ~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}, {:plug, "~> 1.1", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "eee4b891e6e65c6a4b15386dc7b7a72b717f3c123cc0012cfd19e8f2ab21116d"},
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
"castore": {:hex, :castore, "1.0.10", "43bbeeac820f16c89f79721af1b3e092399b3a1ecc8df1a472738fd853574911", [:mix], [], "hexpm", "1b0b7ea14d889d9ea21202c43a4fa015eb913021cb535e8ed91946f4b77a8848"}, "castore": {:hex, :castore, "1.0.10", "43bbeeac820f16c89f79721af1b3e092399b3a1ecc8df1a472738fd853574911", [:mix], [], "hexpm", "1b0b7ea14d889d9ea21202c43a4fa015eb913021cb535e8ed91946f4b77a8848"},
"certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"},
"cowboy": {:git, "https://github.com/ninenines/cowboy.git", "0c2e2224e372f01e6cf51a8e12d4856edb4cb8ac", [tag: "0.6.0"]}, "cowboy": {:git, "https://github.com/ninenines/cowboy.git", "0c2e2224e372f01e6cf51a8e12d4856edb4cb8ac", [tag: "0.6.0"]},
"cowlib": {:hex, :cowlib, "2.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"}, "cowlib": {:hex, :cowlib, "2.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"},
"credo": {:hex, :credo, "1.7.10", "6e64fe59be8da5e30a1b96273b247b5cf1cc9e336b5fd66302a64b25749ad44d", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "71fbc9a6b8be21d993deca85bf151df023a3097b01e09a2809d460348561d8cd"},
"decimal": {:hex, :decimal, "1.9.0", "83e8daf59631d632b171faabafb4a9f4242c514b0a06ba3df493951c08f64d07", [:mix], [], "hexpm", "b1f2343568eed6928f3e751cf2dffde95bfaa19dd95d09e8a9ea92ccfd6f7d85"}, "decimal": {:hex, :decimal, "1.9.0", "83e8daf59631d632b171faabafb4a9f4242c514b0a06ba3df493951c08f64d07", [:mix], [], "hexpm", "b1f2343568eed6928f3e751cf2dffde95bfaa19dd95d09e8a9ea92ccfd6f7d85"},
"ecto": {:git, "https://github.com/elixir-ecto/ecto.git", "795036d997c7503b21fb64d6bf1a89b83c44f2b5", [ref: "795036d997c7503b21fb64d6bf1a89b83c44f2b5"]}, "ecto": {:git, "https://github.com/elixir-ecto/ecto.git", "795036d997c7503b21fb64d6bf1a89b83c44f2b5", [ref: "795036d997c7503b21fb64d6bf1a89b83c44f2b5"]},
"secret": {:hex, :secret, "1.5.0", "344dbbf6610d205760ec37e2848bff2aab5a2de182bb5cdaa72cc2fd19d74535", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "19c205c8de0e2e5817f2250100281c58e717cb11ff1bb410bf661ee78c24e79b"}, "secret": {:hex, :secret, "1.5.0", "344dbbf6610d205760ec37e2848bff2aab5a2de182bb5cdaa72cc2fd19d74535", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "19c205c8de0e2e5817f2250100281c58e717cb11ff1bb410bf661ee78c24e79b"},
"also_secret": {:hex, :also_secret, "1.3.4", "344dbbf6610d205760ec37e2848bff2aab5a2de182bb5cdaa72cc2fd19d74535", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "19c205c8de0e2e5817f2250100281c58e717cb11ff1bb410bf661ee78c24e79b"}, "also_secret": {:hex, :also_secret, "1.3.4", "344dbbf6610d205760ec37e2848bff2aab5a2de182bb5cdaa72cc2fd19d74535", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "19c205c8de0e2e5817f2250100281c58e717cb11ff1bb410bf661ee78c24e79b"},
"file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"},
"floki": {:hex, :floki, "0.37.0", "b83e0280bbc6372f2a403b2848013650b16640cd2470aea6701f0632223d719e", [:mix], [], "hexpm", "516a0c15a69f78c47dc8e0b9b3724b29608aa6619379f91b1ffa47109b5d0dd3"},
"gun": {:hex, :grpc_gun, "2.0.1", "221b792df3a93e8fead96f697cbaf920120deacced85c6cd3329d2e67f0871f8", [:rebar3], [{:cowlib, "~> 2.11", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "795a65eb9d0ba16697e6b0e1886009ce024799e43bb42753f0c59b029f592831"}, "gun": {:hex, :grpc_gun, "2.0.1", "221b792df3a93e8fead96f697cbaf920120deacced85c6cd3329d2e67f0871f8", [:rebar3], [{:cowlib, "~> 2.11", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "795a65eb9d0ba16697e6b0e1886009ce024799e43bb42753f0c59b029f592831"},
"hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"}, "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"},
"hammer": {:hex, :hammer, "6.2.1", "5ae9c33e3dceaeb42de0db46bf505bd9c35f259c8defb03390cd7556fea67ee2", [:mix], [{:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}], "hexpm", "b9476d0c13883d2dc0cc72e786bac6ac28911fba7cc2e04b70ce6a6d9c4b2bdc"}, "hammer": {:hex, :hammer, "6.2.1", "5ae9c33e3dceaeb42de0db46bf505bd9c35f259c8defb03390cd7556fea67ee2", [:mix], [{:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}], "hexpm", "b9476d0c13883d2dc0cc72e786bac6ac28911fba7cc2e04b70ce6a6d9c4b2bdc"},

View file

@ -20,12 +20,14 @@ describe('modules/manager/mix/extract', () => {
currentValue: '~> 0.8.1', currentValue: '~> 0.8.1',
datasource: 'hex', datasource: 'hex',
depName: 'postgrex', depName: 'postgrex',
depType: 'prod',
packageName: 'postgrex', packageName: 'postgrex',
}, },
{ {
currentValue: '<1.7.0 or ~>1.7.1', currentValue: '<1.7.0 or ~>1.7.1',
datasource: 'hex', datasource: 'hex',
depName: 'ranch', depName: 'ranch',
depType: 'prod',
packageName: 'ranch', packageName: 'ranch',
}, },
{ {
@ -33,6 +35,7 @@ describe('modules/manager/mix/extract', () => {
currentValue: '0.6.0', currentValue: '0.6.0',
datasource: 'github-tags', datasource: 'github-tags',
depName: 'cowboy', depName: 'cowboy',
depType: 'prod',
packageName: 'ninenines/cowboy', packageName: 'ninenines/cowboy',
}, },
{ {
@ -40,6 +43,7 @@ describe('modules/manager/mix/extract', () => {
currentValue: 'main', currentValue: 'main',
datasource: 'git-tags', datasource: 'git-tags',
depName: 'phoenix', depName: 'phoenix',
depType: 'prod',
packageName: 'https://github.com/phoenixframework/phoenix.git', packageName: 'https://github.com/phoenixframework/phoenix.git',
}, },
{ {
@ -47,42 +51,49 @@ describe('modules/manager/mix/extract', () => {
currentValue: undefined, currentValue: undefined,
datasource: 'github-tags', datasource: 'github-tags',
depName: 'ecto', depName: 'ecto',
depType: 'prod',
packageName: 'elixir-ecto/ecto', packageName: 'elixir-ecto/ecto',
}, },
{ {
currentValue: '~> 1.0', currentValue: '~> 1.0',
datasource: 'hex', datasource: 'hex',
depName: 'secret', depName: 'secret',
depType: 'prod',
packageName: 'secret:acme', packageName: 'secret:acme',
}, },
{ {
currentValue: '~> 1.0', currentValue: '~> 1.0',
datasource: 'hex', datasource: 'hex',
depName: 'also_secret', depName: 'also_secret',
depType: 'dev',
packageName: 'also_secret:acme', packageName: 'also_secret:acme',
}, },
{ {
currentValue: '>0.2.0 and <=1.0.0', currentValue: '>0.2.0 and <=1.0.0',
datasource: 'hex', datasource: 'hex',
depName: 'metrics', depName: 'metrics',
depType: 'prod',
packageName: 'metrics', packageName: 'metrics',
}, },
{ {
currentValue: '>= 1.0.0', currentValue: '>= 1.0.0',
datasource: 'hex', datasource: 'hex',
depName: 'jason', depName: 'jason',
depType: 'prod',
packageName: 'jason', packageName: 'jason',
}, },
{ {
currentValue: '~> 1.0', currentValue: '~> 1.0',
datasource: 'hex', datasource: 'hex',
depName: 'hackney', depName: 'hackney',
depType: 'prod',
packageName: 'hackney', packageName: 'hackney',
}, },
{ {
currentValue: '~> 6.1', currentValue: '~> 6.1',
datasource: 'hex', datasource: 'hex',
depName: 'hammer_backend_redis', depName: 'hammer_backend_redis',
depType: 'prod',
packageName: 'hammer_backend_redis', packageName: 'hammer_backend_redis',
}, },
{ {
@ -90,20 +101,38 @@ describe('modules/manager/mix/extract', () => {
currentVersion: '1.0.10', currentVersion: '1.0.10',
datasource: 'hex', datasource: 'hex',
depName: 'castore', depName: 'castore',
depType: 'prod',
packageName: 'castore', packageName: 'castore',
}, },
{ {
currentValue: '~> 2.0.0', currentValue: '~> 2.0.0',
datasource: 'hex', datasource: 'hex',
depName: 'gun', depName: 'gun',
depType: 'prod',
packageName: 'grpc_gun', packageName: 'grpc_gun',
}, },
{ {
currentValue: '~> 0.4.0', currentValue: '~> 0.4.0',
datasource: 'hex', datasource: 'hex',
depName: 'another_gun', depName: 'another_gun',
depType: 'prod',
packageName: 'raygun', packageName: 'raygun',
}, },
{
currentValue: '~> 1.7',
datasource: 'hex',
depName: 'credo',
depType: 'dev',
packageName: 'credo',
},
{
currentValue: '== 0.37.0',
currentVersion: '0.37.0',
datasource: 'hex',
depName: 'floki',
depType: 'dev',
packageName: 'floki',
},
]); ]);
}); });
@ -116,6 +145,7 @@ describe('modules/manager/mix/extract', () => {
currentValue: '~> 0.8.1', currentValue: '~> 0.8.1',
datasource: 'hex', datasource: 'hex',
depName: 'postgrex', depName: 'postgrex',
depType: 'prod',
packageName: 'postgrex', packageName: 'postgrex',
lockedVersion: '0.8.4', lockedVersion: '0.8.4',
}, },
@ -123,6 +153,7 @@ describe('modules/manager/mix/extract', () => {
currentValue: '<1.7.0 or ~>1.7.1', currentValue: '<1.7.0 or ~>1.7.1',
datasource: 'hex', datasource: 'hex',
depName: 'ranch', depName: 'ranch',
depType: 'prod',
packageName: 'ranch', packageName: 'ranch',
lockedVersion: '1.7.1', lockedVersion: '1.7.1',
}, },
@ -131,6 +162,7 @@ describe('modules/manager/mix/extract', () => {
currentValue: '0.6.0', currentValue: '0.6.0',
datasource: 'github-tags', datasource: 'github-tags',
depName: 'cowboy', depName: 'cowboy',
depType: 'prod',
packageName: 'ninenines/cowboy', packageName: 'ninenines/cowboy',
lockedVersion: '0.6.0', lockedVersion: '0.6.0',
}, },
@ -139,6 +171,7 @@ describe('modules/manager/mix/extract', () => {
currentValue: 'main', currentValue: 'main',
datasource: 'git-tags', datasource: 'git-tags',
depName: 'phoenix', depName: 'phoenix',
depType: 'prod',
packageName: 'https://github.com/phoenixframework/phoenix.git', packageName: 'https://github.com/phoenixframework/phoenix.git',
lockedVersion: undefined, lockedVersion: undefined,
}, },
@ -147,6 +180,7 @@ describe('modules/manager/mix/extract', () => {
currentValue: undefined, currentValue: undefined,
datasource: 'github-tags', datasource: 'github-tags',
depName: 'ecto', depName: 'ecto',
depType: 'prod',
packageName: 'elixir-ecto/ecto', packageName: 'elixir-ecto/ecto',
lockedVersion: undefined, lockedVersion: undefined,
}, },
@ -154,6 +188,7 @@ describe('modules/manager/mix/extract', () => {
currentValue: '~> 1.0', currentValue: '~> 1.0',
datasource: 'hex', datasource: 'hex',
depName: 'secret', depName: 'secret',
depType: 'prod',
packageName: 'secret:acme', packageName: 'secret:acme',
lockedVersion: '1.5.0', lockedVersion: '1.5.0',
}, },
@ -161,6 +196,7 @@ describe('modules/manager/mix/extract', () => {
currentValue: '~> 1.0', currentValue: '~> 1.0',
datasource: 'hex', datasource: 'hex',
depName: 'also_secret', depName: 'also_secret',
depType: 'dev',
packageName: 'also_secret:acme', packageName: 'also_secret:acme',
lockedVersion: '1.3.4', lockedVersion: '1.3.4',
}, },
@ -168,6 +204,7 @@ describe('modules/manager/mix/extract', () => {
currentValue: '>0.2.0 and <=1.0.0', currentValue: '>0.2.0 and <=1.0.0',
datasource: 'hex', datasource: 'hex',
depName: 'metrics', depName: 'metrics',
depType: 'prod',
packageName: 'metrics', packageName: 'metrics',
lockedVersion: '1.0.0', lockedVersion: '1.0.0',
}, },
@ -175,6 +212,7 @@ describe('modules/manager/mix/extract', () => {
currentValue: '>= 1.0.0', currentValue: '>= 1.0.0',
datasource: 'hex', datasource: 'hex',
depName: 'jason', depName: 'jason',
depType: 'prod',
packageName: 'jason', packageName: 'jason',
lockedVersion: '1.4.4', lockedVersion: '1.4.4',
}, },
@ -182,6 +220,7 @@ describe('modules/manager/mix/extract', () => {
currentValue: '~> 1.0', currentValue: '~> 1.0',
datasource: 'hex', datasource: 'hex',
depName: 'hackney', depName: 'hackney',
depType: 'prod',
packageName: 'hackney', packageName: 'hackney',
lockedVersion: '1.20.1', lockedVersion: '1.20.1',
}, },
@ -189,6 +228,7 @@ describe('modules/manager/mix/extract', () => {
currentValue: '~> 6.1', currentValue: '~> 6.1',
datasource: 'hex', datasource: 'hex',
depName: 'hammer_backend_redis', depName: 'hammer_backend_redis',
depType: 'prod',
packageName: 'hammer_backend_redis', packageName: 'hammer_backend_redis',
lockedVersion: '6.2.0', lockedVersion: '6.2.0',
}, },
@ -197,6 +237,7 @@ describe('modules/manager/mix/extract', () => {
currentVersion: '1.0.10', currentVersion: '1.0.10',
datasource: 'hex', datasource: 'hex',
depName: 'castore', depName: 'castore',
depType: 'prod',
packageName: 'castore', packageName: 'castore',
lockedVersion: '1.0.10', lockedVersion: '1.0.10',
}, },
@ -204,6 +245,7 @@ describe('modules/manager/mix/extract', () => {
currentValue: '~> 2.0.0', currentValue: '~> 2.0.0',
datasource: 'hex', datasource: 'hex',
depName: 'gun', depName: 'gun',
depType: 'prod',
packageName: 'grpc_gun', packageName: 'grpc_gun',
lockedVersion: '2.0.1', lockedVersion: '2.0.1',
}, },
@ -211,9 +253,27 @@ describe('modules/manager/mix/extract', () => {
currentValue: '~> 0.4.0', currentValue: '~> 0.4.0',
datasource: 'hex', datasource: 'hex',
depName: 'another_gun', depName: 'another_gun',
depType: 'prod',
packageName: 'raygun', packageName: 'raygun',
lockedVersion: '0.4.0', lockedVersion: '0.4.0',
}, },
{
currentValue: '~> 1.7',
datasource: 'hex',
depName: 'credo',
depType: 'dev',
packageName: 'credo',
lockedVersion: '1.7.10',
},
{
currentValue: '== 0.37.0',
currentVersion: '0.37.0',
datasource: 'hex',
depName: 'floki',
depType: 'dev',
lockedVersion: '0.37.0',
packageName: 'floki',
},
]); ]);
}); });
}); });

View file

@ -20,6 +20,8 @@ const lockedVersionRegExp = regEx(
/^\s+"(?<app>\w+)".*?"(?<lockedVersion>\d+\.\d+\.\d+)"/, /^\s+"(?<app>\w+)".*?"(?<lockedVersion>\d+\.\d+\.\d+)"/,
); );
const hexRegexp = regEx(/hex:\s*(?:"(?<strValue>[^"]+)"|:(?<atomValue>\w+))/); const hexRegexp = regEx(/hex:\s*(?:"(?<strValue>[^"]+)"|:(?<atomValue>\w+))/);
const onlyValueRegexp = regEx(/only:\s*(?<only>\[[^\]]*\]|:\w+)/);
const onlyEnvironmentsRegexp = regEx(/:(\w+)/gm);
export async function extractPackageFile( export async function extractPackageFile(
content: string, content: string,
@ -48,22 +50,28 @@ export async function extractPackageFile(
const hexGroups = hexRegexp.exec(opts)?.groups; const hexGroups = hexRegexp.exec(opts)?.groups;
const hex = hexGroups?.strValue ?? hexGroups?.atomValue; const hex = hexGroups?.strValue ?? hexGroups?.atomValue;
let dep: PackageDependency; const onlyValue = onlyValueRegexp.exec(opts)?.groups?.only;
const onlyEnvironments = [];
let match;
if (onlyValue) {
while ((match = onlyEnvironmentsRegexp.exec(onlyValue)) !== null) {
onlyEnvironments.push(match[1]);
}
}
const dep: PackageDependency = {
depName: app,
depType: 'prod',
};
if (git ?? github) { if (git ?? github) {
dep = { dep.currentDigest = ref;
depName: app, dep.currentValue = branchOrTag;
currentDigest: ref, dep.datasource = git ? GitTagsDatasource.id : GithubTagsDatasource.id;
currentValue: branchOrTag, dep.packageName = git ?? github;
datasource: git ? GitTagsDatasource.id : GithubTagsDatasource.id,
packageName: git ?? github,
};
} else { } else {
dep = { dep.currentValue = requirement;
depName: app, dep.datasource = HexDatasource.id;
currentValue: requirement,
datasource: HexDatasource.id,
};
if (organization) { if (organization) {
dep.packageName = `${app}:${organization}`; dep.packageName = `${app}:${organization}`;
} else if (hex) { } else if (hex) {
@ -71,11 +79,16 @@ export async function extractPackageFile(
} else { } else {
dep.packageName = app; dep.packageName = app;
} }
if (requirement?.startsWith('==')) { if (requirement?.startsWith('==')) {
dep.currentVersion = requirement.replace(regEx(/^==\s*/), ''); dep.currentVersion = requirement.replace(regEx(/^==\s*/), '');
} }
} }
if (onlyValue !== undefined && !onlyEnvironments.includes('prod')) {
dep.depType = 'dev';
}
deps.set(app, dep); deps.set(app, dep);
logger.trace({ dep }, `setting ${app}`); logger.trace({ dep }, `setting ${app}`);
depMatchGroups = depMatchRegExp.exec(depBuffer)?.groups; depMatchGroups = depMatchRegExp.exec(depBuffer)?.groups;

View file

@ -1,3 +1,8 @@
The `mix` manager extracts dependencies for the `hex` datasource and uses Renovate's implementation of Hex SemVer to evaluate updates. The `mix` manager uses Renovate's implementation of [Elixir SemVer](https://hexdocs.pm/elixir/Version.html#module-requirements) to evaluate update ranges.
The `mix` package manager itself is also used to keep the lock file up-to-date. The `mix` package manager itself is used to keep the lock file up-to-date.
The following `depTypes` are currently supported by the `mix` manager :
- `prod`: all dependencies by default
- `dev`: dependencies with [`:only` option](https://hexdocs.pm/mix/Mix.Tasks.Deps.html#module-dependency-definition-options) not containing `:prod`

View file

@ -17,6 +17,7 @@ import type {
import type { NpmLockFiles, NpmManagerData } from '../types'; import type { NpmLockFiles, NpmManagerData } from '../types';
import { getExtractedConstraints } from './common/dependency'; import { getExtractedConstraints } from './common/dependency';
import { extractPackageJson } from './common/package-file'; import { extractPackageJson } from './common/package-file';
import { extractPnpmWorkspaceFile } from './pnpm';
import { postExtract } from './post'; import { postExtract } from './post';
import type { NpmPackage } from './types'; import type { NpmPackage } from './types';
import { isZeroInstall } from './yarn'; import { isZeroInstall } from './yarn';
@ -229,12 +230,24 @@ export async function extractAllPackageFiles(
const content = await readLocalFile(packageFile, 'utf8'); const content = await readLocalFile(packageFile, 'utf8');
// istanbul ignore else // istanbul ignore else
if (content) { if (content) {
const deps = await extractPackageFile(content, packageFile, config); // TODO(fpapado): for PR dicussion, consider this vs. a post hook, where
if (deps) { // we look for pnpm-workspace.yaml in the siblings
npmFiles.push({ if (packageFile === 'pnpm-workspace.yaml') {
...deps, const deps = extractPnpmWorkspaceFile(content, packageFile);
packageFile, if (deps) {
}); npmFiles.push({
...deps,
packageFile,
});
}
} else {
const deps = await extractPackageFile(content, packageFile, config);
if (deps) {
npmFiles.push({
...deps,
packageFile,
});
}
} }
} else { } else {
logger.debug({ packageFile }, `No content found`); logger.debug({ packageFile }, `No content found`);

View file

@ -1,3 +1,4 @@
import { codeBlock } from 'common-tags';
import { Fixtures } from '../../../../../test/fixtures'; import { Fixtures } from '../../../../../test/fixtures';
import { getFixturePath, logger, partial } from '../../../../../test/util'; import { getFixturePath, logger, partial } from '../../../../../test/util';
import { GlobalConfig } from '../../../../config/global'; import { GlobalConfig } from '../../../../config/global';
@ -8,6 +9,7 @@ import type { NpmManagerData } from '../types';
import { import {
detectPnpmWorkspaces, detectPnpmWorkspaces,
extractPnpmFilters, extractPnpmFilters,
extractPnpmWorkspaceFile,
findPnpmWorkspace, findPnpmWorkspace,
getPnpmLock, getPnpmLock,
} from './pnpm'; } from './pnpm';
@ -270,4 +272,62 @@ describe('modules/manager/npm/extract/pnpm', () => {
expect(res.lockedVersionsWithPath).toBeUndefined(); expect(res.lockedVersionsWithPath).toBeUndefined();
}); });
}); });
describe('.extractPnpmWorkspaceFile()', () => {
it('ignores invalid pnpm-workspace.yaml file', () => {
expect(extractPnpmWorkspaceFile('', 'pnpm-workspace.yaml')).toBeNull();
});
it('handles empty catalog entries', () => {
expect(
extractPnpmWorkspaceFile(
codeBlock`
catalog:
catalogs:
`,
'pnpm-workspace.yaml',
),
).toBeNull();
});
it('parses valid pnpm-workspace.yaml file', () => {
expect(
extractPnpmWorkspaceFile(
codeBlock`
catalog:
react: 18.3.0
catalogs:
react17:
react: 17.0.2
`,
'pnpm-workspace.yaml',
),
).toMatchObject({
deps: [
{
currentValue: '18.3.0',
datasource: 'npm',
depName: 'react',
depType: 'pnpm.catalog',
prettyDepType: 'pnpm.catalog',
managerData: {
catalogName: 'default',
},
},
{
currentValue: '17.0.2',
datasource: 'npm',
depName: 'react',
depType: 'pnpm.catalog',
prettyDepType: 'pnpm.catalog',
managerData: {
catalogName: 'react17',
},
},
],
packageFile: 'pnpm-workspace.yaml',
});
});
});
}); });

View file

@ -1,6 +1,7 @@
import is from '@sindresorhus/is'; import is from '@sindresorhus/is';
import { findPackages } from 'find-packages'; import { findPackages } from 'find-packages';
import upath from 'upath'; import upath from 'upath';
import { z } from 'zod';
import { GlobalConfig } from '../../../../config/global'; import { GlobalConfig } from '../../../../config/global';
import { logger } from '../../../../logger'; import { logger } from '../../../../logger';
import { import {
@ -10,10 +11,19 @@ import {
readLocalFile, readLocalFile,
} from '../../../../util/fs'; } from '../../../../util/fs';
import { parseSingleYaml } from '../../../../util/yaml'; import { parseSingleYaml } from '../../../../util/yaml';
import type { PackageFile } from '../../types'; import type {
PackageDependency,
PackageFile,
PackageFileContent,
} from '../../types';
import type { PnpmDependencySchema, PnpmLockFile } from '../post-update/types'; import type { PnpmDependencySchema, PnpmLockFile } from '../post-update/types';
import type { NpmManagerData } from '../types'; import type { NpmManagerData } from '../types';
import type { LockFile, PnpmWorkspaceFile } from './types'; import { extractDependency, parseDepName } from './common/dependency';
import type {
LockFile,
NpmPackageDependency,
PnpmWorkspaceFile,
} from './types';
function isPnpmLockfile(obj: any): obj is PnpmLockFile { function isPnpmLockfile(obj: any): obj is PnpmLockFile {
return is.plainObject(obj) && 'lockfileVersion' in obj; return is.plainObject(obj) && 'lockfileVersion' in obj;
@ -86,8 +96,8 @@ export async function detectPnpmWorkspaces(
const packagePathCache = new Map<string, string[] | null>(); const packagePathCache = new Map<string, string[] | null>();
for (const p of packageFiles) { for (const p of packageFiles) {
const { packageFile, managerData } = p; const { packageFile, managerData = {} } = p;
const { pnpmShrinkwrap } = managerData as NpmManagerData; const { pnpmShrinkwrap } = managerData as Partial<NpmManagerData>;
// check if pnpmShrinkwrap-file has already been provided // check if pnpmShrinkwrap-file has already been provided
if (pnpmShrinkwrap) { if (pnpmShrinkwrap) {
@ -222,3 +232,108 @@ function getLockedDependencyVersions(
return res; return res;
} }
/**
* A pnpm catalog is either the default catalog (catalog:, catlog:default), or a
* named one (under catalogs:)
*/
type PnpmCatalog = { name: string; dependencies: NpmPackageDependency };
export function extractPnpmWorkspaceFile(
content: string,
packageFile: string,
): PackageFile | null {
logger.trace(`pnpm.extractPnpmWorkspaceFile(${packageFile})`);
let pnpmCatalogs: Array<PnpmCatalog>;
try {
pnpmCatalogs = parsePnpmCatalogs(content);
} catch {
logger.debug({ packageFile }, `Invalid pnpm workspace YAML.`);
return null;
}
const extracted = extractPnpmCatalogDeps(pnpmCatalogs);
if (!extracted) {
logger.debug({ packageFile }, 'No dependencies found');
return null;
}
logger.debug(extracted, 'Extracted catalog dependencies.');
return {
...extracted,
packageFile,
};
}
function extractPnpmCatalogDeps(
catalogs: Array<PnpmCatalog>,
): PackageFileContent<NpmManagerData> | null {
const CATALOG_DEPENDENCY = 'pnpm.catalog';
const deps: PackageDependency[] = [];
for (const catalog of catalogs) {
for (const [key, val] of Object.entries(catalog.dependencies)) {
const depName = parseDepName(CATALOG_DEPENDENCY, key);
let dep: PackageDependency = {
depType: CATALOG_DEPENDENCY,
// TODO(fpapado): for PR discussion, consider how users might be able to
// match on specific catalogs for their config.
//
// For example, we could change depType to `pnpm.catalog.${string}`, so
// that users can match use `{matchDepTypes: ["pnpm.catalog.default"]}`,
// `{matchDepTypes: ["pnpm.catalog.react17"]}` and so on.
//
// Another option would be to mess with depName/packageName.
//
// Is there precedence for something similar?
depName,
managerData: {
// We assign the name of the catalog, in order to know which fields to
// update later on.
catalogName: catalog.name,
},
};
if (depName !== key) {
dep.managerData!.key = key;
}
// TODO: fix type #22198
dep = {
...dep,
...extractDependency(CATALOG_DEPENDENCY, depName, val!),
prettyDepType: CATALOG_DEPENDENCY,
};
dep.prettyDepType = CATALOG_DEPENDENCY;
deps.push(dep);
}
}
return {
deps,
};
}
export const pnpmCatalogsSchema = z.object({
catalog: z.optional(z.record(z.string())),
catalogs: z.optional(z.record(z.record(z.string()))),
});
function parsePnpmCatalogs(content: string): Array<PnpmCatalog> {
const { catalog: defaultCatalogDeps, catalogs: namedCatalogs } =
parseSingleYaml(content, { customSchema: pnpmCatalogsSchema });
return [
{
name: 'default',
dependencies: defaultCatalogDeps ?? {},
},
...Object.entries(namedCatalogs ?? {}).map(([name, dependencies]) => ({
name,
dependencies,
})),
];
}

View file

@ -36,6 +36,8 @@ export interface LockFile {
export interface PnpmWorkspaceFile { export interface PnpmWorkspaceFile {
packages: string[]; packages: string[];
catalog?: Partial<Record<string, string>>;
catalogs?: Partial<Record<string, Partial<Record<string, string>>>>;
} }
export type OverrideDependency = Record<string, RecursiveOverride>; export type OverrideDependency = Record<string, RecursiveOverride>;

View file

@ -20,7 +20,8 @@ export const url = 'https://docs.npmjs.com';
export const categories: Category[] = ['js']; export const categories: Category[] = ['js'];
export const defaultConfig = { export const defaultConfig = {
fileMatch: ['(^|/)package\\.json$'], // TODO(fpapado): for PR dicussion, consider this vs. a post hook
fileMatch: ['(^|/)package\\.json$', '(^|/)pnpm-workspace\\.yaml$'],
digest: { digest: {
prBodyDefinitions: { prBodyDefinitions: {
Change: Change:

View file

@ -0,0 +1,33 @@
import { logger } from '../../../../../logger';
import type { Upgrade } from '../../../types';
export function getNewGitValue(upgrade: Upgrade): string | undefined {
if (!upgrade.currentRawValue) {
return;
}
if (upgrade.currentDigest) {
logger.debug('Updating git digest');
return upgrade.currentRawValue.replace(
upgrade.currentDigest,
// TODO #22198
upgrade.newDigest!.substring(0, upgrade.currentDigest.length),
);
} else {
logger.debug('Updating git version tag');
return upgrade.currentRawValue.replace(
upgrade.currentValue,
upgrade.newValue,
);
}
}
export function getNewNpmAliasValue(
value: string | undefined,
upgrade: Upgrade,
): string | undefined {
if (!upgrade.npmPackageAlias) {
return;
}
return `npm:${upgrade.packageName}@${value}`;
}

View file

@ -11,6 +11,8 @@ import type {
RecursiveOverride, RecursiveOverride,
} from '../../extract/types'; } from '../../extract/types';
import type { NpmDepType, NpmManagerData } from '../../types'; import type { NpmDepType, NpmManagerData } from '../../types';
import { getNewGitValue, getNewNpmAliasValue } from './common';
import { updatePnpmCatalogDependency } from './pnpm';
function renameObjKey( function renameObjKey(
oldObj: DependenciesMeta, oldObj: DependenciesMeta,
@ -115,29 +117,16 @@ export function updateDependency({
fileContent, fileContent,
upgrade, upgrade,
}: UpdateDependencyConfig): string | null { }: UpdateDependencyConfig): string | null {
if (upgrade.depType === 'pnpm.catalog') {
return updatePnpmCatalogDependency({ fileContent, upgrade });
}
const { depType, managerData } = upgrade; const { depType, managerData } = upgrade;
const depName: string = managerData?.key || upgrade.depName; const depName: string = managerData?.key || upgrade.depName;
let { newValue } = upgrade; let { newValue } = upgrade;
if (upgrade.currentRawValue) {
if (upgrade.currentDigest) {
logger.debug('Updating package.json git digest');
newValue = upgrade.currentRawValue.replace(
upgrade.currentDigest,
// TODO #22198
upgrade.newDigest!.substring(0, upgrade.currentDigest.length), newValue = getNewGitValue(upgrade) ?? newValue;
); newValue = getNewNpmAliasValue(newValue, upgrade) ?? newValue;
} else {
logger.debug('Updating package.json git version tag');
newValue = upgrade.currentRawValue.replace(
upgrade.currentValue,
upgrade.newValue,
);
}
}
if (upgrade.npmPackageAlias) {
newValue = `npm:${upgrade.packageName}@${newValue}`;
}
logger.debug(`npm.updateDependency(): ${depType}.${depName} = ${newValue}`); logger.debug(`npm.updateDependency(): ${depType}.${depName} = ${newValue}`);
try { try {

View file

@ -0,0 +1,336 @@
import { codeBlock } from 'common-tags';
import * as npmUpdater from '../..';
/**
* Per the YAML spec, a document ends with a newline. The 'yaml' library always
* uses that when serialising, but `codeBlock` strips the last indentation. This
* helper makes assertions simpler.
*/
function yamlCodeBlock(
literals: TemplateStringsArray,
...placeholders: any[]
): string {
return codeBlock(literals, placeholders) + '\n';
}
describe('modules/manager/npm/update/dependency/pnpm', () => {
it('handles implicit default catalog dependency', () => {
const upgrade = {
depType: 'pnpm.catalog',
depName: 'react',
newValue: '19.0.0',
managerData: {
catalogName: 'default',
},
};
const pnpmWorkspaceYaml = yamlCodeBlock`
packages:
- pkg-a
catalog:
react: 18.3.1
`;
const expected = yamlCodeBlock`
packages:
- pkg-a
catalog:
react: 19.0.0
`;
const testContent = npmUpdater.updateDependency({
fileContent: pnpmWorkspaceYaml,
upgrade,
});
expect(testContent).toEqual(expected);
});
it('handles explicit default catalog dependency', () => {
const upgrade = {
depType: 'pnpm.catalog',
depName: 'react',
newValue: '19.0.0',
managerData: {
catalogName: 'default',
},
};
const pnpmWorkspaceYaml = yamlCodeBlock`
packages:
- pkg-a
catalogs:
default:
react: 18.3.1
`;
const expected = yamlCodeBlock`
packages:
- pkg-a
catalogs:
default:
react: 19.0.0
`;
const testContent = npmUpdater.updateDependency({
fileContent: pnpmWorkspaceYaml,
upgrade,
});
expect(testContent).toEqual(expected);
});
it('handles explicit named catalog dependency', () => {
const upgrade = {
depType: 'pnpm.catalog',
depName: 'react',
newValue: '19.0.0',
managerData: {
catalogName: 'react17',
},
};
const pnpmWorkspaceYaml = yamlCodeBlock`
packages:
- pkg-a
catalog:
react: 18.3.1
catalogs:
react17:
react: 17.0.0
`;
const expected = yamlCodeBlock`
packages:
- pkg-a
catalog:
react: 18.3.1
catalogs:
react17:
react: 19.0.0
`;
const testContent = npmUpdater.updateDependency({
fileContent: pnpmWorkspaceYaml,
upgrade,
});
expect(testContent).toEqual(expected);
});
it('replaces package', () => {
const upgrade = {
depType: 'pnpm.catalog',
depName: 'config',
newName: 'abc',
newValue: '2.0.0',
managerData: {
catalogName: 'default',
},
};
const pnpmWorkspaceYaml = yamlCodeBlock`
packages:
- pkg-a
catalog:
config: 1.21.0
`;
const expected = yamlCodeBlock`
packages:
- pkg-a
catalog:
abc: 2.0.0
`;
const testContent = npmUpdater.updateDependency({
fileContent: pnpmWorkspaceYaml,
upgrade,
});
expect(testContent).toEqual(expected);
});
it('replaces a github dependency value', () => {
const upgrade = {
depType: 'pnpm.catalog',
depName: 'gulp',
currentValue: 'v4.0.0-alpha.2',
currentRawValue: 'gulpjs/gulp#v4.0.0-alpha.2',
newValue: 'v4.0.0',
managerData: {
catalogName: 'default',
},
};
const pnpmWorkspaceYaml = yamlCodeBlock`
packages:
- pkg-a
catalog:
gulp: gulpjs/gulp#v4.0.0-alpha.2
`;
const expected = yamlCodeBlock`
packages:
- pkg-a
catalog:
gulp: gulpjs/gulp#v4.0.0
`;
const testContent = npmUpdater.updateDependency({
fileContent: pnpmWorkspaceYaml,
upgrade,
});
expect(testContent).toEqual(expected);
});
it('replaces a npm package alias', () => {
const upgrade = {
depType: 'pnpm.catalog',
depName: 'hapi',
npmPackageAlias: true,
packageName: '@hapi/hapi',
currentValue: '18.3.0',
newValue: '18.3.1',
managerData: {
catalogName: 'default',
},
};
const pnpmWorkspaceYaml = yamlCodeBlock`
packages:
- pkg-a
catalog:
hapi: npm:@hapi/hapi@18.3.0
`;
const expected = yamlCodeBlock`
packages:
- pkg-a
catalog:
hapi: npm:@hapi/hapi@18.3.1
`;
const testContent = npmUpdater.updateDependency({
fileContent: pnpmWorkspaceYaml,
upgrade,
});
expect(testContent).toEqual(expected);
});
it('replaces a github short hash', () => {
const upgrade = {
depType: 'pnpm.catalog',
depName: 'gulp',
currentDigest: 'abcdef7',
currentRawValue: 'gulpjs/gulp#abcdef7',
newDigest: '0000000000111111111122222222223333333333',
managerData: {
catalogName: 'default',
},
};
const pnpmWorkspaceYaml = yamlCodeBlock`
packages:
- pkg-a
catalog:
gulp: gulpjs/gulp#abcdef7
`;
const expected = yamlCodeBlock`
packages:
- pkg-a
catalog:
gulp: gulpjs/gulp#0000000
`;
const testContent = npmUpdater.updateDependency({
fileContent: pnpmWorkspaceYaml,
upgrade,
});
expect(testContent).toEqual(expected);
});
it('replaces a github fully specified version', () => {
const upgrade = {
depType: 'pnpm.catalog',
depName: 'n',
currentValue: 'v1.0.0',
currentRawValue: 'git+https://github.com/owner/n#v1.0.0',
newValue: 'v1.1.0',
managerData: {
catalogName: 'default',
},
};
const pnpmWorkspaceYaml = yamlCodeBlock`
packages:
- pkg-a
catalog:
n: git+https://github.com/owner/n#v1.0.0
`;
const expected = yamlCodeBlock`
packages:
- pkg-a
catalog:
n: git+https://github.com/owner/n#v1.1.0
`;
const testContent = npmUpdater.updateDependency({
fileContent: pnpmWorkspaceYaml,
upgrade,
});
expect(testContent).toEqual(expected);
});
it('returns null if the dependency is not present in the target catalog', () => {
const upgrade = {
depType: 'pnpm.catalog',
depName: 'react-not',
newValue: '19.0.0',
managerData: {
catalogName: 'default',
},
};
const pnpmWorkspaceYaml = yamlCodeBlock`
packages:
- pkg-a
catalog:
react: 18.3.1
`;
const testContent = npmUpdater.updateDependency({
fileContent: pnpmWorkspaceYaml,
upgrade,
});
expect(testContent).toBeNull();
});
it('returns null if catalogs are missing', () => {
const upgrade = {
depType: 'pnpm.catalog',
depName: 'react',
newValue: '19.0.0',
managerData: {
catalogName: 'default',
},
};
const pnpmWorkspaceYaml = yamlCodeBlock`
packages:
- pkg-a
`;
const testContent = npmUpdater.updateDependency({
fileContent: pnpmWorkspaceYaml,
upgrade,
});
expect(testContent).toBeNull();
});
it('returns null if empty file', () => {
const upgrade = {
depType: 'pnpm.catalog',
depName: 'react',
newValue: '19.0.0',
managerData: {
catalogName: 'default',
},
};
const testContent = npmUpdater.updateDependency({
fileContent: null as never,
upgrade,
});
expect(testContent).toBeNull();
});
});

View file

@ -0,0 +1,104 @@
import is from '@sindresorhus/is';
import { stringify } from 'yaml';
import { logger } from '../../../../../logger';
import { parseSingleYamlDocument } from '../../../../../util/yaml';
import type { UpdateDependencyConfig } from '../../../types';
import { pnpmCatalogsSchema } from '../../extract/pnpm';
import { getNewGitValue, getNewNpmAliasValue } from './common';
export function updatePnpmCatalogDependency({
fileContent,
upgrade,
}: UpdateDependencyConfig): string | null {
const { depType, managerData, depName } = upgrade;
const catalogName = managerData?.catalogName;
if (!is.string(catalogName)) {
logger.error(
'No catalogName was found; this is likely an extraction error.',
);
return null;
}
let { newValue } = upgrade;
newValue = getNewGitValue(upgrade) ?? newValue;
newValue = getNewNpmAliasValue(newValue, upgrade) ?? newValue;
logger.debug(
`npm.updatePnpmCatalogDependency(): ${depType}:${managerData?.catalogName}.${depName} = ${newValue}`,
);
let document;
let parsedContents;
try {
document = parseSingleYamlDocument(fileContent);
parsedContents = pnpmCatalogsSchema.parse(document.toJS());
} catch (err) {
logger.debug({ err }, 'Could not parse pnpm-workspace YAML file.');
return null;
}
// In pnpm-workspace.yaml, the default catalog can be either `catalog` or
// `catalog.default`, but not both (pnpm throws outright with a config error).
// Thus, we must check which entry is being used, to reference it from the
// right place.
const usesImplicitDefaultCatalog = parsedContents.catalog !== undefined;
// Save the old version
const oldVersion =
catalogName === 'default' && usesImplicitDefaultCatalog
? parsedContents.catalog?.[depName!]
: parsedContents.catalogs?.[catalogName]?.[depName!];
if (oldVersion === newValue) {
logger.trace('Version is already updated');
return fileContent;
}
// Update the value
const path = getDepPath({
depName: depName!,
catalogName,
usesImplicitDefaultCatalog,
});
if (!document.hasIn(path)) {
return null;
}
document.setIn(path, newValue);
// Update the name, for replacements
if (upgrade.newName) {
const newPath = getDepPath({
depName: upgrade.newName,
catalogName,
usesImplicitDefaultCatalog,
});
const oldValue = document.getIn(path);
document.deleteIn(path);
document.setIn(newPath, oldValue);
}
return stringify(document);
}
function getDepPath({
catalogName,
depName,
usesImplicitDefaultCatalog,
}: {
usesImplicitDefaultCatalog: boolean;
catalogName: string;
depName: string;
}): string[] {
if (catalogName === 'default' && usesImplicitDefaultCatalog) {
return ['catalog', depName];
} else {
return ['catalogs', catalogName, depName];
}
}

View file

@ -11,7 +11,7 @@ import { find } from '../../../../util/host-rules';
import { Result } from '../../../../util/result'; import { Result } from '../../../../util/result';
import { parseUrl } from '../../../../util/url'; import { parseUrl } from '../../../../util/url';
import { PypiDatasource } from '../../../datasource/pypi'; import { PypiDatasource } from '../../../datasource/pypi';
import { getGoogleAuthTokenRaw } from '../../../datasource/util'; import { getGoogleAuthHostRule } from '../../../datasource/util';
import type { import type {
PackageDependency, PackageDependency,
UpdateArtifact, UpdateArtifact,
@ -265,12 +265,9 @@ async function getUsernamePassword(
} }
if (url.hostname.endsWith('.pkg.dev')) { if (url.hostname.endsWith('.pkg.dev')) {
const accessToken = await getGoogleAuthTokenRaw(); const hostRule = await getGoogleAuthHostRule();
if (accessToken) { if (hostRule) {
return { return hostRule;
username: 'oauth2accesstoken',
password: accessToken,
};
} else { } else {
logger.once.debug({ url }, 'Could not get Google access token'); logger.once.debug({ url }, 'Could not get Google access token');
} }

View file

@ -19,7 +19,7 @@ import { Result } from '../../../util/result';
import { parse as parseToml } from '../../../util/toml'; import { parse as parseToml } from '../../../util/toml';
import { parseUrl } from '../../../util/url'; import { parseUrl } from '../../../util/url';
import { PypiDatasource } from '../../datasource/pypi'; import { PypiDatasource } from '../../datasource/pypi';
import { getGoogleAuthTokenRaw } from '../../datasource/util'; import { getGoogleAuthHostRule } from '../../datasource/util';
import type { UpdateArtifact, UpdateArtifactsResult } from '../types'; import type { UpdateArtifact, UpdateArtifactsResult } from '../types';
import { Lockfile, PoetrySchemaToml } from './schema'; import { Lockfile, PoetrySchemaToml } from './schema';
import type { PoetryFile, PoetrySource } from './types'; import type { PoetryFile, PoetrySource } from './types';
@ -131,12 +131,9 @@ async function getMatchingHostRule(url: string | undefined): Promise<HostRule> {
} }
if (parsedUrl.hostname.endsWith('.pkg.dev')) { if (parsedUrl.hostname.endsWith('.pkg.dev')) {
const accessToken = await getGoogleAuthTokenRaw(); const hostRule = await getGoogleAuthHostRule();
if (accessToken) { if (hostRule) {
return { return hostRule;
username: 'oauth2accesstoken',
password: accessToken,
};
} }
logger.once.debug(`Could not get Google access token (url=${url})`); logger.once.debug(`Could not get Google access token (url=${url})`);
} }

View file

@ -1,5 +1,6 @@
import type { import type {
CreateNodeOptions, CreateNodeOptions,
Document,
DocumentOptions, DocumentOptions,
ParseOptions, ParseOptions,
SchemaOptions, SchemaOptions,
@ -20,6 +21,13 @@ interface YamlOptions<
removeTemplates?: boolean; removeTemplates?: boolean;
} }
interface YamlParseDocumentOptions
extends ParseOptions,
DocumentOptions,
SchemaOptions {
removeTemplates?: boolean;
}
interface YamlOptionsMultiple< interface YamlOptionsMultiple<
ResT = unknown, ResT = unknown,
Schema extends ZodType<ResT> = ZodType<ResT>, Schema extends ZodType<ResT> = ZodType<ResT>,
@ -117,6 +125,29 @@ export function parseSingleYaml<ResT = unknown>(
content: string, content: string,
options?: YamlOptions<ResT>, options?: YamlOptions<ResT>,
): ResT { ): ResT {
const rawDocument = parseSingleYamlDocument(content, options);
const document = rawDocument.toJS({ maxAliasCount: 10000 });
const schema = options?.customSchema;
if (!schema) {
return document as ResT;
}
return schema.parse(document);
}
/**
* Parse a YAML string into a Document representation.
*
* Only a single document is supported.
*
* @param content
* @param options
*/
export function parseSingleYamlDocument(
content: string,
options?: YamlParseDocumentOptions,
): Document {
const massagedContent = massageContent(content, options); const massagedContent = massageContent(content, options);
const rawDocument = parseDocument( const rawDocument = parseDocument(
massagedContent, massagedContent,
@ -127,13 +158,7 @@ export function parseSingleYaml<ResT = unknown>(
throw new AggregateError(rawDocument.errors, 'Failed to parse YAML file'); throw new AggregateError(rawDocument.errors, 'Failed to parse YAML file');
} }
const document = rawDocument.toJS({ maxAliasCount: 10000 }); return rawDocument;
const schema = options?.customSchema;
if (!schema) {
return document as ResT;
}
return schema.parse(document);
} }
export function dump(obj: any, opts?: DumpOptions): string { export function dump(obj: any, opts?: DumpOptions): string {