mirror of
https://github.com/renovatebot/renovate.git
synced 2026-10-25 15:01:49 +00:00
feat(github-actions): add some community action support (#34791)
Co-authored-by: Michael Kriese <michael.kriese@gmx.de>
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { SkipReason, StageName } from '../../../types';
|
||||
import { escapeRegExp, regEx } from '../../../util/regex';
|
||||
import { GithubReleasesDatasource } from '../../datasource/github-releases';
|
||||
import { NpmDatasource } from '../../datasource/npm';
|
||||
import { PypiDatasource } from '../../datasource/pypi';
|
||||
import * as condaVersioning from '../../versioning/conda';
|
||||
import * as npmVersioning from '../../versioning/npm';
|
||||
import * as pep440versioning from '../../versioning/pep440';
|
||||
import type { PackageDependency } from '../types';
|
||||
|
||||
function matchAction(action: string): z.Schema {
|
||||
return z
|
||||
.string()
|
||||
.regex(regEx(`(?:https?://[^/]+/)?${escapeRegExp(action)}(?:@.+)?$`));
|
||||
}
|
||||
|
||||
const SetupUVSchema = z
|
||||
.object({
|
||||
// https://github.com/astral-sh/setup-uv
|
||||
uses: matchAction('astral-sh/setup-uv'),
|
||||
with: z.object({ version: z.string().optional() }),
|
||||
})
|
||||
.transform(({ with: val }): PackageDependency => {
|
||||
let skipStage: StageName | undefined;
|
||||
let skipReason: SkipReason | undefined;
|
||||
|
||||
if (!val.version) {
|
||||
skipStage = 'extract';
|
||||
skipReason = 'unspecified-version';
|
||||
}
|
||||
|
||||
return {
|
||||
datasource: GithubReleasesDatasource.id,
|
||||
depName: 'astral-sh/uv',
|
||||
versioning: npmVersioning.id,
|
||||
packageName: 'astral-sh/uv',
|
||||
...(skipStage && { skipStage }),
|
||||
...(skipReason && { skipReason }),
|
||||
currentValue: val.version,
|
||||
depType: 'uses-with',
|
||||
};
|
||||
});
|
||||
|
||||
const SetupPnpmSchema = z
|
||||
.object({
|
||||
uses: matchAction('pnpm/action-setup'),
|
||||
with: z.object({
|
||||
version: z.string().optional(),
|
||||
}),
|
||||
})
|
||||
.transform(({ with: val }): PackageDependency => {
|
||||
let skipStage: StageName | undefined;
|
||||
let skipReason: SkipReason | undefined;
|
||||
if (!val.version) {
|
||||
skipStage = 'extract';
|
||||
skipReason = 'unspecified-version';
|
||||
}
|
||||
|
||||
return {
|
||||
datasource: NpmDatasource.id,
|
||||
depName: 'pnpm',
|
||||
versioning: npmVersioning.id,
|
||||
packageName: 'pnpm',
|
||||
...(skipStage && { skipStage }),
|
||||
...(skipReason && { skipReason }),
|
||||
currentValue: val.version,
|
||||
depType: 'uses-with',
|
||||
};
|
||||
});
|
||||
|
||||
const SetupPDMSchema = z
|
||||
.object({
|
||||
uses: matchAction('pdm-project/setup-pdm'),
|
||||
with: z.object({ version: z.string().optional() }),
|
||||
})
|
||||
.transform(({ with: val }): PackageDependency => {
|
||||
let skipStage: StageName | undefined;
|
||||
let skipReason: SkipReason | undefined;
|
||||
if (!val.version) {
|
||||
skipStage = 'extract';
|
||||
skipReason = 'unspecified-version';
|
||||
}
|
||||
|
||||
return {
|
||||
datasource: PypiDatasource.id,
|
||||
depName: 'pdm',
|
||||
versioning: pep440versioning.id,
|
||||
packageName: 'pdm',
|
||||
currentValue: val.version,
|
||||
...(skipStage && { skipStage }),
|
||||
...(skipReason && { skipReason }),
|
||||
depType: 'uses-with',
|
||||
};
|
||||
});
|
||||
|
||||
const InstallBinarySchema = z
|
||||
.object({
|
||||
uses: z.union([
|
||||
matchAction('jaxxstorm/action-install-gh-release'),
|
||||
matchAction('sigoden/install-binary'),
|
||||
]),
|
||||
with: z.object({ repo: z.string(), tag: z.string() }),
|
||||
})
|
||||
.transform(({ with: val }): PackageDependency => {
|
||||
return {
|
||||
datasource: GithubReleasesDatasource.id,
|
||||
depName: val.repo,
|
||||
packageName: val.repo,
|
||||
currentValue: val.tag,
|
||||
depType: 'uses-with',
|
||||
};
|
||||
});
|
||||
|
||||
const SetupPixiSchema = z
|
||||
.object({
|
||||
uses: matchAction('prefix-dev/setup-pixi'),
|
||||
with: z.object({ 'pixi-version': z.string() }),
|
||||
})
|
||||
.transform(({ with: val }): PackageDependency => {
|
||||
return {
|
||||
datasource: GithubReleasesDatasource.id,
|
||||
versioning: condaVersioning.id,
|
||||
depName: 'prefix-dev/pixi',
|
||||
packageName: 'prefix-dev/pixi',
|
||||
currentValue: val['pixi-version'],
|
||||
depType: 'uses-with',
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* schema here should match the whole step,
|
||||
* there may be some actions use env as arguments version.
|
||||
*
|
||||
* each type should return `PackageDependency | undefined`
|
||||
*/
|
||||
export const CommunityActions = z.union([
|
||||
InstallBinarySchema,
|
||||
SetupPDMSchema,
|
||||
SetupPixiSchema,
|
||||
SetupPnpmSchema,
|
||||
SetupUVSchema,
|
||||
]);
|
||||
@@ -1,5 +1,6 @@
|
||||
import { codeBlock } from 'common-tags';
|
||||
import { GlobalConfig } from '../../../config/global';
|
||||
import * as yaml from '../../../util/yaml';
|
||||
import { extractPackageFile } from '.';
|
||||
import { Fixtures } from '~test/fixtures';
|
||||
|
||||
@@ -852,4 +853,233 @@ describe('modules/manager/github-actions/extract', () => {
|
||||
expect(extractPackageFile(yamlContent, 'action.yml')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
step: {
|
||||
uses: 'astral-sh/setup-uv@v5',
|
||||
with: {
|
||||
version: 'latest',
|
||||
},
|
||||
},
|
||||
expected: [
|
||||
{
|
||||
currentValue: 'latest',
|
||||
datasource: 'github-releases',
|
||||
depName: 'astral-sh/uv',
|
||||
depType: 'uses-with',
|
||||
packageName: 'astral-sh/uv',
|
||||
versioning: 'npm',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
step: {
|
||||
name: 'Pinning a minor version of uv',
|
||||
uses: 'astral-sh/setup-uv@v5',
|
||||
with: {
|
||||
version: '0.4.x',
|
||||
},
|
||||
},
|
||||
expected: [
|
||||
{
|
||||
currentValue: '0.4.x',
|
||||
datasource: 'github-releases',
|
||||
depName: 'astral-sh/uv',
|
||||
depType: 'uses-with',
|
||||
packageName: 'astral-sh/uv',
|
||||
versioning: 'npm',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
step: {
|
||||
name: 'Pinning a minor version of uv',
|
||||
uses: 'https://github.com/astral-sh/setup-uv@v5',
|
||||
with: {
|
||||
version: '0.4.x',
|
||||
},
|
||||
},
|
||||
expected: [
|
||||
{
|
||||
currentValue: '0.4.x',
|
||||
datasource: 'github-releases',
|
||||
depName: 'astral-sh/uv',
|
||||
depType: 'uses-with',
|
||||
packageName: 'astral-sh/uv',
|
||||
versioning: 'npm',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
step: {
|
||||
uses: 'https://github.com/astral-sh/setup-uv@v5',
|
||||
with: {},
|
||||
},
|
||||
expected: [
|
||||
{
|
||||
datasource: 'github-releases',
|
||||
depName: 'astral-sh/uv',
|
||||
depType: 'uses-with',
|
||||
skipStage: 'extract',
|
||||
skipReason: 'unspecified-version',
|
||||
packageName: 'astral-sh/uv',
|
||||
versioning: 'npm',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
step: {
|
||||
uses: 'pnpm/action-setup@v4',
|
||||
with: {
|
||||
version: 'latest',
|
||||
},
|
||||
},
|
||||
expected: [
|
||||
{
|
||||
currentValue: 'latest',
|
||||
datasource: 'npm',
|
||||
depName: 'pnpm',
|
||||
depType: 'uses-with',
|
||||
packageName: 'pnpm',
|
||||
versioning: 'npm',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
step: {
|
||||
uses: 'pnpm/action-setup@v4',
|
||||
with: {},
|
||||
},
|
||||
expected: [
|
||||
{
|
||||
skipStage: 'extract',
|
||||
skipReason: 'unspecified-version',
|
||||
datasource: 'npm',
|
||||
depName: 'pnpm',
|
||||
depType: 'uses-with',
|
||||
packageName: 'pnpm',
|
||||
versioning: 'npm',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
step: {
|
||||
name: 'Install gotestsum',
|
||||
uses: 'jaxxstorm/action-install-gh-release@v1.10.0',
|
||||
with: {
|
||||
repo: 'gotestyourself/gotestsum',
|
||||
tag: 'v1.12.1',
|
||||
platform: 'linux',
|
||||
arch: 'amd64',
|
||||
},
|
||||
},
|
||||
expected: [
|
||||
{
|
||||
currentValue: 'v1.12.1',
|
||||
datasource: 'github-releases',
|
||||
depName: 'gotestyourself/gotestsum',
|
||||
depType: 'uses-with',
|
||||
packageName: 'gotestyourself/gotestsum',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
step: {
|
||||
uses: 'pnpm/action-setup@v4',
|
||||
with: {
|
||||
version: 10,
|
||||
},
|
||||
},
|
||||
expected: [
|
||||
{
|
||||
currentValue: '10',
|
||||
datasource: 'npm',
|
||||
depName: 'pnpm',
|
||||
depType: 'uses-with',
|
||||
packageName: 'pnpm',
|
||||
versioning: 'npm',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
step: {
|
||||
uses: 'pnpm/action-setup@v4',
|
||||
with: {
|
||||
version: '10.x',
|
||||
},
|
||||
},
|
||||
expected: [
|
||||
{
|
||||
currentValue: '10.x',
|
||||
datasource: 'npm',
|
||||
depName: 'pnpm',
|
||||
depType: 'uses-with',
|
||||
packageName: 'pnpm',
|
||||
versioning: 'npm',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
step: {
|
||||
uses: 'pdm-project/setup-pdm@v4.2',
|
||||
with: {
|
||||
version: '1.2.3',
|
||||
},
|
||||
},
|
||||
expected: [
|
||||
{
|
||||
currentValue: '1.2.3',
|
||||
datasource: 'pypi',
|
||||
depName: 'pdm',
|
||||
depType: 'uses-with',
|
||||
packageName: 'pdm',
|
||||
versioning: 'pep440',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
step: {
|
||||
uses: 'pdm-project/setup-pdm@v4.2',
|
||||
with: {},
|
||||
},
|
||||
expected: [
|
||||
{
|
||||
skipStage: 'extract',
|
||||
skipReason: 'unspecified-version',
|
||||
datasource: 'pypi',
|
||||
depName: 'pdm',
|
||||
depType: 'uses-with',
|
||||
packageName: 'pdm',
|
||||
versioning: 'pep440',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
step: {
|
||||
uses: 'prefix-dev/setup-pixi@v0.8.3',
|
||||
with: {
|
||||
'pixi-version': 'v0.41.4',
|
||||
},
|
||||
},
|
||||
expected: [
|
||||
{
|
||||
currentValue: 'v0.41.4',
|
||||
datasource: 'github-releases',
|
||||
depName: 'prefix-dev/pixi',
|
||||
depType: 'uses-with',
|
||||
packageName: 'prefix-dev/pixi',
|
||||
versioning: 'conda',
|
||||
},
|
||||
],
|
||||
},
|
||||
])('extract from $step.uses', ({ step, expected }) => {
|
||||
const yamlContent = yaml.dump({ jobs: { build: { steps: [step] } } });
|
||||
|
||||
const res = extractPackageFile(yamlContent, 'workflow.yml');
|
||||
expect(res?.deps.filter((pkg) => pkg.depType !== 'action')).toMatchObject(
|
||||
expected,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,6 +16,7 @@ import type {
|
||||
PackageDependency,
|
||||
PackageFileContent,
|
||||
} from '../types';
|
||||
import { CommunityActions } from './community';
|
||||
import type { Steps } from './schema';
|
||||
import { WorkflowSchema } from './schema';
|
||||
|
||||
@@ -188,6 +189,12 @@ function extractSteps(
|
||||
deps: PackageDependency<Record<string, any>>[],
|
||||
): void {
|
||||
for (const step of steps) {
|
||||
const res = CommunityActions.safeParse(step);
|
||||
if (res.success) {
|
||||
deps.push(res.data);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const [action, versioning] of Object.entries(versionedActions)) {
|
||||
const actionName = `actions/setup-${action}`;
|
||||
if (step.uses === actionName || step.uses?.startsWith(`${actionName}@`)) {
|
||||
|
||||
@@ -75,3 +75,14 @@ For example, normally the `^` syntax is not used in `go` or `python`, but it's s
|
||||
|
||||
Depending on your use case, you may need to change `versioning` manually.
|
||||
If you find a use case which you think Renovate could/should automatically detect and support without manual configuration, please raise a Discussion to suggest it.
|
||||
|
||||
### commonly used community actions
|
||||
|
||||
Renovate also supports some commonly used community actions:
|
||||
|
||||
- `astral-sh/setup-uv`
|
||||
- `pnpm/action-setup`
|
||||
- `pdm-project/setup-pdm`
|
||||
- `jaxxstorm/action-install-gh-release`
|
||||
- `sigoden/install-binary`
|
||||
- `prefix-dev/setup-pixi`
|
||||
|
||||
@@ -8,7 +8,9 @@ import {
|
||||
|
||||
const StepsSchema = z.object({
|
||||
uses: z.string(),
|
||||
with: LooseRecord(z.string()),
|
||||
with: LooseRecord(
|
||||
z.union([z.string(), z.number().transform((s) => s.toString())]),
|
||||
),
|
||||
});
|
||||
export type Steps = z.infer<typeof StepsSchema>;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user