mirror of
https://github.com/astral-sh/setup-uv.git
synced 2026-03-27 01:37:31 +00:00
Compare commits
425 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c62c59261 | ||
|
|
e1a7373adb | ||
|
|
89709315bb | ||
|
|
8cc8d1cbfc | ||
|
|
c20049fc26 | ||
|
|
37802adc94 | ||
|
|
9f00d186ce | ||
|
|
fd8f376b22 | ||
|
|
f9070de1ea | ||
|
|
cadb67bdc9 | ||
|
|
e06108dd0a | ||
|
|
0f6ec07aaf | ||
|
|
821e5c9815 | ||
|
|
6ee6290f1c | ||
|
|
9f332a133a | ||
|
|
0acf9708ce | ||
|
|
fe3617d6e9 | ||
|
|
2ff70eebcc | ||
|
|
5ba8a7e5d0 | ||
|
|
4bc8fabc0c | ||
|
|
950b623541 | ||
|
|
09ff6fe0ae | ||
|
|
bd870193dd | ||
|
|
f8858e6756 | ||
|
|
5a095e7a20 | ||
|
|
b12532f27f | ||
|
|
0098a7571c | ||
|
|
2e7ed0e2bb | ||
|
|
04224aa8ca | ||
|
|
2bc602ff89 | ||
|
|
dd9d748439 | ||
|
|
14eede1834 | ||
|
|
c452423b2c | ||
|
|
eac588ad8d | ||
|
|
a97c6cbe9c | ||
|
|
02182fa02a | ||
|
|
a3b3eaea92 | ||
|
|
78cebeceac | ||
|
|
b6b8e2cd6a | ||
|
|
e31bec8546 | ||
|
|
db2b65ebae | ||
|
|
3511ff7054 | ||
|
|
99b0f0474b | ||
|
|
db4d6bf3d6 | ||
|
|
98e1309028 | ||
|
|
5ed2ede620 | ||
|
|
5fca386933 | ||
|
|
803947b9bd | ||
|
|
24553ac46d | ||
|
|
085087a5d3 | ||
|
|
9cfd029643 | ||
|
|
dd9d55bc18 | ||
|
|
8512ad0289 | ||
|
|
cc5581700e | ||
|
|
61cb8a9741 | ||
|
|
11050edb83 | ||
|
|
1d22fafd8b | ||
|
|
f4ed82a8ce | ||
|
|
e0409b43c0 | ||
|
|
702b425af1 | ||
|
|
2630c86ac3 | ||
|
|
45cfcb3be5 | ||
|
|
ce0a8994de | ||
|
|
9c8d030b7f | ||
|
|
681c641aba | ||
|
|
2e85713bb0 | ||
|
|
58b6d7b303 | ||
|
|
e8b52af86e | ||
|
|
ed21f2f24f | ||
|
|
93202d8fbe | ||
|
|
5ce090076d | ||
|
|
4180991cd9 | ||
|
|
0439606c8e | ||
|
|
7dd56c18e9 | ||
|
|
9c12baee96 | ||
|
|
64f7f4e15f | ||
|
|
5ae467fbf9 | ||
|
|
06e4edb239 | ||
|
|
8f1d388b4b | ||
|
|
d500d41ebf | ||
|
|
1e64fb113b | ||
|
|
be7fc19b41 | ||
|
|
1e862dfacb | ||
|
|
d7d33e16d4 | ||
|
|
486d0b8872 | ||
|
|
5a7eac68fb | ||
|
|
b49dc9e882 | ||
|
|
30ce38e206 | ||
|
|
0d20755a23 | ||
|
|
8491d1d9a3 | ||
|
|
85856786d1 | ||
|
|
22d500a65c | ||
|
|
14d557131d | ||
|
|
29cd2350cd | ||
|
|
2ddd2b9cb3 | ||
|
|
b7bf78939d | ||
|
|
cb6c0a53d9 | ||
|
|
dffc6292f2 | ||
|
|
6e346e1653 | ||
|
|
3ccd0fd498 | ||
|
|
ce6dbd84e1 | ||
|
|
2382069a66 | ||
|
|
b1daf91f4e | ||
|
|
3259c6206f | ||
|
|
bf8e8ed895 | ||
|
|
9c6b5e9fb5 | ||
|
|
a5129e99f4 | ||
|
|
d18bcc753a | ||
|
|
bd1f875aba | ||
|
|
1a91c3851d | ||
|
|
c79f606987 | ||
|
|
e0249f1599 | ||
|
|
6d2eb15b49 | ||
|
|
3495667518 | ||
|
|
eb1897b8dc | ||
|
|
d78d791822 | ||
|
|
535dc2664c | ||
|
|
f610be5ff9 | ||
|
|
3deccc0075 | ||
|
|
d9ee7e2f26 | ||
|
|
59a0868fea | ||
|
|
c952556164 | ||
|
|
51c3328db2 | ||
|
|
f2859da213 | ||
|
|
f9c6974d8b | ||
|
|
82f21a54fe | ||
|
|
d8a37f6566 | ||
|
|
d0cc045d04 | ||
|
|
2841f9f5c1 | ||
|
|
e554b93b80 | ||
|
|
c7d85d9988 | ||
|
|
07f2cb5db9 | ||
|
|
208b0c0ee4 | ||
|
|
b75a909f75 | ||
|
|
ffff8aa2b5 | ||
|
|
95d0e233fa | ||
|
|
dc724a12b6 | ||
|
|
f67343ac2e | ||
|
|
4dd9f52a47 | ||
|
|
e1e6fe7910 | ||
|
|
b1836110f7 | ||
|
|
557e51de59 | ||
|
|
1b46e13ec8 | ||
|
|
26cf676705 | ||
|
|
4e1e303f7d | ||
|
|
4959332f0f | ||
|
|
adeb28643f | ||
|
|
fce199e243 | ||
|
|
f758a4a1eb | ||
|
|
c0e7e93474 | ||
|
|
fda2399cb3 | ||
|
|
d9e0f98d3f | ||
|
|
e5d42a2b46 | ||
|
|
d664c2a1d1 | ||
|
|
c35b8eac36 | ||
|
|
4109b4033f | ||
|
|
1463845d3c | ||
|
|
ad5ded2d63 | ||
|
|
142240426d | ||
|
|
632449003a | ||
|
|
2a967c9b97 | ||
|
|
43f37368c9 | ||
|
|
4fb0c07c55 | ||
|
|
e92bafb625 | ||
|
|
2c7142f755 | ||
|
|
23482a31a8 | ||
|
|
4ac06a054e | ||
|
|
7edac99f96 | ||
|
|
05273c154d | ||
|
|
de545d4421 | ||
|
|
b75ff7d7b8 | ||
|
|
c893ac1cb2 | ||
|
|
a905f0040b | ||
|
|
d4219d1620 | ||
|
|
aaefb91b77 | ||
|
|
c05b3e180b | ||
|
|
1bf1493664 | ||
|
|
b4c8c41d99 | ||
|
|
8e581f64a9 | ||
|
|
bd01e18f51 | ||
|
|
c6a5ebaafe | ||
|
|
790df8f465 | ||
|
|
445689ea25 | ||
|
|
a02a550bdd | ||
|
|
60cc2b4585 | ||
|
|
7bbb36f434 | ||
|
|
60ecb381b4 | ||
|
|
252c995424 | ||
|
|
477a814f2d | ||
|
|
9b19f8f4b1 | ||
|
|
d44461ea9f | ||
|
|
c19c1b1ffd | ||
|
|
f3121ef80e | ||
|
|
f0ec1fc3b3 | ||
|
|
e3d2ea5ff3 | ||
|
|
b3d7ca7ac0 | ||
|
|
0e0f4bfefa | ||
|
|
71bb8825e4 | ||
|
|
1417e89049 | ||
|
|
1761eea391 | ||
|
|
9864bc9c96 | ||
|
|
0e9cccb4b0 | ||
|
|
b6f9e9c734 | ||
|
|
b87cce2fc5 | ||
|
|
9641fcd493 | ||
|
|
a4f1f549e9 | ||
|
|
023b8ec2bc | ||
|
|
ced7c1dde4 | ||
|
|
41bd088443 | ||
|
|
75567654b6 | ||
|
|
6b9c6063ab | ||
|
|
ef6bcdff59 | ||
|
|
9a311713f4 | ||
|
|
c7f87aa956 | ||
|
|
aadfaf08d6 | ||
|
|
a0f9da6273 | ||
|
|
ec4c691628 | ||
|
|
aa1290542e | ||
|
|
fcaddda076 | ||
|
|
fb3a0a97fa | ||
|
|
d4b2f3b6ec | ||
|
|
594f292eef | ||
|
|
9ad030384d | ||
|
|
9e2c33a082 | ||
|
|
839076380b | ||
|
|
9bf3815166 | ||
|
|
0c5e2b8115 | ||
|
|
794ea9455c | ||
|
|
2d49baf2b6 | ||
|
|
4fa25599ce | ||
|
|
224dce1d79 | ||
|
|
22695119d7 | ||
|
|
bf8ec1ea35 | ||
|
|
1fb7cdfc29 | ||
|
|
57fe17c2c5 | ||
|
|
72002e8b87 | ||
|
|
19df292e24 | ||
|
|
7d9a2d93c4 | ||
|
|
389b596663 | ||
|
|
04c950a723 | ||
|
|
d02c4c2d68 | ||
|
|
a4fd982317 | ||
|
|
a05a582c56 | ||
|
|
0e855c90d0 | ||
|
|
d8a276f11f | ||
|
|
59ae1ec55b | ||
|
|
f94ec6bedd | ||
|
|
0313224678 | ||
|
|
754a7d4c2d | ||
|
|
b498c74bf4 | ||
|
|
b9ef7bd2eb | ||
|
|
1edb52594c | ||
|
|
a4fbf7b827 | ||
|
|
c122541d0b | ||
|
|
7c47ef9ebd | ||
|
|
e2e9087257 | ||
|
|
bb8d247e1a | ||
|
|
1ffa6dc3ad | ||
|
|
ee84cf5cb8 | ||
|
|
f95cd8710c | ||
|
|
61ee7954c6 | ||
|
|
cad8337f4e | ||
|
|
a4c8ae423e | ||
|
|
afa3c8c42b | ||
|
|
4db96194c3 | ||
|
|
2625dd350b | ||
|
|
f9e15a1be8 | ||
|
|
1c21f62d98 | ||
|
|
982fbca0f8 | ||
|
|
35cf70845a | ||
|
|
7cf65ded99 | ||
|
|
6ade4fc248 | ||
|
|
6e6e5a74f6 | ||
|
|
20980170aa | ||
|
|
02dfe76bef | ||
|
|
3548439624 | ||
|
|
9d3a8b144e | ||
|
|
14dc0be27c | ||
|
|
b5f58b2abc | ||
|
|
4e3dbecc19 | ||
|
|
2487ffc9aa | ||
|
|
118b7214ec | ||
|
|
d942048030 | ||
|
|
77cc1aee22 | ||
|
|
169ed2a5f2 | ||
|
|
9fffe05b88 | ||
|
|
5ce9ee0011 | ||
|
|
d577e74f98 | ||
|
|
7174288630 | ||
|
|
94a861f4b5 | ||
|
|
e9f61537d9 | ||
|
|
4cd05096c3 | ||
|
|
7768fe6bf0 | ||
|
|
7b290f7b85 | ||
|
|
949720bc7f | ||
|
|
d837751086 | ||
|
|
9869cbc19a | ||
|
|
03fe035094 | ||
|
|
887a942a15 | ||
|
|
d174a24c07 | ||
|
|
12c852e6ba | ||
|
|
180f8b4439 | ||
|
|
e3fb95a689 | ||
|
|
2af22b5b2d | ||
|
|
dd578776bb | ||
|
|
85aa0bf0c1 | ||
|
|
1f2cbfa7bb | ||
|
|
25b3ce6330 | ||
|
|
856099c958 | ||
|
|
e3017a763c | ||
|
|
3460fe1a9a | ||
|
|
884a30e33c | ||
|
|
f064c84ddb | ||
|
|
be4207d29e | ||
|
|
bdcda7e77f | ||
|
|
1e4d4ea9ff | ||
|
|
f0b64e0d53 | ||
|
|
38f3f10444 | ||
|
|
8bdd012be5 | ||
|
|
5f42d5af6c | ||
|
|
26ddfef6e1 | ||
|
|
ee4fa33003 | ||
|
|
420915557e | ||
|
|
9839fa9fb5 | ||
|
|
196fe5f098 | ||
|
|
49d8a3d9a8 | ||
|
|
d8db0a86d3 | ||
|
|
ed171c292b | ||
|
|
691a091485 | ||
|
|
9b71657bb2 | ||
|
|
caf0cab7a6 | ||
|
|
7c238111e6 | ||
|
|
3eca4c2715 | ||
|
|
aee2e918ee | ||
|
|
4ffb6d766c | ||
|
|
e779db7426 | ||
|
|
cb1ce8a914 | ||
|
|
cf7bbf8f13 | ||
|
|
2e657c127d | ||
|
|
a7e15805d2 | ||
|
|
2a578ce17f | ||
|
|
6f467a02b3 | ||
|
|
d2242d1901 | ||
|
|
5552ab3709 | ||
|
|
3e4fe09ab3 | ||
|
|
56f89d8124 | ||
|
|
3b9817b1bf | ||
|
|
cf841c25e2 | ||
|
|
864c48a352 | ||
|
|
64311bdf43 | ||
|
|
59604e6118 | ||
|
|
260f4a22de | ||
|
|
273d3782a2 | ||
|
|
ed0a39790e | ||
|
|
2d11fcb2c1 | ||
|
|
a714a3589c | ||
|
|
59ca521371 | ||
|
|
f3bcaebff5 | ||
|
|
10d8740fc2 | ||
|
|
f731690a1d | ||
|
|
77c28f02b3 | ||
|
|
00c695b84c | ||
|
|
2422c84f47 | ||
|
|
680950fd0f | ||
|
|
c9aa747934 | ||
|
|
97dc6041e9 | ||
|
|
c11f8674f8 | ||
|
|
023eb7875f | ||
|
|
315da29189 | ||
|
|
4cda7d7332 | ||
|
|
8114e5e81b | ||
|
|
7ee921e18a | ||
|
|
8c3a35e468 | ||
|
|
abac0ce7b0 | ||
|
|
03e245b158 | ||
|
|
aeb46491c7 | ||
|
|
dbb680fbd2 | ||
|
|
8de9ba90a2 | ||
|
|
118bd876d2 | ||
|
|
adcf5c892e | ||
|
|
0c326cbd51 | ||
|
|
8205eab75b | ||
|
|
ce0062aac7 | ||
|
|
8e09161b4b | ||
|
|
bf2778f5d6 | ||
|
|
223591d242 | ||
|
|
79fb68ebfe | ||
|
|
9f1f1fece2 | ||
|
|
6343f7c79b | ||
|
|
bfea2a2a76 | ||
|
|
8d65000ec9 | ||
|
|
4456fa154a | ||
|
|
8af341e206 | ||
|
|
6beca84284 | ||
|
|
6dfebec6dd | ||
|
|
756f81db94 | ||
|
|
8e854494e0 | ||
|
|
d70817ce85 | ||
|
|
49df72dfcf | ||
|
|
89ebbb66d2 | ||
|
|
b463f5b8ca | ||
|
|
417c97acee | ||
|
|
4beb0eca42 | ||
|
|
428055c3da | ||
|
|
20d812c7a3 | ||
|
|
d9a2b6b6fa | ||
|
|
606b0d67da | ||
|
|
67736b0f01 | ||
|
|
fc672aa0a3 | ||
|
|
79643936e1 | ||
|
|
663d8f7082 | ||
|
|
d8c8151c2b | ||
|
|
040c0c3cf0 | ||
|
|
902c4a5691 | ||
|
|
b3cf8231d5 | ||
|
|
bfebbf80cb | ||
|
|
a6cb892a36 | ||
|
|
bd99ba67dc | ||
|
|
1f78a33fdb | ||
|
|
182c9c7e92 | ||
|
|
1785c7bde0 | ||
|
|
43f9a7cdf9 | ||
|
|
0fa3b93f6e | ||
|
|
6f825bd8e7 | ||
|
|
e4d1d0eef8 |
48
.agents/skills/dependabot-pr-rollup/SKILL.md
Normal file
48
.agents/skills/dependabot-pr-rollup/SKILL.md
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
name: dependabot-pr-rollup
|
||||||
|
description: Find open Dependabot PRs for the current GitHub repo, compare each PR head to its base branch, replay only the net dependency changes in a fresh worktree and branch, run npm validation, and optionally commit, push, and open a PR. Use when you want to batch or manually replicate active Dependabot updates.
|
||||||
|
license: MIT
|
||||||
|
compatibility: Requires git, git worktree, gh CLI auth, npm, and a GitHub repo with an origin remote.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Dependabot PR Rollup
|
||||||
|
|
||||||
|
## When to use
|
||||||
|
|
||||||
|
Use this skill when the user wants to:
|
||||||
|
- find all open Dependabot PRs in the current repo
|
||||||
|
- reproduce their net effect in one local branch
|
||||||
|
- validate the result with the repo's standard npm checks
|
||||||
|
- optionally commit, push, and open a PR
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
1. Inspect the current checkout state, but do not reuse a dirty worktree.
|
||||||
|
2. List open Dependabot PRs with `gh pr list --state open --author app/dependabot`.
|
||||||
|
3. For each PR, collect the title, base branch, head branch, changed files, and relevant diffs.
|
||||||
|
4. Compare each PR head against `origin/<base>` instead of trusting the PR title. Dependabot PRs can already be partially merged, superseded by newer versions, or have no remaining net effect.
|
||||||
|
5. Create a new worktree and branch from `origin/<base>`.
|
||||||
|
6. Reproduce only the remaining dependency changes in the new worktree.
|
||||||
|
- Inspect `package.json` before editing.
|
||||||
|
- Run `npm ci --ignore-scripts` before applying updates.
|
||||||
|
- Use `npm install ... --ignore-scripts` for direct dependency changes so `package-lock.json` stays in sync.
|
||||||
|
7. Run `npm run all`.
|
||||||
|
8. If requested, commit the changed source, lockfile, and generated artifacts, then push and open a PR.
|
||||||
|
|
||||||
|
## Repo-specific notes
|
||||||
|
|
||||||
|
- Use `gh` for GitHub operations.
|
||||||
|
- Keep the user's original checkout untouched by working in a separate worktree.
|
||||||
|
- In this repo, `npm run all` is the safest validation command because it runs build, check, package, and test.
|
||||||
|
- If dependency changes affect bundled output, include the regenerated `dist/` files.
|
||||||
|
|
||||||
|
## Report back
|
||||||
|
|
||||||
|
Always report:
|
||||||
|
- open Dependabot PRs found
|
||||||
|
- which PRs required no net changes
|
||||||
|
- new branch name
|
||||||
|
- new worktree path
|
||||||
|
- files changed
|
||||||
|
- `npm run all` result
|
||||||
|
- if applicable, commit SHA and PR URL
|
||||||
20
.editorconfig
Normal file
20
.editorconfig
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Check http://editorconfig.org for more information
|
||||||
|
# This is the main config file for this project:
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
end_of_line = lf
|
||||||
|
indent_style = space
|
||||||
|
insert_final_newline = true
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.{rs,py,pyi}]
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
[*.snap]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
max_line_length = 100
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
dist/
|
|
||||||
lib/
|
|
||||||
node_modules/
|
|
||||||
jest.config.js
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
{
|
|
||||||
"plugins": ["jest", "@typescript-eslint"],
|
|
||||||
"extends": ["plugin:github/recommended"],
|
|
||||||
"parser": "@typescript-eslint/parser",
|
|
||||||
"parserOptions": {
|
|
||||||
"ecmaVersion": 9,
|
|
||||||
"sourceType": "module",
|
|
||||||
"project": "./tsconfig.json"
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"no-shadow": "off",
|
|
||||||
"@typescript-eslint/no-shadow": ["error"],
|
|
||||||
"i18n-text/no-en": "off",
|
|
||||||
"eslint-comments/no-use": "off",
|
|
||||||
"import/no-namespace": "off",
|
|
||||||
"no-unused-vars": "off",
|
|
||||||
"@typescript-eslint/no-unused-vars": "error",
|
|
||||||
"@typescript-eslint/explicit-member-accessibility": ["error", {"accessibility": "no-public"}],
|
|
||||||
"@typescript-eslint/no-require-imports": "error",
|
|
||||||
"@typescript-eslint/array-type": "error",
|
|
||||||
"@typescript-eslint/await-thenable": "error",
|
|
||||||
"@typescript-eslint/ban-ts-comment": "error",
|
|
||||||
"camelcase": "off",
|
|
||||||
"@typescript-eslint/consistent-type-assertions": "error",
|
|
||||||
"@typescript-eslint/explicit-function-return-type": ["error", {"allowExpressions": true}],
|
|
||||||
"@typescript-eslint/func-call-spacing": ["error", "never"],
|
|
||||||
"@typescript-eslint/no-array-constructor": "error",
|
|
||||||
"@typescript-eslint/no-empty-interface": "error",
|
|
||||||
"@typescript-eslint/no-explicit-any": "error",
|
|
||||||
"@typescript-eslint/no-extraneous-class": "error",
|
|
||||||
"@typescript-eslint/no-for-in-array": "error",
|
|
||||||
"@typescript-eslint/no-inferrable-types": "error",
|
|
||||||
"@typescript-eslint/no-misused-new": "error",
|
|
||||||
"@typescript-eslint/no-namespace": "error",
|
|
||||||
"@typescript-eslint/no-non-null-assertion": "warn",
|
|
||||||
"@typescript-eslint/no-unnecessary-qualifier": "error",
|
|
||||||
"@typescript-eslint/no-unnecessary-type-assertion": "error",
|
|
||||||
"@typescript-eslint/no-useless-constructor": "error",
|
|
||||||
"@typescript-eslint/no-var-requires": "error",
|
|
||||||
"@typescript-eslint/prefer-for-of": "warn",
|
|
||||||
"@typescript-eslint/prefer-function-type": "warn",
|
|
||||||
"@typescript-eslint/prefer-includes": "error",
|
|
||||||
"@typescript-eslint/prefer-string-starts-ends-with": "error",
|
|
||||||
"@typescript-eslint/promise-function-async": "error",
|
|
||||||
"@typescript-eslint/require-array-sort-compare": "error",
|
|
||||||
"@typescript-eslint/restrict-plus-operands": "error",
|
|
||||||
"semi": "off",
|
|
||||||
"@typescript-eslint/semi": ["error", "never"],
|
|
||||||
"@typescript-eslint/type-annotation-spacing": "error",
|
|
||||||
"@typescript-eslint/unbound-method": "error"
|
|
||||||
},
|
|
||||||
"env": {
|
|
||||||
"node": true,
|
|
||||||
"es6": true,
|
|
||||||
"jest/globals": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
0
.git-blame-ignore-revs
Normal file
0
.git-blame-ignore-revs
Normal file
13
.github/actionlint.yaml
vendored
Normal file
13
.github/actionlint.yaml
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
self-hosted-runner:
|
||||||
|
# Custom labels of self-hosted or large GitHub hosted runners
|
||||||
|
# so that actionlint knows that they are not a typo
|
||||||
|
labels:
|
||||||
|
- selfhosted-ubuntu-arm64
|
||||||
|
# Configuration variables in array of strings defined in your repository or
|
||||||
|
# organization. `null` means disabling configuration variables check.
|
||||||
|
# Empty array means no configuration variable is allowed.
|
||||||
|
config-variables: null
|
||||||
|
paths:
|
||||||
|
.github/workflows/test.yml:
|
||||||
|
ignore:
|
||||||
|
- 'invalid runner name.+'
|
||||||
4
.github/dependabot.yml
vendored
4
.github/dependabot.yml
vendored
@@ -4,8 +4,12 @@ updates:
|
|||||||
directory: /
|
directory: /
|
||||||
schedule:
|
schedule:
|
||||||
interval: daily
|
interval: daily
|
||||||
|
cooldown:
|
||||||
|
default-days: 7
|
||||||
|
|
||||||
- package-ecosystem: npm
|
- package-ecosystem: npm
|
||||||
directory: /
|
directory: /
|
||||||
schedule:
|
schedule:
|
||||||
interval: daily
|
interval: daily
|
||||||
|
cooldown:
|
||||||
|
default-days: 7
|
||||||
|
|||||||
8
.github/python.json
vendored
8
.github/python.json
vendored
@@ -4,13 +4,13 @@
|
|||||||
"owner": "python",
|
"owner": "python",
|
||||||
"pattern": [
|
"pattern": [
|
||||||
{
|
{
|
||||||
"regexp": "^\\s*File\\s\\\"(.*)\\\",\\sline\\s(\\d+),\\sin\\s(.*)$",
|
|
||||||
"file": 1,
|
"file": 1,
|
||||||
"line": 2
|
"line": 2,
|
||||||
|
"regexp": "^\\s*File\\s\\\"(.*)\\\",\\sline\\s(\\d+),\\sin\\s(.*)$"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"regexp": "^\\s*raise\\s(.*)\\(\\'(.*)\\'\\)$",
|
"message": 2,
|
||||||
"message": 2
|
"regexp": "^\\s*raise\\s(.*)\\(\\'(.*)\\'\\)$"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
22
.github/release-drafter.yml
vendored
22
.github/release-drafter.yml
vendored
@@ -1,5 +1,5 @@
|
|||||||
name-template: 'v$RESOLVED_VERSION 🌈'
|
name-template: "v$RESOLVED_VERSION 🌈"
|
||||||
tag-template: 'v$RESOLVED_VERSION'
|
tag-template: "v$RESOLVED_VERSION"
|
||||||
categories:
|
categories:
|
||||||
- title: "🚨 Breaking changes"
|
- title: "🚨 Breaking changes"
|
||||||
labels:
|
labels:
|
||||||
@@ -19,29 +19,29 @@ categories:
|
|||||||
labels:
|
labels:
|
||||||
- "maintenance"
|
- "maintenance"
|
||||||
- "ci"
|
- "ci"
|
||||||
- "default-version-update"
|
- "update-known-checksums"
|
||||||
- title: "📚 Documentation"
|
- title: "📚 Documentation"
|
||||||
labels:
|
labels:
|
||||||
- "documentation"
|
- "documentation"
|
||||||
- title: "⬆️ Dependency updates"
|
- title: "⬆️ Dependency updates"
|
||||||
labels:
|
labels:
|
||||||
- "dependencies"
|
- "dependencies"
|
||||||
change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
|
change-template: "- $TITLE @$AUTHOR (#$NUMBER)"
|
||||||
change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks.
|
change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks.
|
||||||
version-resolver:
|
version-resolver:
|
||||||
major:
|
major:
|
||||||
labels:
|
labels:
|
||||||
- 'major'
|
- "major"
|
||||||
- 'breaking-change'
|
- "breaking-change"
|
||||||
minor:
|
minor:
|
||||||
labels:
|
labels:
|
||||||
- 'minor'
|
- "minor"
|
||||||
- 'new-feature'
|
- "new-feature"
|
||||||
- 'enhancement'
|
- "enhancement"
|
||||||
patch:
|
patch:
|
||||||
labels:
|
labels:
|
||||||
- 'patch'
|
- "patch"
|
||||||
- 'bugfix'
|
- "bugfix"
|
||||||
- "default-version-update"
|
- "default-version-update"
|
||||||
default: patch
|
default: patch
|
||||||
template: |
|
template: |
|
||||||
|
|||||||
35
.github/scripts/check-all-tests-passed-needs.ts
vendored
Normal file
35
.github/scripts/check-all-tests-passed-needs.ts
vendored
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import * as fs from "node:fs";
|
||||||
|
import * as yaml from "js-yaml";
|
||||||
|
|
||||||
|
interface WorkflowJob {
|
||||||
|
needs?: string[];
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Workflow {
|
||||||
|
jobs: Record<string, WorkflowJob>;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
const workflow = yaml.load(
|
||||||
|
fs.readFileSync("../workflows/test.yml", "utf8"),
|
||||||
|
) as Workflow;
|
||||||
|
const jobs = Object.keys(workflow.jobs);
|
||||||
|
const allTestsPassed = workflow.jobs["all-tests-passed"];
|
||||||
|
const needs: string[] = allTestsPassed.needs || [];
|
||||||
|
|
||||||
|
const expectedNeeds = jobs.filter((j) => j !== "all-tests-passed");
|
||||||
|
const missing = expectedNeeds.filter((j) => !needs.includes(j));
|
||||||
|
|
||||||
|
if (missing.length > 0) {
|
||||||
|
console.error(
|
||||||
|
`Missing jobs in all-tests-passed needs: ${missing.join(", ")}`,
|
||||||
|
);
|
||||||
|
console.info(
|
||||||
|
"Please add the missing jobs to the needs section of all-tests-passed in test.yml.",
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
console.log(
|
||||||
|
"All jobs in test.yml are in the needs section of all-tests-passed.",
|
||||||
|
);
|
||||||
53
.github/workflows/check-dist.yml
vendored
53
.github/workflows/check-dist.yml
vendored
@@ -1,53 +0,0 @@
|
|||||||
# `dist/index.js` is a special file in Actions.
|
|
||||||
# When you reference an action with `uses:` in a workflow,
|
|
||||||
# `index.js` is the code that will run.
|
|
||||||
# For our project, we generate this file through a build process from other source files.
|
|
||||||
# We need to make sure the checked-in `index.js` actually matches what we expect it to be.
|
|
||||||
name: Check dist/
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
paths-ignore:
|
|
||||||
- '**.md'
|
|
||||||
pull_request:
|
|
||||||
paths-ignore:
|
|
||||||
- '**.md'
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
check-dist:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Set up Node.js 20
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: 20
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: npm ci
|
|
||||||
|
|
||||||
- name: Rebuild the dist/ directory
|
|
||||||
run: |
|
|
||||||
npm run build
|
|
||||||
npm run package
|
|
||||||
|
|
||||||
- name: Compare the expected and actual dist/ directories
|
|
||||||
run: |
|
|
||||||
if [ "$(git diff --ignore-space-at-eol dist/ | wc -l)" -gt "0" ]; then
|
|
||||||
echo "Detected uncommitted changes after build. See status below:"
|
|
||||||
git diff --text -v
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
id: diff
|
|
||||||
|
|
||||||
# If index.js was different than expected, upload the expected version as an artifact
|
|
||||||
- uses: actions/upload-artifact@v4
|
|
||||||
if: ${{ failure() && steps.diff.conclusion == 'failure' }}
|
|
||||||
with:
|
|
||||||
name: dist
|
|
||||||
path: dist/
|
|
||||||
23
.github/workflows/codeql-analysis.yml
vendored
23
.github/workflows/codeql-analysis.yml
vendored
@@ -12,13 +12,16 @@
|
|||||||
name: "CodeQL"
|
name: "CodeQL"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches: [ main ]
|
branches:
|
||||||
|
- main
|
||||||
pull_request:
|
pull_request:
|
||||||
# The branches below must be a subset of the branches above
|
# The branches below must be a subset of the branches above
|
||||||
branches: [ main ]
|
branches:
|
||||||
schedule:
|
- main
|
||||||
- cron: '31 7 * * 3'
|
|
||||||
|
permissions: {}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
analyze:
|
analyze:
|
||||||
@@ -32,17 +35,19 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
language: [ 'TypeScript' ]
|
language: ["TypeScript"]
|
||||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||||
# Learn more about CodeQL language support at https://git.io/codeql-language-support
|
# Learn more about CodeQL language support at https://git.io/codeql-language-support
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v3
|
uses: github/codeql-action/init@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
source-root: src
|
source-root: src
|
||||||
@@ -54,7 +59,7 @@ jobs:
|
|||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
# If this step fails, then you should remove it and run the build manually (see below)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v3
|
uses: github/codeql-action/autobuild@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
# 📚 https://git.io/JvXDl
|
# 📚 https://git.io/JvXDl
|
||||||
@@ -68,4 +73,4 @@ jobs:
|
|||||||
# make release
|
# make release
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v3
|
uses: github/codeql-action/analyze@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2
|
||||||
|
|||||||
11
.github/workflows/release-drafter.yml
vendored
11
.github/workflows/release-drafter.yml
vendored
@@ -3,17 +3,22 @@ name: Release Drafter
|
|||||||
|
|
||||||
# yamllint disable-line rule:truthy
|
# yamllint disable-line rule:truthy
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
workflow_dispatch:
|
|
||||||
|
permissions: {}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
update_release_draft:
|
update_release_draft:
|
||||||
name: ✏️ Draft release
|
name: ✏️ Draft release
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04-arm
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: read
|
||||||
steps:
|
steps:
|
||||||
- name: 🚀 Run Release Drafter
|
- name: 🚀 Run Release Drafter
|
||||||
uses: release-drafter/release-drafter@v6.0.0
|
uses: release-drafter/release-drafter@6db134d15f3909ccc9eefd369f02bd1e9cffdf97 # v6.2.0
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
49
.github/workflows/test-cache-windows.yml
vendored
49
.github/workflows/test-cache-windows.yml
vendored
@@ -1,49 +0,0 @@
|
|||||||
name: 'test-cache-windows'
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test-setup-cache:
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
os: [windows-latest]
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Setup with cache
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
enable-cache: true
|
|
||||||
cache-suffix: ${{ github.run_id }}-${{ github.run_attempt }}
|
|
||||||
- run: uv sync
|
|
||||||
working-directory: __tests__\fixtures\uv-project
|
|
||||||
test-restore-cache:
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
os: [windows-latest]
|
|
||||||
needs: test-setup-cache
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Restore with cache
|
|
||||||
id: restore
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
enable-cache: true
|
|
||||||
cache-suffix: ${{ github.run_id }}-${{ github.run_attempt }}
|
|
||||||
- name: Cache was hit
|
|
||||||
run: |
|
|
||||||
if ($env:CACHE_HIT -ne "true") {
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
env:
|
|
||||||
CACHE_HIT: ${{ steps.restore.outputs.cache-hit }}
|
|
||||||
- run: uv sync
|
|
||||||
working-directory: __tests__\fixtures\uv-project
|
|
||||||
83
.github/workflows/test-cache.yml
vendored
83
.github/workflows/test-cache.yml
vendored
@@ -1,83 +0,0 @@
|
|||||||
name: 'test-cache'
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test-setup-cache:
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
os: [ubuntu-latest, macos-latest, macos-14]
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Setup with cache
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
enable-cache: true
|
|
||||||
cache-suffix: ${{ github.run_id }}-${{ github.run_attempt }}
|
|
||||||
- run: uv sync
|
|
||||||
working-directory: __tests__/fixtures/uv-project
|
|
||||||
test-restore-cache:
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
os: [ubuntu-latest, macos-latest, macos-14]
|
|
||||||
needs: test-setup-cache
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Restore with cache
|
|
||||||
id: restore
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
enable-cache: true
|
|
||||||
cache-suffix: ${{ github.run_id }}-${{ github.run_attempt }}
|
|
||||||
- name: Cache was hit
|
|
||||||
run: |
|
|
||||||
if [ "$CACHE_HIT" != "true" ]; then
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
env:
|
|
||||||
CACHE_HIT: ${{ steps.restore.outputs.cache-hit }}
|
|
||||||
- run: uv sync
|
|
||||||
working-directory: __tests__/fixtures/uv-project
|
|
||||||
|
|
||||||
test-setup-cache-local:
|
|
||||||
runs-on: oracle-aarch64
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Setup with cache
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
enable-cache: true
|
|
||||||
cache-suffix: ${{ github.run_id }}-${{ github.run_attempt }}
|
|
||||||
cache-local-path: /tmp/uv-cache
|
|
||||||
- run: uv sync
|
|
||||||
working-directory: __tests__/fixtures/uv-project
|
|
||||||
test-restore-cache-local:
|
|
||||||
runs-on: oracle-aarch64
|
|
||||||
needs: test-setup-cache-local
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Restore with cache
|
|
||||||
id: restore
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
enable-cache: true
|
|
||||||
cache-suffix: ${{ github.run_id }}-${{ github.run_attempt }}
|
|
||||||
cache-local-path: /tmp/uv-cache
|
|
||||||
- name: Cache was hit
|
|
||||||
run: |
|
|
||||||
if [ "$CACHE_HIT" != "true" ]; then
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
env:
|
|
||||||
CACHE_HIT: ${{ steps.restore.outputs.cache-hit }}
|
|
||||||
- run: uv sync
|
|
||||||
working-directory: __tests__/fixtures/uv-project
|
|
||||||
29
.github/workflows/test-windows.yml
vendored
29
.github/workflows/test-windows.yml
vendored
@@ -1,29 +0,0 @@
|
|||||||
name: 'test-windows'
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test-default-version:
|
|
||||||
runs-on: windows-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Should not be on path
|
|
||||||
run: |
|
|
||||||
if (!(Get-Command -Name "uv" -ErrorAction SilentlyContinue)) {
|
|
||||||
exit 0
|
|
||||||
} else {
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
- name: Setup uv
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- run: uv sync
|
|
||||||
working-directory: __tests__\fixtures\uv-project
|
|
||||||
1086
.github/workflows/test.yml
vendored
1086
.github/workflows/test.yml
vendored
File diff suppressed because it is too large
Load Diff
26
.github/workflows/update-default-version.yml
vendored
26
.github/workflows/update-default-version.yml
vendored
@@ -1,26 +0,0 @@
|
|||||||
name: 'Update default version and checksums'
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: '20'
|
|
||||||
- name: Update default version and checksums
|
|
||||||
id: update-default-version
|
|
||||||
run: node dist/update-default-version/index.js src/download/checksum/known-checksums.ts action.yml ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- run: npm install && npm run all
|
|
||||||
- name: Create Pull Request
|
|
||||||
uses: peter-evans/create-pull-request@4320041ed380b20e97d388d56a7fb4f9b8c20e79 # v7.0.0
|
|
||||||
with:
|
|
||||||
commit-message: "chore: update checksums"
|
|
||||||
title: "chore: update default version to ${{ steps.update-default-version.outputs.latest-version }}"
|
|
||||||
body: "chore: update default version to ${{ steps.update-default-version.outputs.latest-version }}"
|
|
||||||
base: main
|
|
||||||
labels: "automated-pr,default-version-update"
|
|
||||||
branch: update-default-version-pr
|
|
||||||
delete-branch: true
|
|
||||||
69
.github/workflows/update-known-checksums.yml
vendored
Normal file
69
.github/workflows/update-known-checksums.yml
vendored
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
name: "Update known checksums"
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 4 * * *" # Run every day at 4am UTC
|
||||||
|
repository_dispatch:
|
||||||
|
types: [ pypi_release ]
|
||||||
|
|
||||||
|
permissions: {}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-24.04-arm
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
with:
|
||||||
|
persist-credentials: true
|
||||||
|
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||||
|
with:
|
||||||
|
node-version-file: .nvmrc
|
||||||
|
cache: npm
|
||||||
|
- name: Update known checksums
|
||||||
|
id: update-known-checksums
|
||||||
|
run:
|
||||||
|
node dist/update-known-checksums/index.cjs
|
||||||
|
src/download/checksum/known-checksums.ts
|
||||||
|
- name: Check for changes
|
||||||
|
id: changes-exist
|
||||||
|
run: |
|
||||||
|
git status --porcelain
|
||||||
|
if [ -n "$(git status --porcelain)" ]; then
|
||||||
|
echo "changes-exist=true" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "changes-exist=false" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
- name: Compile changes
|
||||||
|
if: ${{ steps.changes-exist.outputs.changes-exist == 'true' }}
|
||||||
|
run: npm ci --ignore-scripts && npm run all
|
||||||
|
- name: Commit and push changes
|
||||||
|
if: ${{ steps.changes-exist.outputs.changes-exist == 'true' }}
|
||||||
|
id: commit-and-push
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
git config user.name "$GITHUB_ACTOR"
|
||||||
|
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
|
||||||
|
git add .
|
||||||
|
git commit -m "chore: update known checksums for $LATEST_VERSION"
|
||||||
|
git push origin HEAD:refs/heads/main
|
||||||
|
env:
|
||||||
|
LATEST_VERSION: ${{ steps.update-known-checksums.outputs.latest-version }}
|
||||||
|
|
||||||
|
- name: Create Pull Request
|
||||||
|
if: ${{ steps.changes-exist.outputs.changes-exist == 'true' && steps.commit-and-push.outcome != 'success' }}
|
||||||
|
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
||||||
|
with:
|
||||||
|
commit-message: "chore: update known checksums"
|
||||||
|
title:
|
||||||
|
"chore: update known checksums for ${{
|
||||||
|
steps.update-known-checksums.outputs.latest-version }}"
|
||||||
|
body:
|
||||||
|
"chore: update known checksums for ${{
|
||||||
|
steps.update-known-checksums.outputs.latest-version }}"
|
||||||
|
base: main
|
||||||
|
labels: "automated-pr,update-known-checksums"
|
||||||
|
branch: update-known-checksums-pr
|
||||||
|
delete-branch: true
|
||||||
46
.github/workflows/update-major-minor-tags.yml
vendored
46
.github/workflows/update-major-minor-tags.yml
vendored
@@ -1,19 +1,51 @@
|
|||||||
---
|
---
|
||||||
name: Update Major Minor Tags
|
name: Update Major Minor Tags
|
||||||
|
|
||||||
# yamllint disable-line rule:truthy
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches-ignore:
|
branches-ignore:
|
||||||
- '**'
|
- "**"
|
||||||
tags:
|
tags:
|
||||||
- 'v*.*.*'
|
- "v*.*.*"
|
||||||
|
|
||||||
|
permissions: {}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
update_major_minor_tags:
|
update_major_minor_tags:
|
||||||
name: Make sure major and minor tags are up to date on a patch release
|
name: Make sure major and minor tags are up to date on a patch release
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04-arm
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
- name: Run Update semver
|
with:
|
||||||
uses: haya14busa/action-update-semver@v1.2.1
|
persist-credentials: true # needed for git push below
|
||||||
|
- name: Update Major Minor Tags
|
||||||
|
run: |
|
||||||
|
set -x
|
||||||
|
|
||||||
|
cd "${GITHUB_WORKSPACE}" || exit
|
||||||
|
|
||||||
|
# Set up variables.
|
||||||
|
TAG="${GITHUB_REF#refs/tags/}" # v1.2.3
|
||||||
|
MINOR="${TAG%.*}" # v1.2
|
||||||
|
MAJOR="${MINOR%.*}" # v1
|
||||||
|
|
||||||
|
if [ "${GITHUB_REF}" = "${TAG}" ]; then
|
||||||
|
echo "This workflow is not triggered by tag push: GITHUB_REF=${GITHUB_REF}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
MESSAGE="Release ${TAG}"
|
||||||
|
|
||||||
|
# Set up Git.
|
||||||
|
git config user.name "${GITHUB_ACTOR}"
|
||||||
|
git config user.email "${GITHUB_ACTOR}@users.noreply.github.com"
|
||||||
|
|
||||||
|
# Update MAJOR/MINOR tag
|
||||||
|
git tag -fa "${MAJOR}" -m "${MESSAGE}"
|
||||||
|
git tag -fa "${MINOR}" -m "${MESSAGE}"
|
||||||
|
|
||||||
|
# Push
|
||||||
|
git push --force origin "${MINOR}"
|
||||||
|
git push --force origin "${MAJOR}"
|
||||||
|
|||||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -97,3 +97,9 @@ Thumbs.db
|
|||||||
# Ignore built ts files
|
# Ignore built ts files
|
||||||
__tests__/runner/*
|
__tests__/runner/*
|
||||||
lib/**/*
|
lib/**/*
|
||||||
|
|
||||||
|
# Idea IDEs (PyCharm, WebStorm, IntelliJ, etc)
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Compiled scripts
|
||||||
|
.github/scripts/*.js
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
dist/
|
|
||||||
lib/
|
|
||||||
node_modules/
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"printWidth": 80,
|
|
||||||
"tabWidth": 2,
|
|
||||||
"useTabs": false,
|
|
||||||
"semi": false,
|
|
||||||
"singleQuote": true,
|
|
||||||
"trailingComma": "none",
|
|
||||||
"bracketSpacing": false,
|
|
||||||
"arrowParens": "avoid"
|
|
||||||
}
|
|
||||||
3
.vscode/extensions.json
vendored
Normal file
3
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"recommendations": ["biomejs.biome"]
|
||||||
|
}
|
||||||
16
.vscode/settings.json
vendored
Normal file
16
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.action.useSortedAttributes.biome": "explicit",
|
||||||
|
"source.action.useSortedKeys.biome": "explicit",
|
||||||
|
"source.fixAll.biome": "explicit"
|
||||||
|
},
|
||||||
|
"editor.defaultFormatter": "biomejs.biome",
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"explorer.excludeGitIgnore": false,
|
||||||
|
"search.defaultViewMode": "list",
|
||||||
|
"search.exclude": {
|
||||||
|
"**/node_modules": true
|
||||||
|
},
|
||||||
|
"typescript.enablePromptUseWorkspaceTsdk": true,
|
||||||
|
"typescript.tsdk": "node_modules/typescript/lib"
|
||||||
|
}
|
||||||
18
AGENTS.md
Normal file
18
AGENTS.md
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# setup-uv agent notes
|
||||||
|
|
||||||
|
This repository is a TypeScript-based GitHub Action for installing `uv` in GitHub Actions workflows. It also supports restoring/saving the `uv` cache and optional managed-Python caching.
|
||||||
|
|
||||||
|
- The published action runs the committed bundles in `dist/`, not the TypeScript in `src/`. After any code change, run `npm run package` and commit the resulting `dist/` updates.
|
||||||
|
- Standard local validation is:
|
||||||
|
1. `npm ci --ignore-scripts`
|
||||||
|
2. `npm run all`
|
||||||
|
- `npm run check` uses Biome (not ESLint/Prettier) and rewrites files in place.
|
||||||
|
- User-facing changes are usually multi-file changes. If you add or change inputs, outputs, or behavior, update `action.yml`, the implementation in `src/`, tests in `__tests__/`, relevant docs/README, and then re-package.
|
||||||
|
- The easiest areas to regress are version resolution and caching. When touching them, add or update tests for precedence, cache invalidation, and cross-platform path behavior.
|
||||||
|
- Workflow edits have extra CI-only checks (`actionlint` and `zizmor`); `npm run all` does not cover them.
|
||||||
|
- Source is authored with bundler-friendly TypeScript, but published action artifacts in `dist/` are bundled as CommonJS for maximum GitHub Actions runtime compatibility with `@actions/*` dependencies.
|
||||||
|
- Keep these concerns separate when changing module formats:
|
||||||
|
- `src/` and tests may use modern ESM-friendly TypeScript patterns.
|
||||||
|
- `dist/` should prioritize runtime reliability over format purity.
|
||||||
|
- Do not switch published bundles to ESM without validating the actual committed artifacts under the target Node runtime.
|
||||||
|
- Before finishing, make sure validation does not leave generated or formatting-only diffs behind.
|
||||||
376
README.md
376
README.md
@@ -2,175 +2,233 @@
|
|||||||
|
|
||||||
Set up your GitHub Actions workflow with a specific version of [uv](https://docs.astral.sh/uv/).
|
Set up your GitHub Actions workflow with a specific version of [uv](https://docs.astral.sh/uv/).
|
||||||
|
|
||||||
* Install a version of uv and add it to the path
|
- Install a version of uv and add it to PATH
|
||||||
* Cache the installed version of uv to speed up consecutive runs on self-hosted runners
|
- Cache the installed version of uv to speed up consecutive runs on self-hosted runners
|
||||||
* Register problem matchers for error output
|
- Register problem matchers for error output
|
||||||
* Optional: Cache the uv cache
|
- (Optional) Persist the uv's cache in the GitHub Actions Cache
|
||||||
* Optional: Verify the checksum of the downloaded uv executable
|
- (Optional) Verify the checksum of the downloaded uv executable
|
||||||
|
|
||||||
## Contents
|
## Contents
|
||||||
|
|
||||||
* [Usage](#usage)
|
- [Usage](#usage)
|
||||||
* [Install specific version](#install-specific-version)
|
- [Install a required-version or latest (default)](#install-a-required-version-or-latest-default)
|
||||||
* [Install latest version](#install-latest-version)
|
- [Inputs](#inputs)
|
||||||
* [Validate checksum](#validate-checksum)
|
- [Outputs](#outputs)
|
||||||
* [Enable Caching](#enable-caching)
|
- [Python version](#python-version)
|
||||||
* [Local cache path](#local-cache-path)
|
- [Working directory](#working-directory)
|
||||||
* [Cache dependency glob](#cache-dependency-glob)
|
- [Advanced Configuration](#advanced-configuration)
|
||||||
* [API rate limit](#api-rate-limit)
|
- [How it works](#how-it-works)
|
||||||
* [How it works](#how-it-works)
|
- [FAQ](#faq)
|
||||||
* [FAQ](#faq)
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Example workflow in a real world project can be found [here](https://github.com/eifinger/hass-weenect/blob/main/.github/workflows/ci.yml)
|
### Install a required-version or latest (default)
|
||||||
|
|
||||||
### Install specific version
|
|
||||||
|
|
||||||
You can also specify a specific version of uv
|
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- name: Install a specific version
|
- name: Install the latest version of uv
|
||||||
uses: eifinger/setup-uv@v1
|
uses: astral-sh/setup-uv@v7
|
||||||
|
```
|
||||||
|
|
||||||
|
If you do not specify a version, this action will look for a [required-version](https://docs.astral.sh/uv/reference/settings/#required-version)
|
||||||
|
in a `uv.toml` or `pyproject.toml` file in the repository root. If none is found, the latest version will be installed.
|
||||||
|
|
||||||
|
For an example workflow, see
|
||||||
|
[here](https://github.com/charliermarsh/autobot/blob/e42c66659bf97b90ca9ff305a19cc99952d0d43f/.github/workflows/ci.yaml).
|
||||||
|
|
||||||
|
### Inputs
|
||||||
|
|
||||||
|
All inputs and their defaults.
|
||||||
|
Have a look under [Advanced Configuration](#advanced-configuration) for detailed documentation on most of them.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Install uv with all available options
|
||||||
|
uses: astral-sh/setup-uv@v7
|
||||||
with:
|
with:
|
||||||
version: '0.3.0'
|
# The version of uv to install (default: searches for version in config files, then latest)
|
||||||
|
version: ""
|
||||||
|
|
||||||
|
# Path to a file containing the version of uv to install (default: searches uv.toml then pyproject.toml)
|
||||||
|
version-file: ""
|
||||||
|
|
||||||
|
# Resolution strategy when resolving version ranges: 'highest' or 'lowest'
|
||||||
|
resolution-strategy: "highest"
|
||||||
|
|
||||||
|
# The version of Python to set UV_PYTHON to
|
||||||
|
python-version: ""
|
||||||
|
|
||||||
|
# Use uv venv to activate a venv ready to be used by later steps
|
||||||
|
activate-environment: "false"
|
||||||
|
|
||||||
|
# Custom path for the virtual environment when using activate-environment (default: .venv in the working directory)
|
||||||
|
venv-path: ""
|
||||||
|
|
||||||
|
# The directory to execute all commands in and look for files such as pyproject.toml
|
||||||
|
working-directory: ""
|
||||||
|
|
||||||
|
# The checksum of the uv version to install
|
||||||
|
checksum: ""
|
||||||
|
|
||||||
|
# Used when downloading uv from GitHub releases
|
||||||
|
github-token: ${{ github.token }}
|
||||||
|
|
||||||
|
# Enable uploading of the uv cache: true, false, or auto (enabled on GitHub-hosted runners, disabled on self-hosted runners)
|
||||||
|
enable-cache: "auto"
|
||||||
|
|
||||||
|
# Glob pattern to match files relative to the repository root to control the cache
|
||||||
|
cache-dependency-glob: |
|
||||||
|
**/*requirements*.txt
|
||||||
|
**/*requirements*.in
|
||||||
|
**/*constraints*.txt
|
||||||
|
**/*constraints*.in
|
||||||
|
**/pyproject.toml
|
||||||
|
**/uv.lock
|
||||||
|
**/*.py.lock
|
||||||
|
|
||||||
|
# Whether to restore the cache if found
|
||||||
|
restore-cache: "true"
|
||||||
|
|
||||||
|
# Whether to save the cache after the run
|
||||||
|
save-cache: "true"
|
||||||
|
|
||||||
|
# Suffix for the cache key
|
||||||
|
cache-suffix: ""
|
||||||
|
|
||||||
|
# Local path to store the cache (default: "" - uses system temp directory)
|
||||||
|
cache-local-path: ""
|
||||||
|
|
||||||
|
# Prune cache before saving
|
||||||
|
prune-cache: "true"
|
||||||
|
|
||||||
|
# Upload managed Python installations to the GitHub Actions cache
|
||||||
|
cache-python: "false"
|
||||||
|
|
||||||
|
# Ignore when nothing is found to cache
|
||||||
|
ignore-nothing-to-cache: "false"
|
||||||
|
|
||||||
|
# Ignore when the working directory is empty
|
||||||
|
ignore-empty-workdir: "false"
|
||||||
|
|
||||||
|
# Custom path to set UV_TOOL_DIR to
|
||||||
|
tool-dir: ""
|
||||||
|
|
||||||
|
# Custom path to set UV_TOOL_BIN_DIR to
|
||||||
|
tool-bin-dir: ""
|
||||||
|
|
||||||
|
# URL to a custom manifest file in the astral-sh/versions format
|
||||||
|
manifest-file: ""
|
||||||
|
|
||||||
|
# Add problem matchers
|
||||||
|
add-problem-matchers: "true"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Install latest version
|
### Outputs
|
||||||
|
|
||||||
By default this action installs the version defined as `default` in `action.yml`.
|
- `uv-version`: The installed uv version. Useful when using latest.
|
||||||
This gets automatically updated in a new release of this action when a new version of uv is released.
|
- `uv-path`: The path to the installed uv binary.
|
||||||
If you don't want to wait for a new release of this action you can use use `version: latest`.
|
- `uvx-path`: The path to the installed uvx binary.
|
||||||
|
- `cache-hit`: A boolean value to indicate a cache entry was found.
|
||||||
|
- `venv`: Path to the activated venv if activate-environment is true.
|
||||||
|
- `python-version`: The Python version that was set.
|
||||||
|
- `python-cache-hit`: A boolean value to indicate the Python cache entry was found.
|
||||||
|
|
||||||
> [!WARNING]
|
### Python version
|
||||||
> Using the `latest` version means that the uv executable gets downloaded every single time instead of loaded from the tools cache.
|
|
||||||
> This can take up to 20s depending on the download speed.
|
You can use the input `python-version` to set the environment variable `UV_PYTHON` for the rest of your workflow
|
||||||
> This does not affect the uv cache.
|
|
||||||
|
This will override any python version specifications in `pyproject.toml` and `.python-version`
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- name: Install a specific version
|
- name: Install the latest version of uv and set the python version to 3.13t
|
||||||
uses: eifinger/setup-uv@v1
|
uses: astral-sh/setup-uv@v7
|
||||||
with:
|
with:
|
||||||
version: 'latest'
|
python-version: 3.13t
|
||||||
|
- run: uv pip install --python=3.13t pip
|
||||||
```
|
```
|
||||||
|
|
||||||
### Validate checksum
|
You can combine this with a matrix to test multiple Python versions:
|
||||||
|
|
||||||
You can also specify a checksum to validate the downloaded file.
|
|
||||||
Checksums up to the default version are automatically verified by this action.
|
|
||||||
The sha265 hashes can be found on the [releases page](https://github.com/astral-sh/uv/releases)
|
|
||||||
of the uv repo.
|
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- name: Install a specific version and validate the checksum
|
jobs:
|
||||||
uses: eifinger/setup-uv@v1
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
- name: Install the latest version of uv and set the python version
|
||||||
|
uses: astral-sh/setup-uv@v7
|
||||||
with:
|
with:
|
||||||
version: '0.3.1'
|
python-version: ${{ matrix.python-version }}
|
||||||
checksum: 'e11b01402ab645392c7ad6044db63d37e4fd1e745e015306993b07695ea5f9f8'
|
- name: Test with python ${{ matrix.python-version }}
|
||||||
|
run: uv run --frozen pytest
|
||||||
```
|
```
|
||||||
|
|
||||||
### Enable caching
|
### Working directory
|
||||||
|
|
||||||
If you enable caching the [uv cache](https://docs.astral.sh/uv/concepts/cache/) will
|
You can set the working directory with the `working-directory` input.
|
||||||
be cached to the GitHub Actions Cache. This can speed up runs which can reuse the cache
|
This controls where we look for `pyproject.toml`, `uv.toml` and `.python-version` files
|
||||||
by several minutes. The cache will always be reused on self-hosted runners.
|
which are used to determine the version of uv and python to install.
|
||||||
|
|
||||||
You can optionally define a custom cache key suffix.
|
It also controls where [the venv gets created](#activate-environment), unless `venv-path` is set.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- name: Enable caching and define a custom cache key suffix
|
- name: Install uv based on the config files in the working-directory
|
||||||
id: setup-uv
|
uses: astral-sh/setup-uv@v7
|
||||||
uses: eifinger/setup-uv@v1
|
|
||||||
with:
|
with:
|
||||||
enable-cache: true
|
working-directory: my/subproject/dir
|
||||||
cache-suffix: 'optional-suffix'
|
|
||||||
```
|
```
|
||||||
|
|
||||||
When the cache was successfully restored the output `cache-hit` will be set to `true` and you can use it in subsequent steps.
|
## Advanced Configuration
|
||||||
For the example above you can use it like this:
|
|
||||||
|
|
||||||
```yaml
|
For more advanced configuration options, see our detailed documentation:
|
||||||
- name: Do something if the cache was restored
|
|
||||||
if: steps.setup-uv.outputs.cache-hit == 'true'
|
|
||||||
run: echo "Cache was restored"
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Local cache path
|
- **[Advanced Version Configuration](docs/advanced-version-configuration.md)** - Resolution strategies and version files
|
||||||
|
- **[Caching](docs/caching.md)** - Complete guide to caching configuration
|
||||||
If you want to save the cache to a local path other than the default path (`/tmp/setup-uv-cache`)
|
- **[Environment and Tools](docs/environment-and-tools.md)** - Environment activation, tool directories, authentication, and environment variables
|
||||||
you can specify the path with the `cache-local-path` input.
|
- **[Customization](docs/customization.md)** - Checksum validation, custom manifests, and problem matchers
|
||||||
|
|
||||||
```yaml
|
|
||||||
- name: Define a custom uv cache path
|
|
||||||
uses: eifinger/setup-uv@v1
|
|
||||||
with:
|
|
||||||
enable-cache: true
|
|
||||||
cache-local-path: '/path/to/cache'
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Cache dependency glob
|
|
||||||
|
|
||||||
If you want to control when the cache is invalidated you can specify a glob pattern with the `cache-dependency-glob` input.
|
|
||||||
The cache will be invalidated if any file matching the glob pattern changes.
|
|
||||||
The glob matches files relative to the repository root.
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
- name: Define a cache dependency glob
|
|
||||||
uses: eifinger/setup-uv@v1
|
|
||||||
with:
|
|
||||||
enable-cache: true
|
|
||||||
cache-dependency-glob: 'uv.lock'
|
|
||||||
```
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
- name: Define a cache dependency glob
|
|
||||||
uses: eifinger/setup-uv@v1
|
|
||||||
with:
|
|
||||||
enable-cache: true
|
|
||||||
cache-dependency-glob: '**requirements*.txt'
|
|
||||||
```
|
|
||||||
|
|
||||||
### API rate limit
|
|
||||||
|
|
||||||
To avoid hitting the error `API rate limit exceeded` you can supply a GitHub token with the `github-token` input.
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
- name: Install uv and supply a GitHub token
|
|
||||||
uses: eifinger/setup-uv@v1
|
|
||||||
with:
|
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
```
|
|
||||||
|
|
||||||
## How it works
|
## How it works
|
||||||
|
|
||||||
This action downloads uv from the releases of the [uv repo](https://github.com/astral-sh/uv) and uses the [GitHub Actions Toolkit](https://github.com/actions/toolkit) to cache it as a tool to speed up consecutive runs on self-hosted runners.
|
By default, this action resolves uv versions from the
|
||||||
|
[`astral-sh/versions`](https://github.com/astral-sh/versions) manifest and downloads uv from the
|
||||||
|
official [GitHub Releases](https://github.com/astral-sh/uv).
|
||||||
|
|
||||||
The installed version of uv is then added to the runner path so other steps can just use it by calling `uv`.
|
It then uses the [GitHub Actions Toolkit](https://github.com/actions/toolkit) to cache uv as a
|
||||||
|
tool to speed up consecutive runs on self-hosted runners.
|
||||||
|
|
||||||
|
The installed version of uv is then added to the runner PATH, enabling later steps to invoke it
|
||||||
|
by name (`uv`).
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
### Do I still need actions/setup-python when using this action?
|
### Do I still need `actions/setup-python` alongside `setup-uv`?
|
||||||
|
|
||||||
No! This action was modelled as a drop-in replacement for `actions/setup-python` when using uv.
|
With `setup-uv`, you can install a specific version of Python using `uv python install` rather than
|
||||||
|
relying on `actions/setup-python`.
|
||||||
|
|
||||||
A simple example workflow could look like this:
|
Using `actions/setup-python` can be faster (~1s), because GitHub includes several Python versions in the runner image
|
||||||
|
which are available to get activated by `actions/setup-python` without having to download them.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@main
|
uses: actions/checkout@main
|
||||||
- name: Install the latest version of uv
|
- name: Install the latest version of uv
|
||||||
uses: eifinger/setup-uv@v1
|
uses: astral-sh/setup-uv@v7
|
||||||
with:
|
with:
|
||||||
enable-cache: true
|
enable-cache: true
|
||||||
- name: Test
|
- name: Test
|
||||||
run: uv run --frozen pytest
|
run: uv run --frozen pytest # Uses the Python version automatically installed by uv
|
||||||
```
|
```
|
||||||
|
|
||||||
If you want to have a specific python version installed you can use the command [`uv python install`](https://docs.astral.sh/uv/guides/install-python/):
|
To install a specific version of Python, use
|
||||||
|
[`uv python install`](https://docs.astral.sh/uv/guides/install-python/):
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- name: Install the latest version of uv
|
- name: Install the latest version of uv
|
||||||
uses: eifinger/setup-uv@v1
|
uses: astral-sh/setup-uv@v7
|
||||||
with:
|
with:
|
||||||
enable-cache: true
|
enable-cache: true
|
||||||
- name: Install Python 3.12
|
- name: Install Python 3.12
|
||||||
@@ -179,22 +237,96 @@ If you want to have a specific python version installed you can use the command
|
|||||||
|
|
||||||
### What is the default version?
|
### What is the default version?
|
||||||
|
|
||||||
By default this action installs the version defined as `default` in `action.yml`.
|
By default, this action installs the latest version of uv.
|
||||||
When a new release of uv is published this triggers an automatic release of this action with the new version as `default`.
|
|
||||||
|
|
||||||
If you have to know the version installed for other steps of your workflow you can use the `uv-version` output:
|
If you require the installed version in subsequent steps of your workflow, use the `uv-version`
|
||||||
|
output:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@main
|
uses: actions/checkout@main
|
||||||
- name: Install the default version of uv
|
- name: Install the default version of uv
|
||||||
id: setup-uv
|
id: setup-uv
|
||||||
uses: eifinger/setup-uv@v1
|
uses: astral-sh/setup-uv@v7
|
||||||
- name: Print the installed version
|
- name: Print the installed version
|
||||||
run: echo "Installed uv version is ${{ steps.setup-uv.outputs.uv-version }}"
|
run: echo "Installed uv version is ${{ steps.setup-uv.outputs.uv-version }}"
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
### Should I include the resolution strategy in the cache key?
|
||||||
|
|
||||||
[<img src="https://raw.githubusercontent.com/eifinger/setup-uv/main/docs/images/bmc-button.svg" width=150 height=40 style="margin: 5px"/>](https://www.buymeacoffee.com/eifinger)
|
**Yes!**
|
||||||
[<img src="https://raw.githubusercontent.com/eifinger/setup-uv/main/docs/images/paypal-button.svg" width=150 height=40 style="margin: 5px"/>](https://paypal.me/kevinstillhammer)
|
|
||||||
|
The cache key gets computed by using the cache-dependency-glob (see [Caching documentation](docs/caching.md)).
|
||||||
|
|
||||||
|
If you have jobs which use the same dependency definitions from `requirements.txt` or
|
||||||
|
`pyproject.toml` but different
|
||||||
|
[resolution strategies](https://docs.astral.sh/uv/concepts/resolution/#resolution-strategy),
|
||||||
|
each job will have different dependencies or dependency versions.
|
||||||
|
But if you do not add the resolution strategy as a cache-suffix (see [Caching documentation](docs/caching.md)),
|
||||||
|
they will have the same cache key.
|
||||||
|
|
||||||
|
This means the first job which starts uploading its cache will win and all other job will fail
|
||||||
|
uploading the cache,
|
||||||
|
because they try to upload with the same cache key.
|
||||||
|
|
||||||
|
You might see errors like
|
||||||
|
`Failed to save: Failed to CreateCacheEntry: Received non-retryable error: Failed request: (409) Conflict: cache entry with the same key, version, and scope already exists`
|
||||||
|
|
||||||
|
### Why do I see warnings like `No GitHub Actions cache found for key`
|
||||||
|
|
||||||
|
When a workflow runs for the first time on a branch and has a new cache key, because the
|
||||||
|
cache-dependency-glob (see [Caching documentation](docs/caching.md)) found changed files (changed dependencies),
|
||||||
|
the cache will not be found and the warning `No GitHub Actions cache found for key` will be printed.
|
||||||
|
|
||||||
|
While this might be irritating at first, it is expected behaviour and the cache will be created
|
||||||
|
and reused in later workflows.
|
||||||
|
|
||||||
|
The reason for the warning is that we have to way to know if this is the first run of a new
|
||||||
|
cache key or the user accidentally misconfigured the cache-dependency-glob
|
||||||
|
or cache-suffix (see [Caching documentation](docs/caching.md)) and the cache never gets used.
|
||||||
|
|
||||||
|
### Do I have to run `actions/checkout` before or after `setup-uv`?
|
||||||
|
|
||||||
|
Some workflows need uv but do not need to access the repository content.
|
||||||
|
|
||||||
|
But **if** you need to access the repository content, you have run `actions/checkout` before running `setup-uv`.
|
||||||
|
Running `actions/checkout` after `setup-uv` **is not supported**.
|
||||||
|
|
||||||
|
### Does `setup-uv` also install my project or its dependencies automatically?
|
||||||
|
|
||||||
|
No, `setup-uv` alone won't install any libraries from your `pyproject.toml` or `requirements.txt`, it only sets up `uv`.
|
||||||
|
You should run `uv sync` or `uv pip install .` separately, or use `uv run ...` to ensure necessary dependencies are installed.
|
||||||
|
|
||||||
|
### Why is a changed cache not detected and not the full cache uploaded?
|
||||||
|
|
||||||
|
When `setup-uv` starts it has to know whether it is better to download an existing cache
|
||||||
|
or start fresh and download every dependency again.
|
||||||
|
It does this by using a combination of hashes calculated on the contents of e.g. `uv.lock`.
|
||||||
|
|
||||||
|
By calculating these hashes and combining them in a key `setup-uv` can check
|
||||||
|
if an uploaded cache exists for this key.
|
||||||
|
If yes (e.g. contents of `uv.lock` did not change since last run) the dependencies in the cache
|
||||||
|
are up to date and the cache will be downloaded and used.
|
||||||
|
|
||||||
|
Details on determining which files will lead to different caches can be read in the
|
||||||
|
[Caching documentation](docs/caching.md).
|
||||||
|
|
||||||
|
Some dependencies will never be uploaded to the cache and will be downloaded again on each run
|
||||||
|
as described in the [Caching documentation](docs/caching.md).
|
||||||
|
|
||||||
|
## Acknowledgements
|
||||||
|
|
||||||
|
`setup-uv` was initially written and published by [Kevin Stillhammer](https://github.com/eifinger)
|
||||||
|
before moving under the official [Astral](https://github.com/astral-sh) GitHub organization. You can
|
||||||
|
support Kevin's work in open source on [Buy me a coffee](https://www.buymeacoffee.com/eifinger) or
|
||||||
|
[PayPal](https://paypal.me/kevinstillhammer).
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<a target="_blank" href="https://astral.sh" style="background:none">
|
||||||
|
<img src="https://raw.githubusercontent.com/astral-sh/uv/main/assets/svg/Astral.svg" alt="Made by Astral">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
import {expect, test, it} from '@jest/globals'
|
|
||||||
import {
|
|
||||||
isknownVersion,
|
|
||||||
validateChecksum
|
|
||||||
} from '../../../src/download/checksum/checksum'
|
|
||||||
|
|
||||||
test('checksum should match', async () => {
|
|
||||||
const validChecksum =
|
|
||||||
'f3da96ec7e995debee7f5d52ecd034dfb7074309a1da42f76429ecb814d813a3'
|
|
||||||
const filePath = '__tests__/fixtures/checksumfile'
|
|
||||||
// string params don't matter only test the checksum mechanism, not known checksums
|
|
||||||
await validateChecksum(
|
|
||||||
validChecksum,
|
|
||||||
filePath,
|
|
||||||
'aarch64',
|
|
||||||
'pc-windows-msvc',
|
|
||||||
'1.2.3'
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
type KnownVersionFixture = {version: string; known: boolean}
|
|
||||||
|
|
||||||
it.each<KnownVersionFixture>([
|
|
||||||
{
|
|
||||||
version: '0.3.0',
|
|
||||||
known: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
version: '0.0.15',
|
|
||||||
known: false
|
|
||||||
}
|
|
||||||
])(
|
|
||||||
'isknownVersion should return $known for version $version',
|
|
||||||
({version, known}) => {
|
|
||||||
expect(isknownVersion(version)).toBe(known)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
48
__tests__/download/checksum/checksum.test.ts
Normal file
48
__tests__/download/checksum/checksum.test.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { expect, it, test } from "@jest/globals";
|
||||||
|
import {
|
||||||
|
isknownVersion,
|
||||||
|
validateChecksum,
|
||||||
|
} from "../../../src/download/checksum/checksum";
|
||||||
|
|
||||||
|
const validChecksum =
|
||||||
|
"f3da96ec7e995debee7f5d52ecd034dfb7074309a1da42f76429ecb814d813a3";
|
||||||
|
const filePath = "__tests__/fixtures/checksumfile";
|
||||||
|
|
||||||
|
test("checksum should match", async () => {
|
||||||
|
// string params don't matter only test the checksum mechanism, not known checksums
|
||||||
|
await validateChecksum(
|
||||||
|
validChecksum,
|
||||||
|
filePath,
|
||||||
|
"aarch64",
|
||||||
|
"pc-windows-msvc",
|
||||||
|
"1.2.3",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("provided checksum beats known checksums", async () => {
|
||||||
|
await validateChecksum(
|
||||||
|
validChecksum,
|
||||||
|
filePath,
|
||||||
|
"x86_64",
|
||||||
|
"unknown-linux-gnu",
|
||||||
|
"0.3.0",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
type KnownVersionFixture = { version: string; known: boolean };
|
||||||
|
|
||||||
|
it.each<KnownVersionFixture>([
|
||||||
|
{
|
||||||
|
known: true,
|
||||||
|
version: "0.3.0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
known: false,
|
||||||
|
version: "0.0.15",
|
||||||
|
},
|
||||||
|
])("isknownVersion should return $known for version $version", ({
|
||||||
|
version,
|
||||||
|
known,
|
||||||
|
}) => {
|
||||||
|
expect(isknownVersion(version)).toBe(known);
|
||||||
|
});
|
||||||
1
__tests__/download/custom-manifest.ndjson
Normal file
1
__tests__/download/custom-manifest.ndjson
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":"0.9.26","artifacts":[{"platform":"x86_64-unknown-linux-gnu","variant":"default","url":"https://github.com/astral-sh/uv/releases/download/0.9.26/uv-x86_64-unknown-linux-gnu.tar.gz","archive_format":"tar.gz","sha256":"30ccbf0a66dc8727a02b0e245c583ee970bdafecf3a443c1686e1b30ec4939e8"}]}
|
||||||
353
__tests__/download/download-version.test.ts
Normal file
353
__tests__/download/download-version.test.ts
Normal file
@@ -0,0 +1,353 @@
|
|||||||
|
import { beforeEach, describe, expect, it, jest } from "@jest/globals";
|
||||||
|
import * as semver from "semver";
|
||||||
|
|
||||||
|
const mockInfo = jest.fn();
|
||||||
|
const mockWarning = jest.fn();
|
||||||
|
|
||||||
|
jest.unstable_mockModule("@actions/core", () => ({
|
||||||
|
debug: jest.fn(),
|
||||||
|
info: mockInfo,
|
||||||
|
warning: mockWarning,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: Mock requires flexible typing in tests.
|
||||||
|
const mockDownloadTool = jest.fn<any>();
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: Mock requires flexible typing in tests.
|
||||||
|
const mockExtractTar = jest.fn<any>();
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: Mock requires flexible typing in tests.
|
||||||
|
const mockExtractZip = jest.fn<any>();
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: Mock requires flexible typing in tests.
|
||||||
|
const mockCacheDir = jest.fn<any>();
|
||||||
|
|
||||||
|
jest.unstable_mockModule("@actions/tool-cache", () => ({
|
||||||
|
cacheDir: mockCacheDir,
|
||||||
|
downloadTool: mockDownloadTool,
|
||||||
|
evaluateVersions: (versions: string[], range: string) =>
|
||||||
|
semver.maxSatisfying(versions, range) ?? "",
|
||||||
|
extractTar: mockExtractTar,
|
||||||
|
extractZip: mockExtractZip,
|
||||||
|
find: () => "",
|
||||||
|
findAllVersions: () => [],
|
||||||
|
isExplicitVersion: (version: string) => semver.valid(version) !== null,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: Mock requires flexible typing in tests.
|
||||||
|
const mockGetLatestVersion = jest.fn<any>();
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: Mock requires flexible typing in tests.
|
||||||
|
const mockGetAllVersions = jest.fn<any>();
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: Mock requires flexible typing in tests.
|
||||||
|
const mockGetArtifact = jest.fn<any>();
|
||||||
|
|
||||||
|
jest.unstable_mockModule("../../src/download/manifest", () => ({
|
||||||
|
getAllVersions: mockGetAllVersions,
|
||||||
|
getArtifact: mockGetArtifact,
|
||||||
|
getLatestVersion: mockGetLatestVersion,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: Mock requires flexible typing in tests.
|
||||||
|
const mockValidateChecksum = jest.fn<any>();
|
||||||
|
|
||||||
|
jest.unstable_mockModule("../../src/download/checksum/checksum", () => ({
|
||||||
|
validateChecksum: mockValidateChecksum,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const { downloadVersion, resolveVersion, rewriteToMirror } = await import(
|
||||||
|
"../../src/download/download-version"
|
||||||
|
);
|
||||||
|
|
||||||
|
describe("download-version", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockInfo.mockReset();
|
||||||
|
mockWarning.mockReset();
|
||||||
|
mockDownloadTool.mockReset();
|
||||||
|
mockExtractTar.mockReset();
|
||||||
|
mockExtractZip.mockReset();
|
||||||
|
mockCacheDir.mockReset();
|
||||||
|
mockGetLatestVersion.mockReset();
|
||||||
|
mockGetAllVersions.mockReset();
|
||||||
|
mockGetArtifact.mockReset();
|
||||||
|
mockValidateChecksum.mockReset();
|
||||||
|
|
||||||
|
mockDownloadTool.mockResolvedValue("/tmp/downloaded");
|
||||||
|
mockExtractTar.mockResolvedValue("/tmp/extracted");
|
||||||
|
mockExtractZip.mockResolvedValue("/tmp/extracted");
|
||||||
|
mockCacheDir.mockResolvedValue("/tmp/cached");
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("resolveVersion", () => {
|
||||||
|
it("uses the default manifest to resolve latest", async () => {
|
||||||
|
mockGetLatestVersion.mockResolvedValue("0.9.26");
|
||||||
|
|
||||||
|
const version = await resolveVersion("latest", undefined);
|
||||||
|
|
||||||
|
expect(version).toBe("0.9.26");
|
||||||
|
expect(mockGetLatestVersion).toHaveBeenCalledTimes(1);
|
||||||
|
expect(mockGetLatestVersion).toHaveBeenCalledWith(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses the default manifest to resolve available versions", async () => {
|
||||||
|
mockGetAllVersions.mockResolvedValue(["0.9.26", "0.9.25"]);
|
||||||
|
|
||||||
|
const version = await resolveVersion("^0.9.0", undefined);
|
||||||
|
|
||||||
|
expect(version).toBe("0.9.26");
|
||||||
|
expect(mockGetAllVersions).toHaveBeenCalledTimes(1);
|
||||||
|
expect(mockGetAllVersions).toHaveBeenCalledWith(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses manifest-file when provided", async () => {
|
||||||
|
mockGetAllVersions.mockResolvedValue(["0.9.26", "0.9.25"]);
|
||||||
|
|
||||||
|
const version = await resolveVersion(
|
||||||
|
"^0.9.0",
|
||||||
|
"https://example.com/custom.ndjson",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(version).toBe("0.9.26");
|
||||||
|
expect(mockGetAllVersions).toHaveBeenCalledWith(
|
||||||
|
"https://example.com/custom.ndjson",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("downloadVersion", () => {
|
||||||
|
it("fails when manifest lookup fails", async () => {
|
||||||
|
mockGetArtifact.mockRejectedValue(new Error("manifest unavailable"));
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
downloadVersion(
|
||||||
|
"unknown-linux-gnu",
|
||||||
|
"x86_64",
|
||||||
|
"0.9.26",
|
||||||
|
undefined,
|
||||||
|
"token",
|
||||||
|
),
|
||||||
|
).rejects.toThrow("manifest unavailable");
|
||||||
|
|
||||||
|
expect(mockDownloadTool).not.toHaveBeenCalled();
|
||||||
|
expect(mockValidateChecksum).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("fails when no matching artifact exists in the default manifest", async () => {
|
||||||
|
mockGetArtifact.mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
downloadVersion(
|
||||||
|
"unknown-linux-gnu",
|
||||||
|
"x86_64",
|
||||||
|
"0.9.26",
|
||||||
|
undefined,
|
||||||
|
"token",
|
||||||
|
),
|
||||||
|
).rejects.toThrow(
|
||||||
|
"Could not find artifact for version 0.9.26, arch x86_64, platform unknown-linux-gnu in https://raw.githubusercontent.com/astral-sh/versions/main/v1/uv.ndjson .",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockDownloadTool).not.toHaveBeenCalled();
|
||||||
|
expect(mockValidateChecksum).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses built-in checksums for default manifest downloads", async () => {
|
||||||
|
mockGetArtifact.mockResolvedValue({
|
||||||
|
archiveFormat: "tar.gz",
|
||||||
|
checksum: "manifest-checksum-that-should-be-ignored",
|
||||||
|
downloadUrl: "https://example.com/uv.tar.gz",
|
||||||
|
});
|
||||||
|
|
||||||
|
await downloadVersion(
|
||||||
|
"unknown-linux-gnu",
|
||||||
|
"x86_64",
|
||||||
|
"0.9.26",
|
||||||
|
undefined,
|
||||||
|
"token",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockValidateChecksum).toHaveBeenCalledWith(
|
||||||
|
undefined,
|
||||||
|
"/tmp/downloaded",
|
||||||
|
"x86_64",
|
||||||
|
"unknown-linux-gnu",
|
||||||
|
"0.9.26",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rewrites GitHub Releases URLs to the Astral mirror", async () => {
|
||||||
|
mockGetArtifact.mockResolvedValue({
|
||||||
|
archiveFormat: "tar.gz",
|
||||||
|
checksum: "abc123",
|
||||||
|
downloadUrl:
|
||||||
|
"https://github.com/astral-sh/uv/releases/download/0.9.26/uv-x86_64-unknown-linux-gnu.tar.gz",
|
||||||
|
});
|
||||||
|
|
||||||
|
await downloadVersion(
|
||||||
|
"unknown-linux-gnu",
|
||||||
|
"x86_64",
|
||||||
|
"0.9.26",
|
||||||
|
undefined,
|
||||||
|
"token",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockDownloadTool).toHaveBeenCalledWith(
|
||||||
|
"https://releases.astral.sh/github/uv/releases/download/0.9.26/uv-x86_64-unknown-linux-gnu.tar.gz",
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not rewrite non-GitHub URLs", async () => {
|
||||||
|
mockGetArtifact.mockResolvedValue({
|
||||||
|
archiveFormat: "tar.gz",
|
||||||
|
checksum: "abc123",
|
||||||
|
downloadUrl: "https://example.com/uv.tar.gz",
|
||||||
|
});
|
||||||
|
|
||||||
|
await downloadVersion(
|
||||||
|
"unknown-linux-gnu",
|
||||||
|
"x86_64",
|
||||||
|
"0.9.26",
|
||||||
|
undefined,
|
||||||
|
"token",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockDownloadTool).toHaveBeenCalledWith(
|
||||||
|
"https://example.com/uv.tar.gz",
|
||||||
|
undefined,
|
||||||
|
"token",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls back to GitHub Releases when the mirror fails", async () => {
|
||||||
|
mockGetArtifact.mockResolvedValue({
|
||||||
|
archiveFormat: "tar.gz",
|
||||||
|
checksum: "abc123",
|
||||||
|
downloadUrl:
|
||||||
|
"https://github.com/astral-sh/uv/releases/download/0.9.26/uv-x86_64-unknown-linux-gnu.tar.gz",
|
||||||
|
});
|
||||||
|
|
||||||
|
mockDownloadTool
|
||||||
|
.mockRejectedValueOnce(new Error("mirror unavailable"))
|
||||||
|
.mockResolvedValueOnce("/tmp/downloaded");
|
||||||
|
|
||||||
|
await downloadVersion(
|
||||||
|
"unknown-linux-gnu",
|
||||||
|
"x86_64",
|
||||||
|
"0.9.26",
|
||||||
|
undefined,
|
||||||
|
"token",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockDownloadTool).toHaveBeenCalledTimes(2);
|
||||||
|
expect(mockDownloadTool).toHaveBeenNthCalledWith(
|
||||||
|
1,
|
||||||
|
"https://releases.astral.sh/github/uv/releases/download/0.9.26/uv-x86_64-unknown-linux-gnu.tar.gz",
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
expect(mockDownloadTool).toHaveBeenNthCalledWith(
|
||||||
|
2,
|
||||||
|
"https://github.com/astral-sh/uv/releases/download/0.9.26/uv-x86_64-unknown-linux-gnu.tar.gz",
|
||||||
|
undefined,
|
||||||
|
"token",
|
||||||
|
);
|
||||||
|
expect(mockWarning).toHaveBeenCalledWith(
|
||||||
|
"Failed to download from mirror, falling back to GitHub Releases: mirror unavailable",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not fall back for non-GitHub URLs", async () => {
|
||||||
|
mockGetArtifact.mockResolvedValue({
|
||||||
|
archiveFormat: "tar.gz",
|
||||||
|
checksum: "abc123",
|
||||||
|
downloadUrl: "https://example.com/uv.tar.gz",
|
||||||
|
});
|
||||||
|
|
||||||
|
mockDownloadTool.mockRejectedValue(new Error("download failed"));
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
downloadVersion(
|
||||||
|
"unknown-linux-gnu",
|
||||||
|
"x86_64",
|
||||||
|
"0.9.26",
|
||||||
|
undefined,
|
||||||
|
"token",
|
||||||
|
),
|
||||||
|
).rejects.toThrow("download failed");
|
||||||
|
|
||||||
|
expect(mockDownloadTool).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses manifest-file checksum metadata when checksum input is unset", async () => {
|
||||||
|
mockGetArtifact.mockResolvedValue({
|
||||||
|
archiveFormat: "tar.gz",
|
||||||
|
checksum: "manifest-checksum",
|
||||||
|
downloadUrl: "https://example.com/custom-uv.tar.gz",
|
||||||
|
});
|
||||||
|
|
||||||
|
await downloadVersion(
|
||||||
|
"unknown-linux-gnu",
|
||||||
|
"x86_64",
|
||||||
|
"0.9.26",
|
||||||
|
"",
|
||||||
|
"token",
|
||||||
|
"https://example.com/custom.ndjson",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockValidateChecksum).toHaveBeenCalledWith(
|
||||||
|
"manifest-checksum",
|
||||||
|
"/tmp/downloaded",
|
||||||
|
"x86_64",
|
||||||
|
"unknown-linux-gnu",
|
||||||
|
"0.9.26",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("prefers checksum input over manifest-file checksum metadata", async () => {
|
||||||
|
mockGetArtifact.mockResolvedValue({
|
||||||
|
archiveFormat: "tar.gz",
|
||||||
|
checksum: "manifest-checksum",
|
||||||
|
downloadUrl: "https://example.com/custom-uv.tar.gz",
|
||||||
|
});
|
||||||
|
|
||||||
|
await downloadVersion(
|
||||||
|
"unknown-linux-gnu",
|
||||||
|
"x86_64",
|
||||||
|
"0.9.26",
|
||||||
|
"user-checksum",
|
||||||
|
"token",
|
||||||
|
"https://example.com/custom.ndjson",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockValidateChecksum).toHaveBeenCalledWith(
|
||||||
|
"user-checksum",
|
||||||
|
"/tmp/downloaded",
|
||||||
|
"x86_64",
|
||||||
|
"unknown-linux-gnu",
|
||||||
|
"0.9.26",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("rewriteToMirror", () => {
|
||||||
|
it("rewrites a GitHub Releases URL to the Astral mirror", () => {
|
||||||
|
expect(
|
||||||
|
rewriteToMirror(
|
||||||
|
"https://github.com/astral-sh/uv/releases/download/0.9.26/uv-x86_64-unknown-linux-gnu.tar.gz",
|
||||||
|
),
|
||||||
|
).toBe(
|
||||||
|
"https://releases.astral.sh/github/uv/releases/download/0.9.26/uv-x86_64-unknown-linux-gnu.tar.gz",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns undefined for non-GitHub URLs", () => {
|
||||||
|
expect(rewriteToMirror("https://example.com/uv.tar.gz")).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns undefined for a different GitHub repo", () => {
|
||||||
|
expect(
|
||||||
|
rewriteToMirror(
|
||||||
|
"https://github.com/other/repo/releases/download/v1.0/file.tar.gz",
|
||||||
|
),
|
||||||
|
).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
168
__tests__/download/manifest.test.ts
Normal file
168
__tests__/download/manifest.test.ts
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
import { beforeEach, describe, expect, it, jest } from "@jest/globals";
|
||||||
|
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: Mock requires flexible typing in tests.
|
||||||
|
const mockFetch = jest.fn<any>();
|
||||||
|
|
||||||
|
jest.unstable_mockModule("@actions/core", () => ({
|
||||||
|
debug: jest.fn(),
|
||||||
|
info: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.unstable_mockModule("../../src/utils/fetch", () => ({
|
||||||
|
fetch: mockFetch,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const {
|
||||||
|
clearManifestCache,
|
||||||
|
fetchManifest,
|
||||||
|
getAllVersions,
|
||||||
|
getArtifact,
|
||||||
|
getLatestVersion,
|
||||||
|
parseManifest,
|
||||||
|
} = await import("../../src/download/manifest");
|
||||||
|
|
||||||
|
const sampleManifestResponse = `{"version":"0.9.25","artifacts":[{"platform":"aarch64-apple-darwin","variant":"default","url":"https://github.com/astral-sh/uv/releases/download/0.9.25/uv-aarch64-apple-darwin.tar.gz","archive_format":"tar.gz","sha256":"606b3c6949d971709f2526fa0d9f0fd23ccf60e09f117999b406b424af18a6a6"}]}
|
||||||
|
{"version":"0.9.26","artifacts":[{"platform":"aarch64-apple-darwin","variant":"default","url":"https://github.com/astral-sh/uv/releases/download/0.9.26/uv-aarch64-apple-darwin.tar.gz","archive_format":"tar.gz","sha256":"fcf0a9ea6599c6ae28a4c854ac6da76f2c889354d7c36ce136ef071f7ab9721f"},{"platform":"x86_64-pc-windows-msvc","variant":"default","url":"https://github.com/astral-sh/uv/releases/download/0.9.26/uv-x86_64-pc-windows-msvc.zip","archive_format":"zip","sha256":"eb02fd95d8e0eed462b4a67ecdd320d865b38c560bffcda9a0b87ec944bdf036"}]}`;
|
||||||
|
|
||||||
|
const multiVariantManifestResponse = `{"version":"0.9.26","artifacts":[{"platform":"aarch64-apple-darwin","variant":"python-managed","url":"https://github.com/astral-sh/uv/releases/download/0.9.26/uv-aarch64-apple-darwin-managed.tar.gz","archive_format":"tar.gz","sha256":"managed-checksum"},{"platform":"aarch64-apple-darwin","variant":"default","url":"https://github.com/astral-sh/uv/releases/download/0.9.26/uv-aarch64-apple-darwin.zip","archive_format":"zip","sha256":"default-checksum"}]}`;
|
||||||
|
|
||||||
|
function createMockResponse(
|
||||||
|
ok: boolean,
|
||||||
|
status: number,
|
||||||
|
statusText: string,
|
||||||
|
data: string,
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
ok,
|
||||||
|
status,
|
||||||
|
statusText,
|
||||||
|
text: async () => data,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("manifest", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
clearManifestCache();
|
||||||
|
mockFetch.mockReset();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("fetchManifest", () => {
|
||||||
|
it("fetches and parses manifest data", async () => {
|
||||||
|
mockFetch.mockResolvedValue(
|
||||||
|
createMockResponse(true, 200, "OK", sampleManifestResponse),
|
||||||
|
);
|
||||||
|
|
||||||
|
const versions = await fetchManifest();
|
||||||
|
|
||||||
|
expect(versions).toHaveLength(2);
|
||||||
|
expect(versions[0]?.version).toBe("0.9.25");
|
||||||
|
expect(versions[1]?.version).toBe("0.9.26");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws on a failed fetch", async () => {
|
||||||
|
mockFetch.mockResolvedValue(
|
||||||
|
createMockResponse(false, 500, "Internal Server Error", ""),
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(fetchManifest()).rejects.toThrow(
|
||||||
|
"Failed to fetch manifest data: 500 Internal Server Error",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("caches results per URL", async () => {
|
||||||
|
mockFetch.mockResolvedValue(
|
||||||
|
createMockResponse(true, 200, "OK", sampleManifestResponse),
|
||||||
|
);
|
||||||
|
|
||||||
|
await fetchManifest("https://example.com/custom.ndjson");
|
||||||
|
await fetchManifest("https://example.com/custom.ndjson");
|
||||||
|
|
||||||
|
expect(mockFetch).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getAllVersions", () => {
|
||||||
|
it("returns all version strings", async () => {
|
||||||
|
mockFetch.mockResolvedValue(
|
||||||
|
createMockResponse(true, 200, "OK", sampleManifestResponse),
|
||||||
|
);
|
||||||
|
|
||||||
|
const versions = await getAllVersions(
|
||||||
|
"https://example.com/custom.ndjson",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(versions).toEqual(["0.9.25", "0.9.26"]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getArtifact", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockFetch.mockResolvedValue(
|
||||||
|
createMockResponse(true, 200, "OK", sampleManifestResponse),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("finds an artifact by version and platform", async () => {
|
||||||
|
const artifact = await getArtifact("0.9.26", "aarch64", "apple-darwin");
|
||||||
|
|
||||||
|
expect(artifact).toEqual({
|
||||||
|
archiveFormat: "tar.gz",
|
||||||
|
checksum:
|
||||||
|
"fcf0a9ea6599c6ae28a4c854ac6da76f2c889354d7c36ce136ef071f7ab9721f",
|
||||||
|
downloadUrl:
|
||||||
|
"https://github.com/astral-sh/uv/releases/download/0.9.26/uv-aarch64-apple-darwin.tar.gz",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("finds a windows artifact", async () => {
|
||||||
|
const artifact = await getArtifact("0.9.26", "x86_64", "pc-windows-msvc");
|
||||||
|
|
||||||
|
expect(artifact).toEqual({
|
||||||
|
archiveFormat: "zip",
|
||||||
|
checksum:
|
||||||
|
"eb02fd95d8e0eed462b4a67ecdd320d865b38c560bffcda9a0b87ec944bdf036",
|
||||||
|
downloadUrl:
|
||||||
|
"https://github.com/astral-sh/uv/releases/download/0.9.26/uv-x86_64-pc-windows-msvc.zip",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("prefers the default variant when multiple artifacts share a platform", async () => {
|
||||||
|
mockFetch.mockResolvedValue(
|
||||||
|
createMockResponse(true, 200, "OK", multiVariantManifestResponse),
|
||||||
|
);
|
||||||
|
|
||||||
|
const artifact = await getArtifact("0.9.26", "aarch64", "apple-darwin");
|
||||||
|
|
||||||
|
expect(artifact).toEqual({
|
||||||
|
archiveFormat: "zip",
|
||||||
|
checksum: "default-checksum",
|
||||||
|
downloadUrl:
|
||||||
|
"https://github.com/astral-sh/uv/releases/download/0.9.26/uv-aarch64-apple-darwin.zip",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns undefined for an unknown version", async () => {
|
||||||
|
const artifact = await getArtifact("0.0.1", "aarch64", "apple-darwin");
|
||||||
|
|
||||||
|
expect(artifact).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns undefined for an unknown platform", async () => {
|
||||||
|
const artifact = await getArtifact(
|
||||||
|
"0.9.26",
|
||||||
|
"aarch64",
|
||||||
|
"unknown-linux-musl",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(artifact).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("parseManifest", () => {
|
||||||
|
it("throws for malformed manifest data", () => {
|
||||||
|
expect(() => parseManifest('{"version":"0.1.0"', "test-source")).toThrow(
|
||||||
|
"Failed to parse manifest data from test-source",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
1
__tests__/fixtures/.tool-versions
Normal file
1
__tests__/fixtures/.tool-versions
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uv 0.5.15
|
||||||
16
__tests__/fixtures/cache-dir-defined-project/pyproject.toml
Normal file
16
__tests__/fixtures/cache-dir-defined-project/pyproject.toml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
[project]
|
||||||
|
name = "uv-project"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Add your description here"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = [
|
||||||
|
"ruff>=0.6.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["hatchling"]
|
||||||
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
|
[tool.uv]
|
||||||
|
cache-dir = "/tmp/pyproject-toml-defined-cache-path"
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
def hello() -> str:
|
||||||
|
return "Hello from uv-project!"
|
||||||
38
__tests__/fixtures/cache-dir-defined-project/uv.lock
generated
Normal file
38
__tests__/fixtures/cache-dir-defined-project/uv.lock
generated
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
version = 1
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ruff"
|
||||||
|
version = "0.6.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/23/f4/279d044f66b79261fd37df76bf72b64471afab5d3b7906a01499c4451910/ruff-0.6.2.tar.gz", hash = "sha256:239ee6beb9e91feb8e0ec384204a763f36cb53fb895a1a364618c6abb076b3be", size = 2460281 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/72/4b/47dd7a69287afb4069fa42c198e899463605460a58120196711bfcf0446b/ruff-0.6.2-py3-none-linux_armv6l.whl", hash = "sha256:5c8cbc6252deb3ea840ad6a20b0f8583caab0c5ef4f9cca21adc5a92b8f79f3c", size = 9695871 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ae/c3/8aac62ac4638c14a740ee76a755a925f2d0d04580ab790a9887accb729f6/ruff-0.6.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:17002fe241e76544448a8e1e6118abecbe8cd10cf68fde635dad480dba594570", size = 9459354 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2f/cf/77fbd8d4617b9b9c503f9bffb8552c4e3ea1a58dc36975e7a9104ffb0f85/ruff-0.6.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3dbeac76ed13456f8158b8f4fe087bf87882e645c8e8b606dd17b0b66c2c1158", size = 9163871 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/05/1c/765192bab32b79efbb498b06f0b9dcb3629112b53b8777ae1d19b8209e09/ruff-0.6.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:094600ee88cda325988d3f54e3588c46de5c18dae09d683ace278b11f9d4d534", size = 10096250 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/08/d0/86f3cb0f6934c99f759c232984a5204d67a26745cad2d9edff6248adf7d2/ruff-0.6.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:316d418fe258c036ba05fbf7dfc1f7d3d4096db63431546163b472285668132b", size = 9475376 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cd/cc/4c8d0e225b559a3fae6092ec310d7150d3b02b4669e9223f783ef64d82c0/ruff-0.6.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d72b8b3abf8a2d51b7b9944a41307d2f442558ccb3859bbd87e6ae9be1694a5d", size = 10295634 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/db/96/d2699cfb1bb5a01c68122af43454c76c31331e1c8a9bd97d653d7c82524b/ruff-0.6.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:2aed7e243be68487aa8982e91c6e260982d00da3f38955873aecd5a9204b1d66", size = 11024941 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8b/a9/6ecd66af8929e0f2a1ed308a4137f3521789f28f0eb97d32c2ca3aa7000c/ruff-0.6.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d371f7fc9cec83497fe7cf5eaf5b76e22a8efce463de5f775a1826197feb9df8", size = 10606894 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e4/73/2ee4cd19f44992fedac1cc6db9e3d825966072f6dcbd4032f21cbd063170/ruff-0.6.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8f310d63af08f583363dfb844ba8f9417b558199c58a5999215082036d795a1", size = 11552886 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/60/4c/c0f1cd35ce4a93c54a6bb1ee6934a3a205fa02198dd076678193853ceea1/ruff-0.6.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7db6880c53c56addb8638fe444818183385ec85eeada1d48fc5abe045301b2f1", size = 10264945 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c4/89/e45c9359b9cdd4245512ea2b9f2bb128a997feaa5f726fc9e8c7a66afadf/ruff-0.6.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1175d39faadd9a50718f478d23bfc1d4da5743f1ab56af81a2b6caf0a2394f23", size = 10100007 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/06/74/0bd4e0a7ed5f6908df87892f9bf60a2356c0fd74102d8097298bd9b4f346/ruff-0.6.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5b939f9c86d51635fe486585389f54582f0d65b8238e08c327c1534844b3bb9a", size = 9559267 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/54/03/3dc6dc9419f276f05805bf888c279e3e0b631284abd548d9e87cebb93aec/ruff-0.6.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d0d62ca91219f906caf9b187dea50d17353f15ec9bb15aae4a606cd697b49b4c", size = 9905304 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5c/5b/d6a72a6a6bbf097c09de468326ef5fa1c9e7aa5e6e45979bc0d984b0dbe7/ruff-0.6.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7438a7288f9d67ed3c8ce4d059e67f7ed65e9fe3aa2ab6f5b4b3610e57e3cb56", size = 10341480 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/79/a9/0f2f21fe15ba537c46598f96aa9ae4a3d4b9ec64926664617ca6a8c772f4/ruff-0.6.2-py3-none-win32.whl", hash = "sha256:279d5f7d86696df5f9549b56b9b6a7f6c72961b619022b5b7999b15db392a4da", size = 7961901 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b0/80/fff12ffe11853d9f4ea3e5221e6dd2e93640a161c05c9579833e09ad40a7/ruff-0.6.2-py3-none-win_amd64.whl", hash = "sha256:d9f3469c7dd43cd22eb1c3fc16926fb8258d50cb1b216658a07be95dd117b0f2", size = 8783320 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/56/91/577cdd64cce5e74d3f8b5ecb93f29566def569c741eb008aed4f331ef821/ruff-0.6.2-py3-none-win_arm64.whl", hash = "sha256:f28fcd2cd0e02bdf739297516d5643a945cc7caf09bd9bcb4d932540a5ea4fa9", size = 8225886 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uv-project"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { editable = "." }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "ruff" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
requires-dist = [{ name = "ruff" }]
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
3.11
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
def main():
|
||||||
|
print("Hello from malformed-pyproject-toml-project!")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[project]
|
||||||
|
name = "malformed-pyproject-toml-project"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Add your description here"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.11"
|
||||||
|
dependencies = []
|
||||||
|
|
||||||
|
[malformed-toml
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
[project]
|
||||||
|
name = "old-python-constraint-project"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Add your description here"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.8,<=3.9"
|
||||||
|
dependencies = [
|
||||||
|
"ruff>=0.6.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["uv_build>=0.9.22,<0.10.0"]
|
||||||
|
build-backend = "uv_build"
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
def hello() -> str:
|
||||||
|
return "Hello from uv-project!"
|
||||||
40
__tests__/fixtures/old-python-constraint-project/uv.lock
generated
Normal file
40
__tests__/fixtures/old-python-constraint-project/uv.lock
generated
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
version = 1
|
||||||
|
revision = 3
|
||||||
|
requires-python = ">=3.8, <=3.9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "old-python-constraint-project"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { editable = "." }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "ruff" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
requires-dist = [{ name = "ruff", specifier = ">=0.6.2" }]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ruff"
|
||||||
|
version = "0.14.10"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/57/08/52232a877978dd8f9cf2aeddce3e611b40a63287dfca29b6b8da791f5e8d/ruff-0.14.10.tar.gz", hash = "sha256:9a2e830f075d1a42cd28420d7809ace390832a490ed0966fe373ba288e77aaf4", size = 5859763, upload-time = "2025-12-18T19:28:57.98Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/60/01/933704d69f3f05ee16ef11406b78881733c186fe14b6a46b05cfcaf6d3b2/ruff-0.14.10-py3-none-linux_armv6l.whl", hash = "sha256:7a3ce585f2ade3e1f29ec1b92df13e3da262178df8c8bdf876f48fa0e8316c49", size = 13527080, upload-time = "2025-12-18T19:29:25.642Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/df/58/a0349197a7dfa603ffb7f5b0470391efa79ddc327c1e29c4851e85b09cc5/ruff-0.14.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:674f9be9372907f7257c51f1d4fc902cb7cf014b9980152b802794317941f08f", size = 13797320, upload-time = "2025-12-18T19:29:02.571Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7b/82/36be59f00a6082e38c23536df4e71cdbc6af8d7c707eade97fcad5c98235/ruff-0.14.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d85713d522348837ef9df8efca33ccb8bd6fcfc86a2cde3ccb4bc9d28a18003d", size = 12918434, upload-time = "2025-12-18T19:28:51.202Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a6/00/45c62a7f7e34da92a25804f813ebe05c88aa9e0c25e5cb5a7d23dd7450e3/ruff-0.14.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6987ebe0501ae4f4308d7d24e2d0fe3d7a98430f5adfd0f1fead050a740a3a77", size = 13371961, upload-time = "2025-12-18T19:29:04.991Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/40/31/a5906d60f0405f7e57045a70f2d57084a93ca7425f22e1d66904769d1628/ruff-0.14.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:16a01dfb7b9e4eee556fbfd5392806b1b8550c9b4a9f6acd3dbe6812b193c70a", size = 13275629, upload-time = "2025-12-18T19:29:21.381Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3e/60/61c0087df21894cf9d928dc04bcd4fb10e8b2e8dca7b1a276ba2155b2002/ruff-0.14.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7165d31a925b7a294465fa81be8c12a0e9b60fb02bf177e79067c867e71f8b1f", size = 14029234, upload-time = "2025-12-18T19:29:00.132Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/44/84/77d911bee3b92348b6e5dab5a0c898d87084ea03ac5dc708f46d88407def/ruff-0.14.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c561695675b972effb0c0a45db233f2c816ff3da8dcfbe7dfc7eed625f218935", size = 15449890, upload-time = "2025-12-18T19:28:53.573Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e9/36/480206eaefa24a7ec321582dda580443a8f0671fdbf6b1c80e9c3e93a16a/ruff-0.14.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4bb98fcbbc61725968893682fd4df8966a34611239c9fd07a1f6a07e7103d08e", size = 15123172, upload-time = "2025-12-18T19:29:23.453Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5c/38/68e414156015ba80cef5473d57919d27dfb62ec804b96180bafdeaf0e090/ruff-0.14.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f24b47993a9d8cb858429e97bdf8544c78029f09b520af615c1d261bf827001d", size = 14460260, upload-time = "2025-12-18T19:29:27.808Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b3/19/9e050c0dca8aba824d67cc0db69fb459c28d8cd3f6855b1405b3f29cc91d/ruff-0.14.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59aabd2e2c4fd614d2862e7939c34a532c04f1084476d6833dddef4afab87e9f", size = 14229978, upload-time = "2025-12-18T19:29:11.32Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/51/eb/e8dd1dd6e05b9e695aa9dd420f4577debdd0f87a5ff2fedda33c09e9be8c/ruff-0.14.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:213db2b2e44be8625002dbea33bb9c60c66ea2c07c084a00d55732689d697a7f", size = 14338036, upload-time = "2025-12-18T19:29:09.184Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6a/12/f3e3a505db7c19303b70af370d137795fcfec136d670d5de5391e295c134/ruff-0.14.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b914c40ab64865a17a9a5b67911d14df72346a634527240039eb3bd650e5979d", size = 13264051, upload-time = "2025-12-18T19:29:13.431Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/08/64/8c3a47eaccfef8ac20e0484e68e0772013eb85802f8a9f7603ca751eb166/ruff-0.14.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1484983559f026788e3a5c07c81ef7d1e97c1c78ed03041a18f75df104c45405", size = 13283998, upload-time = "2025-12-18T19:29:06.994Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/12/84/534a5506f4074e5cc0529e5cd96cfc01bb480e460c7edf5af70d2bcae55e/ruff-0.14.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c70427132db492d25f982fffc8d6c7535cc2fd2c83fc8888f05caaa248521e60", size = 13601891, upload-time = "2025-12-18T19:28:55.811Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0d/1e/14c916087d8598917dbad9b2921d340f7884824ad6e9c55de948a93b106d/ruff-0.14.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5bcf45b681e9f1ee6445d317ce1fa9d6cba9a6049542d1c3d5b5958986be8830", size = 14336660, upload-time = "2025-12-18T19:29:16.531Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f2/1c/d7b67ab43f30013b47c12b42d1acd354c195351a3f7a1d67f59e54227ede/ruff-0.14.10-py3-none-win32.whl", hash = "sha256:104c49fc7ab73f3f3a758039adea978869a918f31b73280db175b43a2d9b51d6", size = 13196187, upload-time = "2025-12-18T19:29:19.006Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fb/9c/896c862e13886fae2af961bef3e6312db9ebc6adc2b156fe95e615dee8c1/ruff-0.14.10-py3-none-win_amd64.whl", hash = "sha256:466297bd73638c6bdf06485683e812db1c00c7ac96d4ddd0294a338c62fdc154", size = 14661283, upload-time = "2025-12-18T19:29:30.16Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/74/31/b0e29d572670dca3674eeee78e418f20bdf97fa8aa9ea71380885e175ca0/ruff-0.14.10-py3-none-win_arm64.whl", hash = "sha256:e51d046cf6dda98a4633b8a8a771451107413b0f07183b2bef03f075599e44e6", size = 13729839, upload-time = "2025-12-18T19:28:48.636Z" },
|
||||||
|
]
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
3.11
|
||||||
0
__tests__/fixtures/pyproject-toml-project/README.md
Normal file
0
__tests__/fixtures/pyproject-toml-project/README.md
Normal file
6
__tests__/fixtures/pyproject-toml-project/hello.py
Normal file
6
__tests__/fixtures/pyproject-toml-project/hello.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
def main():
|
||||||
|
print("Hello from pyproject-toml-project!")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
19
__tests__/fixtures/pyproject-toml-project/pyproject.toml
Normal file
19
__tests__/fixtures/pyproject-toml-project/pyproject.toml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
[project]
|
||||||
|
name = "pyproject-toml-project"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Add your description here"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.11"
|
||||||
|
dependencies = []
|
||||||
|
|
||||||
|
[dependency-groups]
|
||||||
|
dev = [
|
||||||
|
"reuse==5.0.2",
|
||||||
|
{include-group = "lint"},
|
||||||
|
]
|
||||||
|
lint = [
|
||||||
|
"flake8==4.0.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.uv]
|
||||||
|
required-version = "==0.5.14"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
print("Hello world")
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ruff>=0.6.2
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
print("Hello world")
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# This file was autogenerated by uv via the following command:
|
||||||
|
# uv pip compile --generate-hashes - -o ex-requirements.txt
|
||||||
|
click==8.2.1 \
|
||||||
|
--hash=sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202 \
|
||||||
|
--hash=sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b
|
||||||
|
# via uvicorn
|
||||||
|
h11==0.16.0 \
|
||||||
|
--hash=sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1 \
|
||||||
|
--hash=sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86
|
||||||
|
# via uvicorn
|
||||||
|
uv==0.8.3 \
|
||||||
|
--hash=sha256:1121ad1c9389b865d029385031d3fd7d90d343c92a2149a4d4aa20bf469cb27f \
|
||||||
|
--hash=sha256:17bcdb0615e37cc5f985f7d7546f755ac6343c1dc8bbe876c892437f14f8f904 \
|
||||||
|
--hash=sha256:2ccaae4c749126c99f6404d67a0ae1eae29cbafb05603d09094a775061fdf4e5 \
|
||||||
|
--hash=sha256:2e311c029bff2ca07c6ddf877ccc5935cabb78e09b94b53a849542665b6a6fa1 \
|
||||||
|
--hash=sha256:391c97577048a40fd8c85b370055df6420f26e81df7fa906f0e0ce1aa2af3527 \
|
||||||
|
--hash=sha256:3f904f574dc2d7aa1d96ddf2483480ecd121dc9d060108cadd8bff100b754b64 \
|
||||||
|
--hash=sha256:526f2c3bd6f311ce31f6f7b6b7d818b191f41e76bed3aaab671b716220c02d8f \
|
||||||
|
--hash=sha256:5313ee776ad65731ffa8ac585246f987d3a2bf72e6153c12add1fff22ad6e500 \
|
||||||
|
--hash=sha256:5843cc43bafad05cc710d8e31bd347ee37202462a63d32c30746e9df48cfbda2 \
|
||||||
|
--hash=sha256:76de331a07e5ae9b6490e70a9439a072b91b3167a5684510af10c2752c4ece9a \
|
||||||
|
--hash=sha256:8486f7576d15cc73509f93f47b3190f44701ea36839906369301b58c8604d5db \
|
||||||
|
--hash=sha256:8b16f1bddfdf8f7470924ab34a7b55e4c372d5340c7c1e47e7fc84a743dc541f \
|
||||||
|
--hash=sha256:966ec7d7f57521fef0fee685d71e183c9cafb358ddcfe27519dfeaf40550f247 \
|
||||||
|
--hash=sha256:989898caeb6e972979543b57547d1c28ab8af81ff8fc15921fd354c17d432749 \
|
||||||
|
--hash=sha256:9ce7981f4fbeecf93dc5cf0a5a7915e84956fd99ad3ac977c048fe0cfdb1a17e \
|
||||||
|
--hash=sha256:ad13453ab0a1dfa64a221aac8f52199efdcaa52c97134fffd7bcebed794a6f4b \
|
||||||
|
--hash=sha256:ae7efe91dcfc24126fa91e0fb69a1daf6c0e494a781ba192bb0cc62d7ab623ee \
|
||||||
|
--hash=sha256:daa6e0d657a94f20e962d4a03d833ef7af5c8e51b7c8a2d92ba6cf64a4c07ac1 \
|
||||||
|
--hash=sha256:f1eb7c896fc0d80ed534748aaf46697b6ebc8ce401f1c51666ce0b9923c3db9a
|
||||||
|
uvicorn==0.35.0 \
|
||||||
|
--hash=sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a \
|
||||||
|
--hash=sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
print("Hello world")
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
uvicorn==0.35.0
|
||||||
|
uv==0.6.17
|
||||||
1
__tests__/fixtures/uv-toml-project/.python-version
Normal file
1
__tests__/fixtures/uv-toml-project/.python-version
Normal file
@@ -0,0 +1 @@
|
|||||||
|
3.11
|
||||||
0
__tests__/fixtures/uv-toml-project/README.md
Normal file
0
__tests__/fixtures/uv-toml-project/README.md
Normal file
6
__tests__/fixtures/uv-toml-project/hello.py
Normal file
6
__tests__/fixtures/uv-toml-project/hello.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
def main():
|
||||||
|
print("Hello from uv-toml-project!")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
10
__tests__/fixtures/uv-toml-project/pyproject.toml
Normal file
10
__tests__/fixtures/uv-toml-project/pyproject.toml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
[project]
|
||||||
|
name = "uv-toml-project"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Add your description here"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.11"
|
||||||
|
dependencies = []
|
||||||
|
|
||||||
|
[tool.uv]
|
||||||
|
required-version = "==0.5.14"
|
||||||
1
__tests__/fixtures/uv-toml-project/uv.toml
Normal file
1
__tests__/fixtures/uv-toml-project/uv.toml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
required-version = "==0.5.15"
|
||||||
210
__tests__/utils/inputs.test.ts
Normal file
210
__tests__/utils/inputs.test.ts
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
import {
|
||||||
|
afterEach,
|
||||||
|
beforeEach,
|
||||||
|
describe,
|
||||||
|
expect,
|
||||||
|
it,
|
||||||
|
jest,
|
||||||
|
} from "@jest/globals";
|
||||||
|
|
||||||
|
// Will be mutated per test before (re-)importing the module under test
|
||||||
|
let mockInputs: Record<string, string> = {};
|
||||||
|
const ORIGINAL_HOME = process.env.HOME;
|
||||||
|
|
||||||
|
const mockDebug = jest.fn();
|
||||||
|
const mockGetBooleanInput = jest.fn(
|
||||||
|
(name: string) => (mockInputs[name] ?? "") === "true",
|
||||||
|
);
|
||||||
|
const mockGetInput = jest.fn((name: string) => mockInputs[name] ?? "");
|
||||||
|
const mockInfo = jest.fn();
|
||||||
|
const mockWarning = jest.fn();
|
||||||
|
|
||||||
|
jest.unstable_mockModule("@actions/core", () => ({
|
||||||
|
debug: mockDebug,
|
||||||
|
getBooleanInput: mockGetBooleanInput,
|
||||||
|
getInput: mockGetInput,
|
||||||
|
info: mockInfo,
|
||||||
|
warning: mockWarning,
|
||||||
|
}));
|
||||||
|
|
||||||
|
async function importInputsModule() {
|
||||||
|
return await import("../../src/utils/inputs");
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("cacheDependencyGlob", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.resetModules();
|
||||||
|
jest.clearAllMocks();
|
||||||
|
mockInputs = {};
|
||||||
|
process.env.HOME = "/home/testuser";
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
process.env.HOME = ORIGINAL_HOME;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns empty string when input not provided", async () => {
|
||||||
|
mockInputs["working-directory"] = "/workspace";
|
||||||
|
const { cacheDependencyGlob } = await importInputsModule();
|
||||||
|
expect(cacheDependencyGlob).toBe("");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("resolves a single relative path", async () => {
|
||||||
|
mockInputs["working-directory"] = "/workspace";
|
||||||
|
mockInputs["cache-dependency-glob"] = "requirements.txt";
|
||||||
|
const { cacheDependencyGlob } = await importInputsModule();
|
||||||
|
expect(cacheDependencyGlob).toBe("/workspace/requirements.txt");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("strips leading ./ from relative path", async () => {
|
||||||
|
mockInputs["working-directory"] = "/workspace";
|
||||||
|
mockInputs["cache-dependency-glob"] = "./uv.lock";
|
||||||
|
const { cacheDependencyGlob } = await importInputsModule();
|
||||||
|
expect(cacheDependencyGlob).toBe("/workspace/uv.lock");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles multiple lines, trimming whitespace, tilde expansion and absolute paths", async () => {
|
||||||
|
mockInputs["working-directory"] = "/workspace";
|
||||||
|
mockInputs["cache-dependency-glob"] =
|
||||||
|
" ~/.cache/file1\n ./rel/file2 \nfile3.txt";
|
||||||
|
const { cacheDependencyGlob } = await importInputsModule();
|
||||||
|
expect(cacheDependencyGlob).toBe(
|
||||||
|
[
|
||||||
|
"/home/testuser/.cache/file1", // expanded tilde, absolute path unchanged
|
||||||
|
"/workspace/rel/file2", // ./ stripped and resolved
|
||||||
|
"/workspace/file3.txt", // relative path resolved
|
||||||
|
].join("\n"),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps absolute path unchanged in multiline input", async () => {
|
||||||
|
mockInputs["working-directory"] = "/workspace";
|
||||||
|
mockInputs["cache-dependency-glob"] = "/abs/path.lock\nrelative.lock";
|
||||||
|
const { cacheDependencyGlob } = await importInputsModule();
|
||||||
|
expect(cacheDependencyGlob).toBe(
|
||||||
|
["/abs/path.lock", "/workspace/relative.lock"].join("\n"),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles exclusions in relative paths correct", async () => {
|
||||||
|
mockInputs["working-directory"] = "/workspace";
|
||||||
|
mockInputs["cache-dependency-glob"] = "!/abs/path.lock\n!relative.lock";
|
||||||
|
const { cacheDependencyGlob } = await importInputsModule();
|
||||||
|
expect(cacheDependencyGlob).toBe(
|
||||||
|
["!/abs/path.lock", "!/workspace/relative.lock"].join("\n"),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("tool directories", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.resetModules();
|
||||||
|
jest.clearAllMocks();
|
||||||
|
mockInputs = {};
|
||||||
|
process.env.HOME = "/home/testuser";
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
process.env.HOME = ORIGINAL_HOME;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("expands tilde for tool-bin-dir and tool-dir", async () => {
|
||||||
|
mockInputs["working-directory"] = "/workspace";
|
||||||
|
mockInputs["tool-bin-dir"] = "~/tool-bin-dir";
|
||||||
|
mockInputs["tool-dir"] = "~/tool-dir";
|
||||||
|
|
||||||
|
const { toolBinDir, toolDir } = await importInputsModule();
|
||||||
|
|
||||||
|
expect(toolBinDir).toBe("/home/testuser/tool-bin-dir");
|
||||||
|
expect(toolDir).toBe("/home/testuser/tool-dir");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("cacheLocalPath", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.resetModules();
|
||||||
|
jest.clearAllMocks();
|
||||||
|
mockInputs = {};
|
||||||
|
process.env.HOME = "/home/testuser";
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
process.env.HOME = ORIGINAL_HOME;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("expands tilde in cache-local-path", async () => {
|
||||||
|
mockInputs["working-directory"] = "/workspace";
|
||||||
|
mockInputs["cache-local-path"] = "~/uv-cache/cache-local-path";
|
||||||
|
|
||||||
|
const { CacheLocalSource, cacheLocalPath } = await importInputsModule();
|
||||||
|
|
||||||
|
expect(cacheLocalPath).toEqual({
|
||||||
|
path: "/home/testuser/uv-cache/cache-local-path",
|
||||||
|
source: CacheLocalSource.Input,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("venvPath", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.resetModules();
|
||||||
|
jest.clearAllMocks();
|
||||||
|
mockInputs = {};
|
||||||
|
process.env.HOME = "/home/testuser";
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
process.env.HOME = ORIGINAL_HOME;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("defaults to .venv in the working directory", async () => {
|
||||||
|
mockInputs["working-directory"] = "/workspace";
|
||||||
|
const { venvPath } = await importInputsModule();
|
||||||
|
expect(venvPath).toBe("/workspace/.venv");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("resolves a relative venv-path", async () => {
|
||||||
|
mockInputs["working-directory"] = "/workspace";
|
||||||
|
mockInputs["activate-environment"] = "true";
|
||||||
|
mockInputs["venv-path"] = "custom-venv";
|
||||||
|
const { venvPath } = await importInputsModule();
|
||||||
|
expect(venvPath).toBe("/workspace/custom-venv");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("normalizes venv-path with trailing slash", async () => {
|
||||||
|
mockInputs["working-directory"] = "/workspace";
|
||||||
|
mockInputs["activate-environment"] = "true";
|
||||||
|
mockInputs["venv-path"] = "custom-venv/";
|
||||||
|
const { venvPath } = await importInputsModule();
|
||||||
|
expect(venvPath).toBe("/workspace/custom-venv");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps an absolute venv-path unchanged", async () => {
|
||||||
|
mockInputs["working-directory"] = "/workspace";
|
||||||
|
mockInputs["activate-environment"] = "true";
|
||||||
|
mockInputs["venv-path"] = "/tmp/custom-venv";
|
||||||
|
const { venvPath } = await importInputsModule();
|
||||||
|
expect(venvPath).toBe("/tmp/custom-venv");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("expands tilde in venv-path", async () => {
|
||||||
|
mockInputs["working-directory"] = "/workspace";
|
||||||
|
mockInputs["activate-environment"] = "true";
|
||||||
|
mockInputs["venv-path"] = "~/.venv";
|
||||||
|
const { venvPath } = await importInputsModule();
|
||||||
|
expect(venvPath).toBe("/home/testuser/.venv");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("warns when venv-path is set but activate-environment is false", async () => {
|
||||||
|
mockInputs["working-directory"] = "/workspace";
|
||||||
|
mockInputs["venv-path"] = "custom-venv";
|
||||||
|
|
||||||
|
const { activateEnvironment, venvPath } = await importInputsModule();
|
||||||
|
|
||||||
|
expect(activateEnvironment).toBe(false);
|
||||||
|
expect(venvPath).toBe("/workspace/custom-venv");
|
||||||
|
expect(mockWarning).toHaveBeenCalledWith(
|
||||||
|
"venv-path is only used when activate-environment is true",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
9
__tests__/version/requirements-file.test.ts
Normal file
9
__tests__/version/requirements-file.test.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { expect, test } from "@jest/globals";
|
||||||
|
import { getUvVersionFromFile } from "../../src/version/resolve";
|
||||||
|
|
||||||
|
test("ignores dependencies starting with uv", async () => {
|
||||||
|
const parsedVersion = getUvVersionFromFile(
|
||||||
|
"__tests__/fixtures/uv-in-requirements-txt-project/requirements.txt",
|
||||||
|
);
|
||||||
|
expect(parsedVersion).toBe("0.6.17");
|
||||||
|
});
|
||||||
9
__tests__/version/requirements-hashes-file.test.ts
Normal file
9
__tests__/version/requirements-hashes-file.test.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { expect, test } from "@jest/globals";
|
||||||
|
import { getUvVersionFromFile } from "../../src/version/resolve";
|
||||||
|
|
||||||
|
test("ignores dependencies starting with uv", async () => {
|
||||||
|
const parsedVersion = getUvVersionFromFile(
|
||||||
|
"__tests__/fixtures/uv-in-requirements-hash-txt-project/requirements.txt",
|
||||||
|
);
|
||||||
|
expect(parsedVersion).toBe("0.8.3");
|
||||||
|
});
|
||||||
123
__tests__/version/tool-versions-file.test.ts
Normal file
123
__tests__/version/tool-versions-file.test.ts
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
import { beforeEach, describe, expect, it, jest } from "@jest/globals";
|
||||||
|
|
||||||
|
const mockReadFileSync = jest.fn();
|
||||||
|
const mockWarning = jest.fn();
|
||||||
|
|
||||||
|
jest.unstable_mockModule("node:fs", () => ({
|
||||||
|
default: {
|
||||||
|
readFileSync: mockReadFileSync,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.unstable_mockModule("@actions/core", () => ({
|
||||||
|
warning: mockWarning,
|
||||||
|
}));
|
||||||
|
|
||||||
|
async function getVersionFromToolVersions(filePath: string) {
|
||||||
|
const { getUvVersionFromToolVersions } = await import(
|
||||||
|
"../../src/version/tool-versions-file"
|
||||||
|
);
|
||||||
|
|
||||||
|
return getUvVersionFromToolVersions(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("getUvVersionFromToolVersions", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.resetModules();
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return undefined for non-.tool-versions files", async () => {
|
||||||
|
const result = await getVersionFromToolVersions("package.json");
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
expect(mockReadFileSync).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return version for valid uv entry", async () => {
|
||||||
|
const fileContent = "python 3.11.0\nuv 0.1.0\nnodejs 18.0.0";
|
||||||
|
mockReadFileSync.mockReturnValue(fileContent);
|
||||||
|
|
||||||
|
const result = await getVersionFromToolVersions(".tool-versions");
|
||||||
|
|
||||||
|
expect(result).toBe("0.1.0");
|
||||||
|
expect(mockReadFileSync).toHaveBeenCalledWith(".tool-versions", "utf8");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return version for uv entry with v prefix", async () => {
|
||||||
|
const fileContent = "uv v0.2.0";
|
||||||
|
mockReadFileSync.mockReturnValue(fileContent);
|
||||||
|
|
||||||
|
const result = await getVersionFromToolVersions(".tool-versions");
|
||||||
|
|
||||||
|
expect(result).toBe("0.2.0");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle whitespace around uv entry", async () => {
|
||||||
|
const fileContent = " uv 0.3.0 ";
|
||||||
|
mockReadFileSync.mockReturnValue(fileContent);
|
||||||
|
|
||||||
|
const result = await getVersionFromToolVersions(".tool-versions");
|
||||||
|
|
||||||
|
expect(result).toBe("0.3.0");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should skip commented lines", async () => {
|
||||||
|
const fileContent = "# uv 0.1.0\npython 3.11.0\nuv 0.2.0";
|
||||||
|
mockReadFileSync.mockReturnValue(fileContent);
|
||||||
|
|
||||||
|
const result = await getVersionFromToolVersions(".tool-versions");
|
||||||
|
|
||||||
|
expect(result).toBe("0.2.0");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return first matching uv version", async () => {
|
||||||
|
const fileContent = "uv 0.1.0\npython 3.11.0\nuv 0.2.0";
|
||||||
|
mockReadFileSync.mockReturnValue(fileContent);
|
||||||
|
|
||||||
|
const result = await getVersionFromToolVersions(".tool-versions");
|
||||||
|
|
||||||
|
expect(result).toBe("0.1.0");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return undefined when no uv entry found", async () => {
|
||||||
|
const fileContent = "python 3.11.0\nnodejs 18.0.0";
|
||||||
|
mockReadFileSync.mockReturnValue(fileContent);
|
||||||
|
|
||||||
|
const result = await getVersionFromToolVersions(".tool-versions");
|
||||||
|
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return undefined for empty file", async () => {
|
||||||
|
mockReadFileSync.mockReturnValue("");
|
||||||
|
|
||||||
|
const result = await getVersionFromToolVersions(".tool-versions");
|
||||||
|
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should warn and return undefined for ref syntax", async () => {
|
||||||
|
const fileContent = "uv ref:main";
|
||||||
|
mockReadFileSync.mockReturnValue(fileContent);
|
||||||
|
|
||||||
|
const result = await getVersionFromToolVersions(".tool-versions");
|
||||||
|
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
expect(mockWarning).toHaveBeenCalledWith(
|
||||||
|
"The ref syntax of .tool-versions is not supported. Please use a released version instead.",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle file path with .tool-versions extension", async () => {
|
||||||
|
const fileContent = "uv 0.1.0";
|
||||||
|
mockReadFileSync.mockReturnValue(fileContent);
|
||||||
|
|
||||||
|
const result = await getVersionFromToolVersions("path/to/.tool-versions");
|
||||||
|
|
||||||
|
expect(result).toBe("0.1.0");
|
||||||
|
expect(mockReadFileSync).toHaveBeenCalledWith(
|
||||||
|
"path/to/.tool-versions",
|
||||||
|
"utf8",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
77
action-types.yml
Normal file
77
action-types.yml
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
# See https://github.com/typesafegithub/github-actions-typing
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
type: string
|
||||||
|
version-file:
|
||||||
|
type: string
|
||||||
|
python-version:
|
||||||
|
type: string
|
||||||
|
activate-environment:
|
||||||
|
type: boolean
|
||||||
|
venv-path:
|
||||||
|
type: string
|
||||||
|
working-directory:
|
||||||
|
type: string
|
||||||
|
checksum:
|
||||||
|
type: string
|
||||||
|
github-token:
|
||||||
|
type: string
|
||||||
|
enable-cache:
|
||||||
|
type: enum
|
||||||
|
allowed-values:
|
||||||
|
- "true"
|
||||||
|
- "false"
|
||||||
|
- auto
|
||||||
|
cache-dependency-glob:
|
||||||
|
type: list
|
||||||
|
separator: "\n"
|
||||||
|
list-item:
|
||||||
|
type: string
|
||||||
|
restore-cache:
|
||||||
|
type: boolean
|
||||||
|
save-cache:
|
||||||
|
type: boolean
|
||||||
|
cache-suffix:
|
||||||
|
type: string
|
||||||
|
cache-local-path:
|
||||||
|
type: string
|
||||||
|
prune-cache:
|
||||||
|
type: boolean
|
||||||
|
cache-python:
|
||||||
|
type: boolean
|
||||||
|
ignore-nothing-to-cache:
|
||||||
|
type: boolean
|
||||||
|
ignore-empty-workdir:
|
||||||
|
type: boolean
|
||||||
|
tool-dir:
|
||||||
|
type: string
|
||||||
|
tool-bin-dir:
|
||||||
|
type: string
|
||||||
|
manifest-file:
|
||||||
|
type: string
|
||||||
|
add-problem-matchers:
|
||||||
|
type: boolean
|
||||||
|
resolution-strategy:
|
||||||
|
type: enum
|
||||||
|
allowed-values:
|
||||||
|
- highest
|
||||||
|
- lowest
|
||||||
|
|
||||||
|
outputs:
|
||||||
|
uv-version:
|
||||||
|
type: string
|
||||||
|
uv-path:
|
||||||
|
type: string
|
||||||
|
uvx-path:
|
||||||
|
type: string
|
||||||
|
cache-hit:
|
||||||
|
type: boolean
|
||||||
|
cache-key:
|
||||||
|
type: string
|
||||||
|
venv:
|
||||||
|
type: string
|
||||||
|
python-version:
|
||||||
|
type: string
|
||||||
|
python-cache-hit:
|
||||||
|
type: boolean
|
||||||
110
action.yml
110
action.yml
@@ -1,38 +1,110 @@
|
|||||||
name: 'Python setup uv'
|
name: "astral-sh/setup-uv"
|
||||||
description: 'Set up your GitHub Actions workflow with a specific version of uv'
|
description:
|
||||||
author: 'eifinger'
|
"Set up your GitHub Actions workflow with a specific version of uv."
|
||||||
|
author: "astral-sh"
|
||||||
inputs:
|
inputs:
|
||||||
version:
|
version:
|
||||||
description: 'The version of uv to install'
|
description: "The version of uv to install e.g., `0.5.0` Defaults to the version in pyproject.toml or 'latest'."
|
||||||
default: '0.4.4'
|
default: ""
|
||||||
|
version-file:
|
||||||
|
description: "Path to a file containing the version of uv to install. Defaults to searching for uv.toml and if not found pyproject.toml."
|
||||||
|
default: ""
|
||||||
|
python-version:
|
||||||
|
description: "The version of Python to set UV_PYTHON to"
|
||||||
|
required: false
|
||||||
|
activate-environment:
|
||||||
|
description: "Use uv venv to activate a venv ready to be used by later steps. "
|
||||||
|
default: "false"
|
||||||
|
venv-path:
|
||||||
|
description: "Custom path for the virtual environment when using activate-environment. Defaults to '.venv' in the working directory."
|
||||||
|
default: ""
|
||||||
|
working-directory:
|
||||||
|
description: "The directory to execute all commands in and look for files such as pyproject.toml"
|
||||||
|
default: ${{ github.workspace }}
|
||||||
checksum:
|
checksum:
|
||||||
description: 'The checksum of the uv version to install'
|
description: "The checksum of the uv version to install"
|
||||||
required: false
|
required: false
|
||||||
github-token:
|
github-token:
|
||||||
description: 'Used to increase the rate limit when retrieving versions and downloading uv.'
|
description:
|
||||||
|
"Used when downloading uv from GitHub releases."
|
||||||
required: false
|
required: false
|
||||||
|
default: ${{ github.token }}
|
||||||
enable-cache:
|
enable-cache:
|
||||||
description: 'Enable caching of the uv cache'
|
description: "Enable uploading of the uv cache"
|
||||||
default: 'false'
|
default: "auto"
|
||||||
cache-dependency-glob:
|
cache-dependency-glob:
|
||||||
description: 'Glob pattern to match files relative to the repository root to control the cache. e.g. "uv.lock"'
|
description:
|
||||||
required: false
|
"Glob pattern to match files relative to the working directory to control
|
||||||
|
the cache."
|
||||||
|
default: |
|
||||||
|
**/*requirements*.txt
|
||||||
|
**/*requirements*.in
|
||||||
|
**/*constraints*.txt
|
||||||
|
**/*constraints*.in
|
||||||
|
**/pyproject.toml
|
||||||
|
**/uv.lock
|
||||||
|
**/*.py.lock
|
||||||
|
restore-cache:
|
||||||
|
description: "Whether to restore the cache if found."
|
||||||
|
default: "true"
|
||||||
|
save-cache:
|
||||||
|
description: "Whether to save the cache after the run."
|
||||||
|
default: "true"
|
||||||
cache-suffix:
|
cache-suffix:
|
||||||
description: 'Suffix for the cache key'
|
description: "Suffix for the cache key"
|
||||||
required: false
|
required: false
|
||||||
cache-local-path:
|
cache-local-path:
|
||||||
description: 'Local path to store the cache.'
|
description: "Local path to store the cache."
|
||||||
default: '/tmp/setup-uv-cache'
|
default: ""
|
||||||
|
prune-cache:
|
||||||
|
description: "Prune cache before saving."
|
||||||
|
default: "true"
|
||||||
|
cache-python:
|
||||||
|
description: "Upload managed Python installations to the Github Actions cache."
|
||||||
|
default: "false"
|
||||||
|
ignore-nothing-to-cache:
|
||||||
|
description: "Ignore when nothing is found to cache."
|
||||||
|
default: "false"
|
||||||
|
ignore-empty-workdir:
|
||||||
|
description: "Ignore when the working directory is empty."
|
||||||
|
default: "false"
|
||||||
|
tool-dir:
|
||||||
|
description: "Custom path to set UV_TOOL_DIR to."
|
||||||
|
required: false
|
||||||
|
tool-bin-dir:
|
||||||
|
description: "Custom path to set UV_TOOL_BIN_DIR to."
|
||||||
|
required: false
|
||||||
|
manifest-file:
|
||||||
|
description: "URL to a custom manifest file in the astral-sh/versions format."
|
||||||
|
required: false
|
||||||
|
add-problem-matchers:
|
||||||
|
description: "Add problem matchers."
|
||||||
|
default: "true"
|
||||||
|
resolution-strategy:
|
||||||
|
description: "Resolution strategy to use when resolving version ranges. 'highest' uses the latest compatible version, 'lowest' uses the oldest compatible version."
|
||||||
|
default: "highest"
|
||||||
outputs:
|
outputs:
|
||||||
uv-version:
|
uv-version:
|
||||||
description: "The installed uv version. Useful when using latest."
|
description: "The installed uv version. Useful when using latest."
|
||||||
|
uv-path:
|
||||||
|
description: "The path to the installed uv binary."
|
||||||
|
uvx-path:
|
||||||
|
description: "The path to the installed uvx binary."
|
||||||
cache-hit:
|
cache-hit:
|
||||||
description: "A boolean value to indicate a cache entry was found"
|
description: "A boolean value to indicate a cache entry was found"
|
||||||
|
cache-key:
|
||||||
|
description: "The cache key used for storing/restoring the cache"
|
||||||
|
venv:
|
||||||
|
description: "Path to the activated venv if activate-environment is true"
|
||||||
|
python-version:
|
||||||
|
description: "The Python version that was set."
|
||||||
|
python-cache-hit:
|
||||||
|
description: "A boolean value to indicate the Python cache entry was found"
|
||||||
runs:
|
runs:
|
||||||
using: 'node20'
|
using: "node24"
|
||||||
main: 'dist/setup/index.js'
|
main: "dist/setup/index.cjs"
|
||||||
post: 'dist/save-cache/index.js'
|
post: "dist/save-cache/index.cjs"
|
||||||
post-if: success()
|
post-if: success()
|
||||||
branding:
|
branding:
|
||||||
icon: 'package'
|
icon: "package"
|
||||||
color: 'blue'
|
color: "black"
|
||||||
|
|||||||
45
biome.json
Normal file
45
biome.json
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://biomejs.dev/schemas/2.4.7/schema.json",
|
||||||
|
"assist": {
|
||||||
|
"actions": {
|
||||||
|
"source": {
|
||||||
|
"organizeImports": "on",
|
||||||
|
"useSortedAttributes": "on",
|
||||||
|
"useSortedKeys": "on"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"files": {
|
||||||
|
"ignoreUnknown": false,
|
||||||
|
"includes": [
|
||||||
|
"**",
|
||||||
|
"!**/dist",
|
||||||
|
"!**/lib",
|
||||||
|
"!**/node_modules",
|
||||||
|
"!**/package*.json",
|
||||||
|
"!**/known-checksums.*"
|
||||||
|
],
|
||||||
|
"maxSize": 2097152
|
||||||
|
},
|
||||||
|
"formatter": {
|
||||||
|
"enabled": true,
|
||||||
|
"indentStyle": "space"
|
||||||
|
},
|
||||||
|
"javascript": {
|
||||||
|
"formatter": {
|
||||||
|
"quoteStyle": "double",
|
||||||
|
"trailingCommas": "all"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"linter": {
|
||||||
|
"enabled": true,
|
||||||
|
"rules": {
|
||||||
|
"recommended": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"vcs": {
|
||||||
|
"clientKind": "git",
|
||||||
|
"enabled": true,
|
||||||
|
"useIgnoreFile": false
|
||||||
|
}
|
||||||
|
}
|
||||||
63325
dist/save-cache/index.cjs
generated
vendored
Normal file
63325
dist/save-cache/index.cjs
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
84957
dist/save-cache/index.js
generated
vendored
84957
dist/save-cache/index.js
generated
vendored
File diff suppressed because one or more lines are too long
1
dist/save-cache/index.js.map
generated
vendored
1
dist/save-cache/index.js.map
generated
vendored
File diff suppressed because one or more lines are too long
1
dist/save-cache/sourcemap-register.js
generated
vendored
1
dist/save-cache/sourcemap-register.js
generated
vendored
File diff suppressed because one or more lines are too long
453
dist/setup/37.index.js
generated
vendored
453
dist/setup/37.index.js
generated
vendored
@@ -1,453 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
exports.id = 37;
|
|
||||||
exports.ids = [37];
|
|
||||||
exports.modules = {
|
|
||||||
|
|
||||||
/***/ 4037:
|
|
||||||
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
|
|
||||||
|
|
||||||
__webpack_require__.r(__webpack_exports__);
|
|
||||||
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
||||||
/* harmony export */ "toFormData": () => (/* binding */ toFormData)
|
|
||||||
/* harmony export */ });
|
|
||||||
/* harmony import */ var fetch_blob_from_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2777);
|
|
||||||
/* harmony import */ var formdata_polyfill_esm_min_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(8010);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let s = 0;
|
|
||||||
const S = {
|
|
||||||
START_BOUNDARY: s++,
|
|
||||||
HEADER_FIELD_START: s++,
|
|
||||||
HEADER_FIELD: s++,
|
|
||||||
HEADER_VALUE_START: s++,
|
|
||||||
HEADER_VALUE: s++,
|
|
||||||
HEADER_VALUE_ALMOST_DONE: s++,
|
|
||||||
HEADERS_ALMOST_DONE: s++,
|
|
||||||
PART_DATA_START: s++,
|
|
||||||
PART_DATA: s++,
|
|
||||||
END: s++
|
|
||||||
};
|
|
||||||
|
|
||||||
let f = 1;
|
|
||||||
const F = {
|
|
||||||
PART_BOUNDARY: f,
|
|
||||||
LAST_BOUNDARY: f *= 2
|
|
||||||
};
|
|
||||||
|
|
||||||
const LF = 10;
|
|
||||||
const CR = 13;
|
|
||||||
const SPACE = 32;
|
|
||||||
const HYPHEN = 45;
|
|
||||||
const COLON = 58;
|
|
||||||
const A = 97;
|
|
||||||
const Z = 122;
|
|
||||||
|
|
||||||
const lower = c => c | 0x20;
|
|
||||||
|
|
||||||
const noop = () => {};
|
|
||||||
|
|
||||||
class MultipartParser {
|
|
||||||
/**
|
|
||||||
* @param {string} boundary
|
|
||||||
*/
|
|
||||||
constructor(boundary) {
|
|
||||||
this.index = 0;
|
|
||||||
this.flags = 0;
|
|
||||||
|
|
||||||
this.onHeaderEnd = noop;
|
|
||||||
this.onHeaderField = noop;
|
|
||||||
this.onHeadersEnd = noop;
|
|
||||||
this.onHeaderValue = noop;
|
|
||||||
this.onPartBegin = noop;
|
|
||||||
this.onPartData = noop;
|
|
||||||
this.onPartEnd = noop;
|
|
||||||
|
|
||||||
this.boundaryChars = {};
|
|
||||||
|
|
||||||
boundary = '\r\n--' + boundary;
|
|
||||||
const ui8a = new Uint8Array(boundary.length);
|
|
||||||
for (let i = 0; i < boundary.length; i++) {
|
|
||||||
ui8a[i] = boundary.charCodeAt(i);
|
|
||||||
this.boundaryChars[ui8a[i]] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.boundary = ui8a;
|
|
||||||
this.lookbehind = new Uint8Array(this.boundary.length + 8);
|
|
||||||
this.state = S.START_BOUNDARY;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Uint8Array} data
|
|
||||||
*/
|
|
||||||
write(data) {
|
|
||||||
let i = 0;
|
|
||||||
const length_ = data.length;
|
|
||||||
let previousIndex = this.index;
|
|
||||||
let {lookbehind, boundary, boundaryChars, index, state, flags} = this;
|
|
||||||
const boundaryLength = this.boundary.length;
|
|
||||||
const boundaryEnd = boundaryLength - 1;
|
|
||||||
const bufferLength = data.length;
|
|
||||||
let c;
|
|
||||||
let cl;
|
|
||||||
|
|
||||||
const mark = name => {
|
|
||||||
this[name + 'Mark'] = i;
|
|
||||||
};
|
|
||||||
|
|
||||||
const clear = name => {
|
|
||||||
delete this[name + 'Mark'];
|
|
||||||
};
|
|
||||||
|
|
||||||
const callback = (callbackSymbol, start, end, ui8a) => {
|
|
||||||
if (start === undefined || start !== end) {
|
|
||||||
this[callbackSymbol](ui8a && ui8a.subarray(start, end));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const dataCallback = (name, clear) => {
|
|
||||||
const markSymbol = name + 'Mark';
|
|
||||||
if (!(markSymbol in this)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clear) {
|
|
||||||
callback(name, this[markSymbol], i, data);
|
|
||||||
delete this[markSymbol];
|
|
||||||
} else {
|
|
||||||
callback(name, this[markSymbol], data.length, data);
|
|
||||||
this[markSymbol] = 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for (i = 0; i < length_; i++) {
|
|
||||||
c = data[i];
|
|
||||||
|
|
||||||
switch (state) {
|
|
||||||
case S.START_BOUNDARY:
|
|
||||||
if (index === boundary.length - 2) {
|
|
||||||
if (c === HYPHEN) {
|
|
||||||
flags |= F.LAST_BOUNDARY;
|
|
||||||
} else if (c !== CR) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
|
||||||
break;
|
|
||||||
} else if (index - 1 === boundary.length - 2) {
|
|
||||||
if (flags & F.LAST_BOUNDARY && c === HYPHEN) {
|
|
||||||
state = S.END;
|
|
||||||
flags = 0;
|
|
||||||
} else if (!(flags & F.LAST_BOUNDARY) && c === LF) {
|
|
||||||
index = 0;
|
|
||||||
callback('onPartBegin');
|
|
||||||
state = S.HEADER_FIELD_START;
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (c !== boundary[index + 2]) {
|
|
||||||
index = -2;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (c === boundary[index + 2]) {
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case S.HEADER_FIELD_START:
|
|
||||||
state = S.HEADER_FIELD;
|
|
||||||
mark('onHeaderField');
|
|
||||||
index = 0;
|
|
||||||
// falls through
|
|
||||||
case S.HEADER_FIELD:
|
|
||||||
if (c === CR) {
|
|
||||||
clear('onHeaderField');
|
|
||||||
state = S.HEADERS_ALMOST_DONE;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
|
||||||
if (c === HYPHEN) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (c === COLON) {
|
|
||||||
if (index === 1) {
|
|
||||||
// empty header field
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
dataCallback('onHeaderField', true);
|
|
||||||
state = S.HEADER_VALUE_START;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
cl = lower(c);
|
|
||||||
if (cl < A || cl > Z) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case S.HEADER_VALUE_START:
|
|
||||||
if (c === SPACE) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
mark('onHeaderValue');
|
|
||||||
state = S.HEADER_VALUE;
|
|
||||||
// falls through
|
|
||||||
case S.HEADER_VALUE:
|
|
||||||
if (c === CR) {
|
|
||||||
dataCallback('onHeaderValue', true);
|
|
||||||
callback('onHeaderEnd');
|
|
||||||
state = S.HEADER_VALUE_ALMOST_DONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case S.HEADER_VALUE_ALMOST_DONE:
|
|
||||||
if (c !== LF) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
state = S.HEADER_FIELD_START;
|
|
||||||
break;
|
|
||||||
case S.HEADERS_ALMOST_DONE:
|
|
||||||
if (c !== LF) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
callback('onHeadersEnd');
|
|
||||||
state = S.PART_DATA_START;
|
|
||||||
break;
|
|
||||||
case S.PART_DATA_START:
|
|
||||||
state = S.PART_DATA;
|
|
||||||
mark('onPartData');
|
|
||||||
// falls through
|
|
||||||
case S.PART_DATA:
|
|
||||||
previousIndex = index;
|
|
||||||
|
|
||||||
if (index === 0) {
|
|
||||||
// boyer-moore derrived algorithm to safely skip non-boundary data
|
|
||||||
i += boundaryEnd;
|
|
||||||
while (i < bufferLength && !(data[i] in boundaryChars)) {
|
|
||||||
i += boundaryLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
i -= boundaryEnd;
|
|
||||||
c = data[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index < boundary.length) {
|
|
||||||
if (boundary[index] === c) {
|
|
||||||
if (index === 0) {
|
|
||||||
dataCallback('onPartData', true);
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
|
||||||
} else {
|
|
||||||
index = 0;
|
|
||||||
}
|
|
||||||
} else if (index === boundary.length) {
|
|
||||||
index++;
|
|
||||||
if (c === CR) {
|
|
||||||
// CR = part boundary
|
|
||||||
flags |= F.PART_BOUNDARY;
|
|
||||||
} else if (c === HYPHEN) {
|
|
||||||
// HYPHEN = end boundary
|
|
||||||
flags |= F.LAST_BOUNDARY;
|
|
||||||
} else {
|
|
||||||
index = 0;
|
|
||||||
}
|
|
||||||
} else if (index - 1 === boundary.length) {
|
|
||||||
if (flags & F.PART_BOUNDARY) {
|
|
||||||
index = 0;
|
|
||||||
if (c === LF) {
|
|
||||||
// unset the PART_BOUNDARY flag
|
|
||||||
flags &= ~F.PART_BOUNDARY;
|
|
||||||
callback('onPartEnd');
|
|
||||||
callback('onPartBegin');
|
|
||||||
state = S.HEADER_FIELD_START;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else if (flags & F.LAST_BOUNDARY) {
|
|
||||||
if (c === HYPHEN) {
|
|
||||||
callback('onPartEnd');
|
|
||||||
state = S.END;
|
|
||||||
flags = 0;
|
|
||||||
} else {
|
|
||||||
index = 0;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
index = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index > 0) {
|
|
||||||
// when matching a possible boundary, keep a lookbehind reference
|
|
||||||
// in case it turns out to be a false lead
|
|
||||||
lookbehind[index - 1] = c;
|
|
||||||
} else if (previousIndex > 0) {
|
|
||||||
// if our boundary turned out to be rubbish, the captured lookbehind
|
|
||||||
// belongs to partData
|
|
||||||
const _lookbehind = new Uint8Array(lookbehind.buffer, lookbehind.byteOffset, lookbehind.byteLength);
|
|
||||||
callback('onPartData', 0, previousIndex, _lookbehind);
|
|
||||||
previousIndex = 0;
|
|
||||||
mark('onPartData');
|
|
||||||
|
|
||||||
// reconsider the current character even so it interrupted the sequence
|
|
||||||
// it could be the beginning of a new sequence
|
|
||||||
i--;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case S.END:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error(`Unexpected state entered: ${state}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dataCallback('onHeaderField');
|
|
||||||
dataCallback('onHeaderValue');
|
|
||||||
dataCallback('onPartData');
|
|
||||||
|
|
||||||
// Update properties for the next call
|
|
||||||
this.index = index;
|
|
||||||
this.state = state;
|
|
||||||
this.flags = flags;
|
|
||||||
}
|
|
||||||
|
|
||||||
end() {
|
|
||||||
if ((this.state === S.HEADER_FIELD_START && this.index === 0) ||
|
|
||||||
(this.state === S.PART_DATA && this.index === this.boundary.length)) {
|
|
||||||
this.onPartEnd();
|
|
||||||
} else if (this.state !== S.END) {
|
|
||||||
throw new Error('MultipartParser.end(): stream ended unexpectedly');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function _fileName(headerValue) {
|
|
||||||
// matches either a quoted-string or a token (RFC 2616 section 19.5.1)
|
|
||||||
const m = headerValue.match(/\bfilename=("(.*?)"|([^()<>@,;:\\"/[\]?={}\s\t]+))($|;\s)/i);
|
|
||||||
if (!m) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const match = m[2] || m[3] || '';
|
|
||||||
let filename = match.slice(match.lastIndexOf('\\') + 1);
|
|
||||||
filename = filename.replace(/%22/g, '"');
|
|
||||||
filename = filename.replace(/&#(\d{4});/g, (m, code) => {
|
|
||||||
return String.fromCharCode(code);
|
|
||||||
});
|
|
||||||
return filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function toFormData(Body, ct) {
|
|
||||||
if (!/multipart/i.test(ct)) {
|
|
||||||
throw new TypeError('Failed to fetch');
|
|
||||||
}
|
|
||||||
|
|
||||||
const m = ct.match(/boundary=(?:"([^"]+)"|([^;]+))/i);
|
|
||||||
|
|
||||||
if (!m) {
|
|
||||||
throw new TypeError('no or bad content-type header, no multipart boundary');
|
|
||||||
}
|
|
||||||
|
|
||||||
const parser = new MultipartParser(m[1] || m[2]);
|
|
||||||
|
|
||||||
let headerField;
|
|
||||||
let headerValue;
|
|
||||||
let entryValue;
|
|
||||||
let entryName;
|
|
||||||
let contentType;
|
|
||||||
let filename;
|
|
||||||
const entryChunks = [];
|
|
||||||
const formData = new formdata_polyfill_esm_min_js__WEBPACK_IMPORTED_MODULE_1__/* .FormData */ .Ct();
|
|
||||||
|
|
||||||
const onPartData = ui8a => {
|
|
||||||
entryValue += decoder.decode(ui8a, {stream: true});
|
|
||||||
};
|
|
||||||
|
|
||||||
const appendToFile = ui8a => {
|
|
||||||
entryChunks.push(ui8a);
|
|
||||||
};
|
|
||||||
|
|
||||||
const appendFileToFormData = () => {
|
|
||||||
const file = new fetch_blob_from_js__WEBPACK_IMPORTED_MODULE_0__/* .File */ .$B(entryChunks, filename, {type: contentType});
|
|
||||||
formData.append(entryName, file);
|
|
||||||
};
|
|
||||||
|
|
||||||
const appendEntryToFormData = () => {
|
|
||||||
formData.append(entryName, entryValue);
|
|
||||||
};
|
|
||||||
|
|
||||||
const decoder = new TextDecoder('utf-8');
|
|
||||||
decoder.decode();
|
|
||||||
|
|
||||||
parser.onPartBegin = function () {
|
|
||||||
parser.onPartData = onPartData;
|
|
||||||
parser.onPartEnd = appendEntryToFormData;
|
|
||||||
|
|
||||||
headerField = '';
|
|
||||||
headerValue = '';
|
|
||||||
entryValue = '';
|
|
||||||
entryName = '';
|
|
||||||
contentType = '';
|
|
||||||
filename = null;
|
|
||||||
entryChunks.length = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
parser.onHeaderField = function (ui8a) {
|
|
||||||
headerField += decoder.decode(ui8a, {stream: true});
|
|
||||||
};
|
|
||||||
|
|
||||||
parser.onHeaderValue = function (ui8a) {
|
|
||||||
headerValue += decoder.decode(ui8a, {stream: true});
|
|
||||||
};
|
|
||||||
|
|
||||||
parser.onHeaderEnd = function () {
|
|
||||||
headerValue += decoder.decode();
|
|
||||||
headerField = headerField.toLowerCase();
|
|
||||||
|
|
||||||
if (headerField === 'content-disposition') {
|
|
||||||
// matches either a quoted-string or a token (RFC 2616 section 19.5.1)
|
|
||||||
const m = headerValue.match(/\bname=("([^"]*)"|([^()<>@,;:\\"/[\]?={}\s\t]+))/i);
|
|
||||||
|
|
||||||
if (m) {
|
|
||||||
entryName = m[2] || m[3] || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
filename = _fileName(headerValue);
|
|
||||||
|
|
||||||
if (filename) {
|
|
||||||
parser.onPartData = appendToFile;
|
|
||||||
parser.onPartEnd = appendFileToFormData;
|
|
||||||
}
|
|
||||||
} else if (headerField === 'content-type') {
|
|
||||||
contentType = headerValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
headerValue = '';
|
|
||||||
headerField = '';
|
|
||||||
};
|
|
||||||
|
|
||||||
for await (const chunk of Body) {
|
|
||||||
parser.write(chunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
parser.end();
|
|
||||||
|
|
||||||
return formData;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/***/ })
|
|
||||||
|
|
||||||
};
|
|
||||||
;
|
|
||||||
//# sourceMappingURL=37.index.js.map
|
|
||||||
1
dist/setup/37.index.js.map
generated
vendored
1
dist/setup/37.index.js.map
generated
vendored
File diff suppressed because one or more lines are too long
97113
dist/setup/index.cjs
generated
vendored
Normal file
97113
dist/setup/index.cjs
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
87823
dist/setup/index.js
generated
vendored
87823
dist/setup/index.js
generated
vendored
File diff suppressed because one or more lines are too long
1
dist/setup/index.js.map
generated
vendored
1
dist/setup/index.js.map
generated
vendored
File diff suppressed because one or more lines are too long
1056
dist/setup/licenses.txt
generated
vendored
1056
dist/setup/licenses.txt
generated
vendored
File diff suppressed because it is too large
Load Diff
1
dist/setup/sourcemap-register.js
generated
vendored
1
dist/setup/sourcemap-register.js
generated
vendored
File diff suppressed because one or more lines are too long
35031
dist/update-checksums/index.js
generated
vendored
35031
dist/update-checksums/index.js
generated
vendored
File diff suppressed because one or more lines are too long
1
dist/update-checksums/index.js.map
generated
vendored
1
dist/update-checksums/index.js.map
generated
vendored
File diff suppressed because one or more lines are too long
1
dist/update-checksums/sourcemap-register.js
generated
vendored
1
dist/update-checksums/sourcemap-register.js
generated
vendored
File diff suppressed because one or more lines are too long
34791
dist/update-default-version/index.js
generated
vendored
34791
dist/update-default-version/index.js
generated
vendored
File diff suppressed because one or more lines are too long
1
dist/update-default-version/index.js.map
generated
vendored
1
dist/update-default-version/index.js.map
generated
vendored
File diff suppressed because one or more lines are too long
1
dist/update-default-version/sourcemap-register.js
generated
vendored
1
dist/update-default-version/sourcemap-register.js
generated
vendored
File diff suppressed because one or more lines are too long
49624
dist/update-known-checksums/index.cjs
generated
vendored
Normal file
49624
dist/update-known-checksums/index.cjs
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
82
docs/advanced-version-configuration.md
Normal file
82
docs/advanced-version-configuration.md
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
# Advanced Version Configuration
|
||||||
|
|
||||||
|
This document covers advanced options for configuring which version of uv to install.
|
||||||
|
|
||||||
|
## Install the latest version
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Install the latest version of uv
|
||||||
|
uses: astral-sh/setup-uv@v7
|
||||||
|
with:
|
||||||
|
version: "latest"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Install a specific version
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Install a specific version of uv
|
||||||
|
uses: astral-sh/setup-uv@v7
|
||||||
|
with:
|
||||||
|
version: "0.4.4"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Install a version by supplying a semver range or pep440 specifier
|
||||||
|
|
||||||
|
You can specify a [semver range](https://github.com/npm/node-semver?tab=readme-ov-file#ranges)
|
||||||
|
or [pep440 specifier](https://peps.python.org/pep-0440/#version-specifiers)
|
||||||
|
to install the latest version that satisfies the range.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Install a semver range of uv
|
||||||
|
uses: astral-sh/setup-uv@v7
|
||||||
|
with:
|
||||||
|
version: ">=0.4.0"
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Pinning a minor version of uv
|
||||||
|
uses: astral-sh/setup-uv@v7
|
||||||
|
with:
|
||||||
|
version: "0.4.x"
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Install a pep440-specifier-satisfying version of uv
|
||||||
|
uses: astral-sh/setup-uv@v7
|
||||||
|
with:
|
||||||
|
version: ">=0.4.25,<0.5"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Resolution strategy
|
||||||
|
|
||||||
|
By default, when resolving version ranges, setup-uv will install the highest compatible version.
|
||||||
|
You can change this behavior using the `resolution-strategy` input:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Install the lowest compatible version of uv
|
||||||
|
uses: astral-sh/setup-uv@v7
|
||||||
|
with:
|
||||||
|
version: ">=0.4.0"
|
||||||
|
resolution-strategy: "lowest"
|
||||||
|
```
|
||||||
|
|
||||||
|
The supported resolution strategies are:
|
||||||
|
- `highest` (default): Install the latest version that satisfies the constraints
|
||||||
|
- `lowest`: Install the oldest version that satisfies the constraints
|
||||||
|
|
||||||
|
This can be useful for testing compatibility with older versions of uv, similar to uv's own `--resolution-strategy` option.
|
||||||
|
|
||||||
|
## Install a version defined in a requirements or config file
|
||||||
|
|
||||||
|
You can use the `version-file` input to specify a file that contains the version of uv to install.
|
||||||
|
This can either be a `pyproject.toml` or `uv.toml` file which defines a `required-version` or
|
||||||
|
uv defined as a dependency in `pyproject.toml` or `requirements.txt`.
|
||||||
|
|
||||||
|
[asdf](https://asdf-vm.com/) `.tool-versions` is also supported, but without the `ref` syntax.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Install uv based on the version defined in pyproject.toml
|
||||||
|
uses: astral-sh/setup-uv@v7
|
||||||
|
with:
|
||||||
|
version-file: "pyproject.toml"
|
||||||
|
```
|
||||||
225
docs/caching.md
Normal file
225
docs/caching.md
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
# Caching
|
||||||
|
|
||||||
|
This document covers all caching-related configuration options for setup-uv.
|
||||||
|
|
||||||
|
## Cache key
|
||||||
|
|
||||||
|
The cache key is automatically generated based on:
|
||||||
|
|
||||||
|
- **Architecture**: CPU architecture (e.g., `x86_64`, `aarch64`)
|
||||||
|
- **Platform**: OS platform type (e.g., `unknown-linux-gnu`, `unknown-linux-musl`, `apple-darwin`,
|
||||||
|
`pc-windows-msvc`)
|
||||||
|
- **OS version**: OS name and version (e.g., `ubuntu-22.04`, `macos-14`, `windows-2022`)
|
||||||
|
- **Python version**: The Python version in use
|
||||||
|
- **Cache options**: Whether pruning and Python caching are enabled
|
||||||
|
- **Dependency hash**: Hash of files matching `cache-dependency-glob`
|
||||||
|
- **Suffix**: Optional `cache-suffix` if provided
|
||||||
|
|
||||||
|
Including the OS version ensures that caches are not shared between different OS versions,
|
||||||
|
preventing binary incompatibility issues when runner images change.
|
||||||
|
|
||||||
|
The computed cache key is available as the `cache-key` output:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Setup uv
|
||||||
|
id: setup-uv
|
||||||
|
uses: astral-sh/setup-uv@v7
|
||||||
|
with:
|
||||||
|
enable-cache: true
|
||||||
|
- name: Print cache key
|
||||||
|
run: echo "Cache key: ${{ steps.setup-uv.outputs.cache-key }}"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Enable caching
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> The cache is pruned before it is uploaded to the GitHub Actions cache. This can lead to
|
||||||
|
> a small or empty cache. See [Disable cache pruning](#disable-cache-pruning) for more details.
|
||||||
|
|
||||||
|
If you enable caching, the [uv cache](https://docs.astral.sh/uv/concepts/cache/) will be uploaded to
|
||||||
|
the GitHub Actions cache. This can speed up runs that reuse the cache by several minutes.
|
||||||
|
Caching is enabled by default on GitHub-hosted runners.
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
>
|
||||||
|
> On self-hosted runners this is usually not needed since the cache generated by uv on the runner's
|
||||||
|
> filesystem is not removed after a run. For more details see [Local cache path](#local-cache-path).
|
||||||
|
|
||||||
|
You can optionally define a custom cache key suffix.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Enable caching and define a custom cache key suffix
|
||||||
|
id: setup-uv
|
||||||
|
uses: astral-sh/setup-uv@v7
|
||||||
|
with:
|
||||||
|
enable-cache: true
|
||||||
|
cache-suffix: "optional-suffix"
|
||||||
|
```
|
||||||
|
|
||||||
|
When the cache was successfully restored, the output `cache-hit` will be set to `true` and you can
|
||||||
|
use it in subsequent steps. For example, to use the cache in the above case:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Do something if the cache was restored
|
||||||
|
if: steps.setup-uv.outputs.cache-hit == 'true'
|
||||||
|
run: echo "Cache was restored"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Cache dependency glob
|
||||||
|
|
||||||
|
If you want to control when the GitHub Actions cache is invalidated, specify a glob pattern with the
|
||||||
|
`cache-dependency-glob` input. The GitHub Actions cache will be invalidated if any file matching the glob pattern
|
||||||
|
changes. If you use relative paths, they are relative to the working directory.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
>
|
||||||
|
> You can look up supported patterns [here](https://github.com/actions/toolkit/tree/main/packages/glob#patterns)
|
||||||
|
>
|
||||||
|
> The default is
|
||||||
|
> ```yaml
|
||||||
|
> cache-dependency-glob: |
|
||||||
|
> **/*requirements*.txt
|
||||||
|
> **/*requirements*.in
|
||||||
|
> **/*constraints*.txt
|
||||||
|
> **/*constraints*.in
|
||||||
|
> **/pyproject.toml
|
||||||
|
> **/uv.lock
|
||||||
|
> **/*.py.lock
|
||||||
|
> ```
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Define a cache dependency glob
|
||||||
|
uses: astral-sh/setup-uv@v7
|
||||||
|
with:
|
||||||
|
enable-cache: true
|
||||||
|
cache-dependency-glob: "**/pyproject.toml"
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Define a list of cache dependency globs
|
||||||
|
uses: astral-sh/setup-uv@v7
|
||||||
|
with:
|
||||||
|
enable-cache: true
|
||||||
|
cache-dependency-glob: |
|
||||||
|
**/requirements*.txt
|
||||||
|
**/pyproject.toml
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Define an absolute cache dependency glob
|
||||||
|
uses: astral-sh/setup-uv@v7
|
||||||
|
with:
|
||||||
|
enable-cache: true
|
||||||
|
cache-dependency-glob: "/tmp/my-folder/requirements*.txt"
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Never invalidate the cache
|
||||||
|
uses: astral-sh/setup-uv@v7
|
||||||
|
with:
|
||||||
|
enable-cache: true
|
||||||
|
cache-dependency-glob: ""
|
||||||
|
```
|
||||||
|
|
||||||
|
## Restore cache
|
||||||
|
|
||||||
|
Restoring an existing cache can be enabled or disabled with the `restore-cache` input.
|
||||||
|
By default, the cache will be restored.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Don't restore an existing cache
|
||||||
|
uses: astral-sh/setup-uv@v7
|
||||||
|
with:
|
||||||
|
enable-cache: true
|
||||||
|
restore-cache: false
|
||||||
|
```
|
||||||
|
|
||||||
|
## Save cache
|
||||||
|
|
||||||
|
You can also disable saving the cache after the run with the `save-cache` input.
|
||||||
|
This can be useful to save cache storage when you know you will not use the cache of the run again.
|
||||||
|
By default, the cache will be saved.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Don't save the cache after the run
|
||||||
|
uses: astral-sh/setup-uv@v7
|
||||||
|
with:
|
||||||
|
enable-cache: true
|
||||||
|
save-cache: false
|
||||||
|
```
|
||||||
|
|
||||||
|
## Local cache path
|
||||||
|
|
||||||
|
If caching is enabled, this action controls where uv stores its cache on the runner's filesystem
|
||||||
|
by setting `UV_CACHE_DIR`.
|
||||||
|
|
||||||
|
It defaults to `setup-uv-cache` in the `TMP` dir, `D:\a\_temp\setup-uv-cache` on Windows and
|
||||||
|
`/tmp/setup-uv-cache` on Linux/macOS. You can change the default by specifying the path with the
|
||||||
|
`cache-local-path` input.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> If the environment variable `UV_CACHE_DIR` is already set this action will not override it.
|
||||||
|
> If you configured [cache-dir](https://docs.astral.sh/uv/reference/settings/#cache-dir) in your
|
||||||
|
> config file then it is also respected and this action will not set `UV_CACHE_DIR`.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> If caching is disabled, you can still use `cache-local-path` so this action sets `UV_CACHE_DIR`
|
||||||
|
> to your desired path.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Define a custom uv cache path
|
||||||
|
uses: astral-sh/setup-uv@v7
|
||||||
|
with:
|
||||||
|
cache-local-path: "/path/to/cache"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Disable cache pruning
|
||||||
|
|
||||||
|
By default, the uv cache is pruned after every run, removing pre-built wheels, but retaining any
|
||||||
|
wheels that were built from source. On GitHub-hosted runners, it's typically faster to omit those
|
||||||
|
pre-built wheels from the cache (and instead re-download them from the registry on each run).
|
||||||
|
However, on self-hosted or local runners, preserving the cache may be more efficient. See
|
||||||
|
the [documentation](https://docs.astral.sh/uv/concepts/cache/#caching-in-continuous-integration) for
|
||||||
|
more information.
|
||||||
|
|
||||||
|
If you want to persist the entire cache across runs, disable cache pruning with the `prune-cache`
|
||||||
|
input.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Don't prune the cache before saving it
|
||||||
|
uses: astral-sh/setup-uv@v7
|
||||||
|
with:
|
||||||
|
enable-cache: true
|
||||||
|
prune-cache: false
|
||||||
|
```
|
||||||
|
|
||||||
|
## Cache Python installs
|
||||||
|
|
||||||
|
By default, the Python install dir (`uv python dir` / `UV_PYTHON_INSTALL_DIR`) is not cached,
|
||||||
|
for the same reason that the dependency cache is pruned.
|
||||||
|
If you want to cache Python installs along with your dependencies, set the `cache-python` input to `true`.
|
||||||
|
|
||||||
|
Note that this only caches Python versions that uv actually installs into `UV_PYTHON_INSTALL_DIR`
|
||||||
|
(i.e. managed Python installs). If uv uses a system Python, there may be nothing to cache.
|
||||||
|
To force managed Python installs, set `UV_PYTHON_PREFERENCE=only-managed`.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Cache Python installs
|
||||||
|
uses: astral-sh/setup-uv@v7
|
||||||
|
with:
|
||||||
|
enable-cache: true
|
||||||
|
cache-python: true
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ignore nothing to cache
|
||||||
|
|
||||||
|
By default, the action will fail if caching is enabled but there is nothing to upload (the uv cache directory does not exist).
|
||||||
|
If you want to ignore this, set the `ignore-nothing-to-cache` input to `true`.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Ignore nothing to cache
|
||||||
|
uses: astral-sh/setup-uv@v7
|
||||||
|
with:
|
||||||
|
enable-cache: true
|
||||||
|
ignore-nothing-to-cache: true
|
||||||
|
```
|
||||||
64
docs/customization.md
Normal file
64
docs/customization.md
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# Customization
|
||||||
|
|
||||||
|
This document covers advanced customization options including checksum validation, custom manifests, and problem matchers.
|
||||||
|
|
||||||
|
## Validate checksum
|
||||||
|
|
||||||
|
You can specify a checksum to validate the downloaded executable. Checksums up to the default version
|
||||||
|
are automatically verified by this action. The sha256 hashes can be found on the
|
||||||
|
[releases page](https://github.com/astral-sh/uv/releases) of the uv repo.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Install a specific version and validate the checksum
|
||||||
|
uses: astral-sh/setup-uv@v7
|
||||||
|
with:
|
||||||
|
version: "0.3.1"
|
||||||
|
checksum: "e11b01402ab645392c7ad6044db63d37e4fd1e745e015306993b07695ea5f9f8"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Manifest file
|
||||||
|
|
||||||
|
By default, setup-uv reads version metadata from
|
||||||
|
[`astral-sh/versions`](https://github.com/astral-sh/versions).
|
||||||
|
|
||||||
|
The `manifest-file` input lets you override that source with your own URL, for example to test
|
||||||
|
custom uv builds or alternate download locations.
|
||||||
|
|
||||||
|
### Format
|
||||||
|
|
||||||
|
The manifest file must use the same format as `astral-sh/versions`: one JSON object per line, where each object represents a version and its artifacts. The versions must be sorted in descending order. For example:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"version":"0.10.7","artifacts":[{"platform":"x86_64-unknown-linux-gnu","variant":"default","url":"https://example.com/uv-x86_64-unknown-linux-gnu.tar.gz","archive_format":"tar.gz","sha256":"..."}]}
|
||||||
|
{"version":"0.10.6","artifacts":[{"platform":"x86_64-unknown-linux-gnu","variant":"default","url":"https://example.com/uv-x86_64-unknown-linux-gnu.tar.gz","archive_format":"tar.gz","sha256":"..."}]}
|
||||||
|
```
|
||||||
|
|
||||||
|
setup-uv currently only supports `default` as the `variant`.
|
||||||
|
|
||||||
|
The `archive_format` field is currently ignored.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Use a custom manifest file
|
||||||
|
uses: astral-sh/setup-uv@v7
|
||||||
|
with:
|
||||||
|
manifest-file: "https://example.com/my-custom-manifest.ndjson"
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> When you use a custom manifest file and do not set the `version` input, setup-uv installs the
|
||||||
|
> latest version from that custom manifest.
|
||||||
|
|
||||||
|
## Add problem matchers
|
||||||
|
|
||||||
|
This action automatically adds
|
||||||
|
[problem matchers](https://github.com/actions/toolkit/blob/main/docs/problem-matchers.md)
|
||||||
|
for python errors.
|
||||||
|
|
||||||
|
You can disable this by setting the `add-problem-matchers` input to `false`.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Install the latest version of uv without problem matchers
|
||||||
|
uses: astral-sh/setup-uv@v7
|
||||||
|
with:
|
||||||
|
add-problem-matchers: false
|
||||||
|
```
|
||||||
160
docs/environment-and-tools.md
Normal file
160
docs/environment-and-tools.md
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
# Environment and Tools
|
||||||
|
|
||||||
|
This document covers environment activation, tool directory configuration, and authentication options.
|
||||||
|
|
||||||
|
## Activate environment
|
||||||
|
|
||||||
|
You can set `activate-environment` to `true` to automatically activate a venv.
|
||||||
|
This allows directly using it in later steps:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Install the latest version of uv and activate the environment
|
||||||
|
uses: astral-sh/setup-uv@v7
|
||||||
|
with:
|
||||||
|
activate-environment: true
|
||||||
|
- run: uv pip install pip
|
||||||
|
```
|
||||||
|
|
||||||
|
By default, the venv is created at `.venv` inside the `working-directory`.
|
||||||
|
|
||||||
|
You can customize the venv location with `venv-path`, for example to place it in the runner temp directory:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- uses: astral-sh/setup-uv@v7
|
||||||
|
with:
|
||||||
|
activate-environment: true
|
||||||
|
venv-path: ${{ runner.temp }}/custom-venv
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
>
|
||||||
|
> Activating the environment adds your dependencies to the `PATH`, which could break some workflows.
|
||||||
|
> For example, if you have a dependency which requires uv, e.g., `hatch`, activating the
|
||||||
|
> environment will shadow the `uv` binary installed by this action and may result in a different uv
|
||||||
|
> version being used.
|
||||||
|
>
|
||||||
|
> We do not recommend using this setting for most use-cases. Instead, use `uv run` to execute
|
||||||
|
> commands in the environment.
|
||||||
|
|
||||||
|
## GitHub authentication token
|
||||||
|
|
||||||
|
By default, this action resolves available uv versions from
|
||||||
|
[`astral-sh/versions`](https://github.com/astral-sh/versions), then downloads uv artifacts from
|
||||||
|
GitHub Releases.
|
||||||
|
|
||||||
|
You can provide a token via `github-token` to authenticate those downloads. By default, the
|
||||||
|
`GITHUB_TOKEN` secret is used, which is automatically provided by GitHub Actions.
|
||||||
|
|
||||||
|
If the default
|
||||||
|
[permissions for the GitHub token](https://docs.github.com/en/actions/security-for-github-actions/security-guides/automatic-token-authentication#permissions-for-the-github_token)
|
||||||
|
are not sufficient, you can provide a custom GitHub token with the necessary permissions.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Install the latest version of uv with a custom GitHub token
|
||||||
|
uses: astral-sh/setup-uv@v7
|
||||||
|
with:
|
||||||
|
github-token: ${{ secrets.CUSTOM_GITHUB_TOKEN }}
|
||||||
|
```
|
||||||
|
|
||||||
|
## UV_TOOL_DIR
|
||||||
|
|
||||||
|
On Windows `UV_TOOL_DIR` is set to `uv-tool-dir` in the `TMP` dir (e.g. `D:\a\_temp\uv-tool-dir`).
|
||||||
|
On GitHub hosted runners this is on the much faster `D:` drive.
|
||||||
|
|
||||||
|
On all other platforms the tool environments are placed in the
|
||||||
|
[default location](https://docs.astral.sh/uv/concepts/tools/#tools-directory).
|
||||||
|
|
||||||
|
If you want to change this behaviour (especially on self-hosted runners) you can use the `tool-dir`
|
||||||
|
input:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Install the latest version of uv with a custom tool dir
|
||||||
|
uses: astral-sh/setup-uv@v7
|
||||||
|
with:
|
||||||
|
tool-dir: "/path/to/tool/dir"
|
||||||
|
```
|
||||||
|
|
||||||
|
## UV_TOOL_BIN_DIR
|
||||||
|
|
||||||
|
On Windows `UV_TOOL_BIN_DIR` is set to `uv-tool-bin-dir` in the `TMP` dir (e.g.
|
||||||
|
`D:\a\_temp\uv-tool-bin-dir`). On GitHub hosted runners this is on the much faster `D:` drive. This
|
||||||
|
path is also automatically added to the PATH.
|
||||||
|
|
||||||
|
On all other platforms the tool binaries get installed to the
|
||||||
|
[default location](https://docs.astral.sh/uv/concepts/tools/#the-bin-directory).
|
||||||
|
|
||||||
|
If you want to change this behaviour (especially on self-hosted runners) you can use the
|
||||||
|
`tool-bin-dir` input:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Install the latest version of uv with a custom tool bin dir
|
||||||
|
uses: astral-sh/setup-uv@v7
|
||||||
|
with:
|
||||||
|
tool-bin-dir: "/path/to/tool-bin/dir"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tilde Expansion
|
||||||
|
|
||||||
|
This action supports expanding the `~` character to the user's home directory for the following inputs:
|
||||||
|
|
||||||
|
- `version-file`
|
||||||
|
- `cache-local-path`
|
||||||
|
- `tool-dir`
|
||||||
|
- `tool-bin-dir`
|
||||||
|
- `cache-dependency-glob`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Expand the tilde character
|
||||||
|
uses: astral-sh/setup-uv@v7
|
||||||
|
with:
|
||||||
|
cache-local-path: "~/path/to/cache"
|
||||||
|
tool-dir: "~/path/to/tool/dir"
|
||||||
|
tool-bin-dir: "~/path/to/tool-bin/dir"
|
||||||
|
cache-dependency-glob: "~/my-cache-buster"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ignore empty workdir
|
||||||
|
|
||||||
|
By default, the action will warn if the workdir is empty, because this is usually the case when
|
||||||
|
`actions/checkout` is configured to run after `setup-uv`, which is not supported.
|
||||||
|
|
||||||
|
If you want to ignore this, set the `ignore-empty-workdir` input to `true`.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Ignore empty workdir
|
||||||
|
uses: astral-sh/setup-uv@v7
|
||||||
|
with:
|
||||||
|
ignore-empty-workdir: true
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
This action sets several environment variables that influence uv's behavior and can be used by subsequent steps:
|
||||||
|
|
||||||
|
- `UV_PYTHON`: Set when `python-version` input is specified. Controls which Python version uv uses.
|
||||||
|
- `UV_CACHE_DIR`: Set when caching is enabled (unless already configured in uv config files). Controls where uv stores its cache.
|
||||||
|
- `UV_TOOL_DIR`: Set when `tool-dir` input is specified. Controls where uv installs tool environments.
|
||||||
|
- `UV_TOOL_BIN_DIR`: Set when `tool-bin-dir` input is specified. Controls where uv installs tool binaries.
|
||||||
|
- `UV_PYTHON_INSTALL_DIR`: Always set. Controls where uv installs Python versions.
|
||||||
|
- `VIRTUAL_ENV`: Set when `activate-environment` is true. Points to the activated virtual environment.
|
||||||
|
|
||||||
|
**Environment variables that affect the action behavior:**
|
||||||
|
|
||||||
|
- `UV_NO_MODIFY_PATH`: If set, prevents the action from modifying PATH. Cannot be used with `activate-environment`.
|
||||||
|
- `UV_CACHE_DIR`: If already set, the action will respect it instead of setting its own cache directory.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Example using environment variables
|
||||||
|
uses: astral-sh/setup-uv@v7
|
||||||
|
with:
|
||||||
|
python-version: "3.12"
|
||||||
|
tool-dir: "/custom/tool/dir"
|
||||||
|
enable-cache: true
|
||||||
|
|
||||||
|
- name: Check environment variables
|
||||||
|
run: |
|
||||||
|
echo "UV_PYTHON: $UV_PYTHON"
|
||||||
|
echo "UV_CACHE_DIR: $UV_CACHE_DIR"
|
||||||
|
echo "UV_TOOL_DIR: $UV_TOOL_DIR"
|
||||||
|
echo "UV_PYTHON_INSTALL_DIR: $UV_PYTHON_INSTALL_DIR"
|
||||||
|
```
|
||||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 36 KiB |
@@ -1,15 +0,0 @@
|
|||||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="545" height="153" viewBox="0 0 545 153">
|
|
||||||
<defs>
|
|
||||||
<style>
|
|
||||||
.cls-1{fill:#009ee3;}.cls-1,.cls-2,.cls-3{fill-rule:evenodd;}.cls-2{fill:#113984;}.cls-3{fill:#172c70;}</style>
|
|
||||||
</defs>
|
|
||||||
<title>paypal-seeklogo.com</title>
|
|
||||||
<path transform="scale(1, 1)" d="M0 24.48C0 10.9601 10.9601 0 24.48 0H520.2C533.72 0 544.68 10.9601 544.68 24.48V128.52C544.68 142.04 533.72 153 520.2 153H24.48C10.9601 153 0 142.04 0 128.52V24.48Z" fill="#ebf2ff"/>
|
|
||||||
<g transform="scale(0.8, 0.8) translate(45, 25)">
|
|
||||||
<path class="cls-1" d="M192.95,386.87h38.74c20.8,0,28.63,10.53,27.42,26-2,25.54-17.44,39.67-37.92,39.67H210.85c-2.81,0-4.7,1.86-5.46,6.9L201,488.74c-0.29,1.9-1.29,3-2.79,3.15H173.87c-2.29,0-3.1-1.75-2.5-5.54l14.84-93.93C186.79,388.66,188.85,386.87,192.95,386.87Z" transform="translate(-143.48 -354.54)"/>
|
|
||||||
<path class="cls-2" d="M361.14,385.13c13.07,0,25.13,7.09,23.48,24.76-2,21-13.25,32.62-31,32.67H338.11c-2.23,0-3.31,1.82-3.89,5.55l-3,19.07c-0.45,2.88-1.93,4.3-4.11,4.3H312.68c-2.3,0-3.1-1.47-2.59-4.76L322,390.29c0.59-3.76,2-5.16,4.57-5.16h34.54Zm-23.5,40.92h11.75c7.35-.28,12.23-5.37,12.72-14.55,0.3-5.67-3.53-9.73-9.62-9.7l-11.06.05-3.79,24.2h0Zm86.21,39.58c1.32-1.2,2.66-1.82,2.47-.34l-0.47,3.54c-0.24,1.85.49,2.83,2.21,2.83h12.82c2.16,0,3.21-.87,3.74-4.21l7.9-49.58c0.4-2.49-.21-3.71-2.1-3.71H436.32c-1.27,0-1.89.71-2.22,2.65l-0.52,3.05c-0.27,1.59-1,1.87-1.68.27-2.39-5.66-8.49-8.2-17-8-19.77.41-33.1,15.42-34.53,34.66-1.1,14.88,9.56,26.57,23.62,26.57,10.2,0,14.76-3,19.9-7.7h0ZM413.11,458c-8.51,0-14.44-6.79-13.21-15.11s9.19-15.11,17.7-15.11,14.44,6.79,13.21,15.11S421.63,458,413.11,458h0Zm64.5-44h-13c-2.68,0-3.77,2-2.92,4.46l16.14,47.26L462,488.21c-1.33,1.88-.3,3.59,1.57,3.59h14.61a4.47,4.47,0,0,0,4.34-2.13l49.64-71.2c1.53-2.19.81-4.49-1.7-4.49H516.63c-2.37,0-3.32.94-4.68,2.91l-20.7,30L482,416.82C481.46,415,480.11,414,477.62,414Z" transform="translate(-143.48 -354.54)"/>
|
|
||||||
<path class="cls-1" d="M583.8,385.13c13.07,0,25.13,7.09,23.48,24.76-2,21-13.25,32.62-31,32.67H560.78c-2.23,0-3.31,1.82-3.89,5.55l-3,19.07c-0.45,2.88-1.93,4.3-4.11,4.3H535.35c-2.3,0-3.1-1.47-2.59-4.76l11.93-76.45c0.59-3.76,2-5.16,4.57-5.16H583.8Zm-23.5,40.92h11.75c7.35-.28,12.23-5.37,12.72-14.55,0.3-5.67-3.53-9.73-9.62-9.7l-11.06.05-3.79,24.2h0Zm86.21,39.58c1.32-1.2,2.66-1.82,2.47-.34l-0.47,3.54c-0.24,1.85.49,2.83,2.21,2.83h12.82c2.16,0,3.21-.87,3.74-4.21l7.9-49.58c0.4-2.49-.21-3.71-2.1-3.71H659c-1.27,0-1.89.71-2.22,2.65l-0.52,3.05c-0.27,1.59-1,1.87-1.68.27-2.39-5.66-8.49-8.2-17-8-19.77.41-33.1,15.42-34.53,34.66-1.1,14.88,9.56,26.57,23.62,26.57,10.2,0,14.76-3,19.9-7.7h0ZM635.78,458c-8.51,0-14.44-6.79-13.21-15.11s9.19-15.11,17.7-15.11,14.44,6.79,13.21,15.11S644.29,458,635.78,458h0Zm59.13,13.74h-14.8a1.75,1.75,0,0,1-1.81-2l13-82.36a2.55,2.55,0,0,1,2.46-2h14.8a1.75,1.75,0,0,1,1.81,2l-13,82.36A2.55,2.55,0,0,1,694.91,471.76Z" transform="translate(-143.48 -354.54)"/>
|
|
||||||
<path class="cls-2" d="M168.72,354.54h38.78c10.92,0,23.88.35,32.54,8,5.79,5.11,8.83,13.24,8.13,22-2.38,29.61-20.09,46.2-43.85,46.2H185.2c-3.26,0-5.41,2.16-6.33,8l-5.34,34c-0.35,2.2-1.3,3.5-3,3.66H146.6c-2.65,0-3.59-2-2.9-6.42L160.9,361C161.59,356.62,164,354.54,168.72,354.54Z" transform="translate(-143.48 -354.54)"/>
|
|
||||||
<path class="cls-3" d="M179.43,435.29l6.77-42.87c0.59-3.76,2.65-5.56,6.75-5.56h38.74c6.41,0,11.6,1,15.66,2.85-3.89,26.36-20.94,41-43.26,41H185C182.44,430.72,180.56,432,179.43,435.29Z" transform="translate(-143.48 -354.54)"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 3.4 KiB |
@@ -1,9 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
clearMocks: true,
|
|
||||||
moduleFileExtensions: ['js', 'ts'],
|
|
||||||
testMatch: ['**/*.test.ts'],
|
|
||||||
transform: {
|
|
||||||
'^.+\\.ts$': 'ts-jest'
|
|
||||||
},
|
|
||||||
verbose: true
|
|
||||||
}
|
|
||||||
14
jest.config.mjs
Normal file
14
jest.config.mjs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { createDefaultEsmPreset } from "ts-jest";
|
||||||
|
|
||||||
|
const esmPreset = createDefaultEsmPreset({
|
||||||
|
tsconfig: "./tsconfig.json",
|
||||||
|
});
|
||||||
|
|
||||||
|
export default {
|
||||||
|
...esmPreset,
|
||||||
|
clearMocks: true,
|
||||||
|
moduleFileExtensions: ["js", "mjs", "ts"],
|
||||||
|
testEnvironment: "node",
|
||||||
|
testMatch: ["**/*.test.ts"],
|
||||||
|
verbose: true,
|
||||||
|
};
|
||||||
14848
package-lock.json
generated
14848
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
62
package.json
62
package.json
@@ -2,22 +2,22 @@
|
|||||||
"name": "setup-uv",
|
"name": "setup-uv",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
"description": "Set up your GitHub Actions workflow with a specific version of uv",
|
"description": "Set up your GitHub Actions workflow with a specific version of uv",
|
||||||
"main": "dist/index.js",
|
"main": "dist/setup/index.cjs",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build": "tsc --noEmit",
|
||||||
"format": "prettier --write '**/*.ts'",
|
"check": "biome check --write",
|
||||||
"format-check": "prettier --check '**/*.ts'",
|
"package": "node scripts/build-dist.mjs",
|
||||||
"lint": "eslint src/**/*.ts --fix",
|
"test:unit": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js",
|
||||||
"package": "ncc build -o dist/setup src/setup-uv.ts && ncc build -o dist/save-cache src/save-cache.ts && ncc build -o dist/update-default-version src/update-default-version.ts",
|
"test": "npm run build && npm run test:unit",
|
||||||
"test": "jest",
|
|
||||||
"act": "act pull_request -W .github/workflows/test.yml --container-architecture linux/amd64 -s GITHUB_TOKEN=\"$(gh auth token)\"",
|
"act": "act pull_request -W .github/workflows/test.yml --container-architecture linux/amd64 -s GITHUB_TOKEN=\"$(gh auth token)\"",
|
||||||
"update-default-version": "node dist/update-default-version/index.js src/download/checksum/known-checksums.ts action.yml \"$(gh auth token)\"",
|
"update-known-checksums": "RUNNER_TEMP=known_versions node dist/update-known-checksums/index.cjs src/download/checksum/known-checksums.ts",
|
||||||
"all": "npm run build && npm run format && npm run lint && npm run package && npm test"
|
"all": "npm run build && npm run check && npm run package && npm run test:unit"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/eifinger/setup-uv.git"
|
"url": "git+https://github.com/astral-sh/setup-uv.git"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"actions",
|
"actions",
|
||||||
@@ -28,30 +28,26 @@
|
|||||||
"author": "@eifinger",
|
"author": "@eifinger",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/cache": "^3.2.4",
|
"@actions/cache": "^6.0.0",
|
||||||
"@actions/core": "^1.10.1",
|
"@actions/core": "^3.0.0",
|
||||||
"@actions/exec": "^1.1.1",
|
"@actions/exec": "^3.0.0",
|
||||||
"@actions/github": "^6.0.0",
|
"@actions/glob": "^0.6.1",
|
||||||
"@actions/glob": "^0.4.0",
|
"@actions/io": "^3.0.2",
|
||||||
"@actions/io": "^1.1.3",
|
"@actions/tool-cache": "^4.0.0",
|
||||||
"@actions/tool-cache": "^2.0.1",
|
"@renovatebot/pep440": "^4.2.2",
|
||||||
"@octokit/rest": "^21.0.2"
|
"smol-toml": "^1.6.0",
|
||||||
|
"undici": "^7.24.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.14.9",
|
"@biomejs/biome": "^2.4.7",
|
||||||
"@types/semver": "^7.5.8",
|
"@types/js-yaml": "^4.0.9",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.15.0",
|
"@types/node": "^25.5.0",
|
||||||
"@typescript-eslint/parser": "^7.18.0",
|
"@types/semver": "^7.7.1",
|
||||||
"@vercel/ncc": "^0.38.1",
|
"@vercel/ncc": "^0.38.4",
|
||||||
"eslint": "^8.57.0",
|
"esbuild": "^0.27.4",
|
||||||
"eslint-plugin-github": "^5.0.1",
|
"jest": "^30.3.0",
|
||||||
"eslint-plugin-import": "^2.29.1",
|
"js-yaml": "^4.1.1",
|
||||||
"eslint-plugin-jest": "^28.8.2",
|
"ts-jest": "^29.4.6",
|
||||||
"eslint-plugin-prettier": "^5.2.1",
|
"typescript": "^5.9.3"
|
||||||
"jest": "^29.7.0",
|
|
||||||
"js-yaml": "^4.1.0",
|
|
||||||
"prettier": "^3.3.3",
|
|
||||||
"ts-jest": "^29.2.5",
|
|
||||||
"typescript": "^5.4.5"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
33
scripts/build-dist.mjs
Normal file
33
scripts/build-dist.mjs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { rm } from "node:fs/promises";
|
||||||
|
import { build } from "esbuild";
|
||||||
|
|
||||||
|
const builds = [
|
||||||
|
{
|
||||||
|
entryPoints: ["src/setup-uv.ts"],
|
||||||
|
outfile: "dist/setup/index.cjs",
|
||||||
|
staleOutfiles: ["dist/setup/index.mjs"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
entryPoints: ["src/save-cache.ts"],
|
||||||
|
outfile: "dist/save-cache/index.cjs",
|
||||||
|
staleOutfiles: ["dist/save-cache/index.mjs"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
entryPoints: ["src/update-known-checksums.ts"],
|
||||||
|
outfile: "dist/update-known-checksums/index.cjs",
|
||||||
|
staleOutfiles: ["dist/update-known-checksums/index.mjs"],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const { staleOutfiles, ...options } of builds) {
|
||||||
|
await Promise.all(
|
||||||
|
staleOutfiles.map((outfile) => rm(outfile, { force: true })),
|
||||||
|
);
|
||||||
|
await build({
|
||||||
|
bundle: true,
|
||||||
|
format: "cjs",
|
||||||
|
platform: "node",
|
||||||
|
target: "node24",
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
}
|
||||||
146
src/cache/restore-cache.ts
vendored
146
src/cache/restore-cache.ts
vendored
@@ -1,65 +1,119 @@
|
|||||||
import * as cache from '@actions/cache'
|
import * as cache from "@actions/cache";
|
||||||
import * as glob from '@actions/glob'
|
import * as core from "@actions/core";
|
||||||
import * as core from '@actions/core'
|
import { hashFiles } from "../hash/hash-files";
|
||||||
import path from 'path'
|
import {
|
||||||
import {cacheDependencyGlob, cacheLocalPath, cacheSuffix} from '../utils/inputs'
|
cacheDependencyGlob,
|
||||||
import {getArch, getPlatform} from '../utils/platforms'
|
cacheLocalPath,
|
||||||
|
cachePython,
|
||||||
|
cacheSuffix,
|
||||||
|
pruneCache,
|
||||||
|
pythonDir,
|
||||||
|
restoreCache as shouldRestoreCache,
|
||||||
|
} from "../utils/inputs";
|
||||||
|
import { getArch, getOSNameVersion, getPlatform } from "../utils/platforms";
|
||||||
|
|
||||||
export const STATE_CACHE_KEY = 'cache-key'
|
export const STATE_CACHE_KEY = "cache-key";
|
||||||
export const STATE_CACHE_MATCHED_KEY = 'cache-matched-key'
|
export const STATE_CACHE_MATCHED_KEY = "cache-matched-key";
|
||||||
const CACHE_VERSION = '1'
|
export const STATE_PYTHON_CACHE_MATCHED_KEY = "python-cache-matched-key";
|
||||||
|
|
||||||
export async function restoreCache(version: string): Promise<void> {
|
const CACHE_VERSION = "2";
|
||||||
const cacheKey = await computeKeys(version)
|
|
||||||
|
|
||||||
let matchedKey: string | undefined
|
export async function restoreCache(pythonVersion?: string): Promise<void> {
|
||||||
core.info(
|
const cacheKey = await computeKeys(pythonVersion);
|
||||||
`Trying to restore uv cache from GitHub Actions cache with key: ${cacheKey}`
|
core.saveState(STATE_CACHE_KEY, cacheKey);
|
||||||
)
|
core.setOutput("cache-key", cacheKey);
|
||||||
try {
|
|
||||||
matchedKey = await cache.restoreCache([cacheLocalPath], cacheKey)
|
if (!shouldRestoreCache) {
|
||||||
} catch (err) {
|
core.info("restore-cache is false. Skipping restore cache step.");
|
||||||
const message = (err as Error).message
|
core.setOutput("python-cache-hit", false);
|
||||||
core.warning(message)
|
return;
|
||||||
core.setOutput('cache-hit', false)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
core.saveState(STATE_CACHE_KEY, cacheKey)
|
if (cacheLocalPath === undefined) {
|
||||||
|
throw new Error(
|
||||||
|
"cache-local-path is not set. Cannot restore cache without a valid cache path.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
handleMatchResult(matchedKey, cacheKey)
|
await restoreCacheFromKey(
|
||||||
|
cacheKey,
|
||||||
|
cacheLocalPath.path,
|
||||||
|
STATE_CACHE_MATCHED_KEY,
|
||||||
|
"cache-hit",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (cachePython) {
|
||||||
|
await restoreCacheFromKey(
|
||||||
|
`${cacheKey}-python`,
|
||||||
|
pythonDir,
|
||||||
|
STATE_PYTHON_CACHE_MATCHED_KEY,
|
||||||
|
"python-cache-hit",
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
core.setOutput("python-cache-hit", false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function computeKeys(version: string): Promise<string> {
|
async function restoreCacheFromKey(
|
||||||
let cacheDependencyPathHash = '-'
|
cacheKey: string,
|
||||||
if (cacheDependencyGlob !== '') {
|
cachePath: string,
|
||||||
const fullCacheDependencyGlob = `${process.env['GITHUB_WORKSPACE']}${path.sep}${cacheDependencyGlob}`
|
stateKey: string,
|
||||||
cacheDependencyPathHash += await glob.hashFiles(fullCacheDependencyGlob)
|
outputKey: string,
|
||||||
if (cacheDependencyPathHash === '-') {
|
): Promise<void> {
|
||||||
throw new Error(
|
core.info(
|
||||||
`No file in ${process.cwd()} matched to [${cacheDependencyGlob}], make sure you have checked out the target repository`
|
`Trying to restore cache from GitHub Actions cache with key: ${cacheKey}`,
|
||||||
)
|
);
|
||||||
|
let matchedKey: string | undefined;
|
||||||
|
try {
|
||||||
|
matchedKey = await cache.restoreCache([cachePath], cacheKey);
|
||||||
|
} catch (err) {
|
||||||
|
const message = (err as Error).message;
|
||||||
|
core.warning(message);
|
||||||
|
core.setOutput(outputKey, false);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
cacheDependencyPathHash += 'no-dependency-glob'
|
handleMatchResult(matchedKey, cacheKey, stateKey, outputKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function computeKeys(pythonVersion?: string): Promise<string> {
|
||||||
|
let cacheDependencyPathHash = "-";
|
||||||
|
if (cacheDependencyGlob !== "") {
|
||||||
|
core.info(
|
||||||
|
`Searching files using cache dependency glob: ${cacheDependencyGlob.split("\n").join(",")}`,
|
||||||
|
);
|
||||||
|
cacheDependencyPathHash += await hashFiles(cacheDependencyGlob, true);
|
||||||
|
if (cacheDependencyPathHash === "-") {
|
||||||
|
core.warning(
|
||||||
|
`No file matched to [${cacheDependencyGlob.split("\n").join(",")}]. The cache will never get invalidated. Make sure you have checked out the target repository and configured the cache-dependency-glob input correctly.`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const suffix = cacheSuffix ? `-${cacheSuffix}` : ''
|
}
|
||||||
return `setup-uv-${CACHE_VERSION}-${getArch()}-${getPlatform()}-${version}${cacheDependencyPathHash}${suffix}`
|
if (cacheDependencyPathHash === "-") {
|
||||||
|
cacheDependencyPathHash = "-no-dependency-glob";
|
||||||
|
}
|
||||||
|
const suffix = cacheSuffix ? `-${cacheSuffix}` : "";
|
||||||
|
const version = pythonVersion ?? "unknown";
|
||||||
|
const platform = await getPlatform();
|
||||||
|
const osNameVersion = getOSNameVersion();
|
||||||
|
const pruned = pruneCache ? "-pruned" : "";
|
||||||
|
const python = cachePython ? "-py" : "";
|
||||||
|
return `setup-uv-${CACHE_VERSION}-${getArch()}-${platform}-${osNameVersion}-${version}${pruned}${python}${cacheDependencyPathHash}${suffix}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleMatchResult(
|
function handleMatchResult(
|
||||||
matchedKey: string | undefined,
|
matchedKey: string | undefined,
|
||||||
primaryKey: string
|
primaryKey: string,
|
||||||
|
stateKey: string,
|
||||||
|
outputKey: string,
|
||||||
): void {
|
): void {
|
||||||
if (!matchedKey) {
|
if (!matchedKey) {
|
||||||
core.info(`No GitHub Actions cache found for key: ${primaryKey}`)
|
core.info(`No GitHub Actions cache found for key: ${primaryKey}`);
|
||||||
core.setOutput('cache-hit', false)
|
core.setOutput(outputKey, false);
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
core.saveState(STATE_CACHE_MATCHED_KEY, matchedKey)
|
core.saveState(stateKey, matchedKey);
|
||||||
core.info(
|
core.info(`cache restored from GitHub Actions cache with key: ${matchedKey}`);
|
||||||
`uv cache restored from GitHub Actions cache with key: ${matchedKey}`
|
core.setOutput(outputKey, true);
|
||||||
)
|
|
||||||
core.setOutput('cache-hit', true)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,55 +1,59 @@
|
|||||||
import * as fs from 'fs'
|
import * as crypto from "node:crypto";
|
||||||
import * as crypto from 'crypto'
|
import * as fs from "node:fs";
|
||||||
|
|
||||||
import * as core from '@actions/core'
|
import * as core from "@actions/core";
|
||||||
import {KNOWN_CHECKSUMS} from './known-checksums'
|
import type { Architecture, Platform } from "../../utils/platforms";
|
||||||
import {Architecture, Platform} from '../../utils/platforms'
|
import { KNOWN_CHECKSUMS } from "./known-checksums";
|
||||||
|
|
||||||
export async function validateChecksum(
|
export async function validateChecksum(
|
||||||
checkSum: string | undefined,
|
checksum: string | undefined,
|
||||||
downloadPath: string,
|
downloadPath: string,
|
||||||
arch: Architecture,
|
arch: Architecture,
|
||||||
platform: Platform,
|
platform: Platform,
|
||||||
version: string
|
version: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
let isValid = true
|
const key = `${arch}-${platform}-${version}`;
|
||||||
if (checkSum !== undefined && checkSum !== '') {
|
const hasProvidedChecksum = checksum !== undefined && checksum !== "";
|
||||||
isValid = await validateFileCheckSum(downloadPath, checkSum)
|
const checksumToUse = hasProvidedChecksum ? checksum : KNOWN_CHECKSUMS[key];
|
||||||
} else {
|
|
||||||
core.debug(`Checksum not provided. Checking known checksums.`)
|
if (checksumToUse === undefined) {
|
||||||
const key = `${arch}-${platform}-${version}`
|
core.debug(`No checksum found for ${key}.`);
|
||||||
if (key in KNOWN_CHECKSUMS) {
|
return;
|
||||||
const knownChecksum = KNOWN_CHECKSUMS[`${arch}-${platform}-${version}`]
|
|
||||||
core.debug(`Checking checksum for ${arch}-${platform}-${version}.`)
|
|
||||||
isValid = await validateFileCheckSum(downloadPath, knownChecksum)
|
|
||||||
} else {
|
|
||||||
core.debug(`No known checksum found for ${key}.`)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const checksumSource = hasProvidedChecksum
|
||||||
|
? "provided checksum"
|
||||||
|
: `KNOWN_CHECKSUMS entry for ${key}`;
|
||||||
|
|
||||||
|
core.debug(`Validating checksum using ${checksumSource}.`);
|
||||||
|
const isValid = await validateFileCheckSum(downloadPath, checksumToUse);
|
||||||
|
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
throw new Error(`Checksum for ${downloadPath} did not match ${checkSum}.`)
|
throw new Error(
|
||||||
|
`Checksum for ${downloadPath} did not match ${checksumToUse}.`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
core.debug(`Checksum for ${downloadPath} is valid.`)
|
|
||||||
|
core.debug(`Checksum for ${downloadPath} is valid.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function validateFileCheckSum(
|
async function validateFileCheckSum(
|
||||||
filePath: string,
|
filePath: string,
|
||||||
expected: string
|
expected: string,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const hash = crypto.createHash('sha256')
|
const hash = crypto.createHash("sha256");
|
||||||
const stream = fs.createReadStream(filePath)
|
const stream = fs.createReadStream(filePath);
|
||||||
stream.on('error', err => reject(err))
|
stream.on("error", (err) => reject(err));
|
||||||
stream.on('data', chunk => hash.update(chunk))
|
stream.on("data", (chunk) => hash.update(chunk));
|
||||||
stream.on('end', () => {
|
stream.on("end", () => {
|
||||||
const actual = hash.digest('hex')
|
const actual = hash.digest("hex");
|
||||||
resolve(actual === expected)
|
resolve(actual === expected);
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isknownVersion(version: string): boolean {
|
export function isknownVersion(version: string): boolean {
|
||||||
const pattern = new RegExp(`^.*-.*-${version}$`)
|
const pattern = new RegExp(`^.*-.*-${version}$`);
|
||||||
return Object.keys(KNOWN_CHECKSUMS).some(key => pattern.test(key))
|
return Object.keys(KNOWN_CHECKSUMS).some((key) => pattern.test(key));
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user