Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 134 additions & 32 deletions .github/workflows/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@ const cacheDir = expr("env.DPRINT_CACHE_DIR");
// attestation (available from dprint 0.57.1) is verified, so nothing outside of
// GitHub is trusted; gh is available on all GitHub-hosted runners, and without
// it the asset is downloaded from GitHub releases unverified
const download = step({
name: "Download dprint",
id: "download",
const resolve = step({
name: "Resolve dprint version",
id: "resolve",
env: {
DPRINT_VERSION: inputs["dprint-version"],
VERIFY_ATTESTATION: inputs["verify-attestation"],
CACHE: inputs.cache,
GH_TOKEN: expr("github.token"),
},
run: [
Expand All @@ -65,41 +65,110 @@ const download = step({
` if ldd /bin/sh | grep -q musl; then target="$target-musl"; else target="$target-gnu"; fi`,
`fi`,
`asset="dprint-$target.zip"`,
`zip="$RUNNER_TEMP/$asset"`,
`verifiable=false`,
`if command -v gh > /dev/null; then`,
` version="\${DPRINT_VERSION:-$(gh release view --repo dprint/dprint --json tagName --jq .tagName)}"`,
` gh release download "$version" --repo dprint/dprint --pattern "$asset" --output "$zip" --clobber`,
` # releases before 0.57.1 don't have attestations`,
` if [ "$VERIFY_ATTESTATION" != "true" ]; then`,
` echo "Attestation verification is disabled."`,
` elif [ "$(printf '%s\\n' 0.57.1 "$version" | sort -V | head -n 1)" = "0.57.1" ]; then`,
` verifiable=true`,
` else`,
` echo "::warning title=dprint::dprint $version predates build provenance attestations, so $asset can't be verified. Upgrade to dprint 0.57.1 or later to have the download verified."`,
` fi`,
`version="$DPRINT_VERSION"`,
`digest=""`,
`if [ "$CACHE" = "true" ] && command -v gh > /dev/null; then`,
` # the cache key needs the exact version and a cached download is checked`,
` # against the release's digest, so look them up (the download step gets`,
` # the latest release itself otherwise)`,
` release=$(gh release view \${DPRINT_VERSION:+"$DPRINT_VERSION"} --repo dprint/dprint --json tagName,assets \\`,
` --jq "[.tagName, (.assets[] | select(.name == \\"$asset\\") | .digest // \\"\\")] | @tsv")`,
` IFS=$'\\t' read -r version digest <<< "$release"`,
`fi`,
`# releases before 0.57.1 don't have attestations (the latest always does)`,
`if [ -z "$version" ] || [ "$(printf '%s\\n' 0.57.1 "$version" | sort -V | head -n 1)" = "0.57.1" ]; then`,
` attested=true`,
`else`,
` if [ "$VERIFY_ATTESTATION" = "true" ]; then`,
` echo "::warning title=dprint::The GitHub CLI (gh) is not available on this runner, so $asset can't be verified. Install it to have the download verified."`,
` attested=false`,
`fi`,
`echo "dprint \${version:-latest} ($asset)"`,
`echo "version=$version" >> "$GITHUB_OUTPUT"`,
`echo "asset=$asset" >> "$GITHUB_OUTPUT"`,
`echo "digest=$digest" >> "$GITHUB_OUTPUT"`,
`echo "attested=$attested" >> "$GITHUB_OUTPUT"`,
`# the digest is in the key so a re-uploaded asset gets its own entry`,
`echo "cache-key=dprint-executable-$RUNNER_OS-$RUNNER_ARCH-$version-\${digest#sha256:}" >> "$GITHUB_OUTPUT"`,
],
outputs: ["version", "asset", "digest", "attested", "cache-key"] as const,
});

// the verified download is cached per version, and on a hit its digest is
// checked against the release's so the attestation verification can be skipped
const downloadDir = concat(expr("runner.temp"), "/dprint-download");
const cacheDownload = cacheEnabled.and(resolve.outputs.digest.notEquals(""));
const restoreDownload = step({
name: "Restore dprint download",
id: "restore-download",
if: cacheDownload,
uses: "actions/cache/restore@v5",
with: {
path: downloadDir,
key: resolve.outputs["cache-key"],
},
outputs: ["cache-hit"] as const,
}).dependsOn(resolve);

const download = step({
name: "Download dprint",
id: "download",
env: {
VERSION: resolve.outputs.version,
ASSET: resolve.outputs.asset,
DIGEST: resolve.outputs.digest,
ATTESTED: resolve.outputs.attested,
CACHE_HIT: restoreDownload.outputs["cache-hit"],
VERIFY_ATTESTATION: inputs["verify-attestation"],
GH_TOKEN: expr("github.token"),
},
run: [
`download_dir="$RUNNER_TEMP/dprint-download"`,
`mkdir -p "$download_dir"`,
`zip="$download_dir/$ASSET"`,
`# hashed via stdin because sha256sum escapes a file name containing a backslash`,
`sha256() { if command -v sha256sum > /dev/null; then sha256sum < "$1"; else shasum -a 256 < "$1"; fi | cut -d ' ' -f 1; }`,
`verify=false`,
`save=false`,
`if [ "$CACHE_HIT" = "true" ] && [ -f "$zip" ] && [ "sha256:$(sha256 "$zip")" = "$DIGEST" ]; then`,
` echo "Using the cached download of $ASSET for dprint $VERSION, which matches the release's digest."`,
`else`,
` if [ "$CACHE_HIT" = "true" ]; then`,
` echo "The cached download of $ASSET doesn't match the release's digest, so downloading it again."`,
` fi`,
` version="$DPRINT_VERSION"`,
` if [ -n "$version" ]; then`,
` url="https://github.com/dprint/dprint/releases/download/$version/$asset"`,
` if command -v gh > /dev/null; then`,
` gh release download \${VERSION:+"$VERSION"} --repo dprint/dprint --pattern "$ASSET" --output "$zip" --clobber`,
` # the download is cached when the release has a digest to check it against`,
` if [ -n "$DIGEST" ]; then save=true; fi`,
` if [ "$VERIFY_ATTESTATION" != "true" ]; then`,
` echo "Attestation verification is disabled."`,
` elif [ "$ATTESTED" = "true" ]; then`,
` verify=true`,
` else`,
` echo "::warning title=dprint::dprint $VERSION predates build provenance attestations, so $ASSET can't be verified. Upgrade to dprint 0.57.1 or later to have the download verified."`,
` fi`,
` else`,
` url="https://github.com/dprint/dprint/releases/latest/download/$asset"`,
` if [ "$VERIFY_ATTESTATION" = "true" ]; then`,
` echo "::warning title=dprint::The GitHub CLI (gh) is not available on this runner, so $ASSET can't be verified. Install it to have the download verified."`,
` fi`,
` if [ -n "$VERSION" ]; then`,
` url="https://github.com/dprint/dprint/releases/download/$VERSION/$ASSET"`,
` else`,
` url="https://github.com/dprint/dprint/releases/latest/download/$ASSET"`,
` fi`,
` # curl drops the authorization header on the redirect to the asset host`,
` curl -fsSL -H "Authorization: Bearer $GH_TOKEN" --output "$zip" "$url"`,
` fi`,
` curl -fsSL --output "$zip" "$url"`,
` echo "Downloaded $ASSET\${VERSION:+ for dprint $VERSION}."`,
`fi`,
`echo "Downloaded $asset\${version:+ for dprint $version}."`,
`echo "zip=$zip" >> "$GITHUB_OUTPUT"`,
`echo "verifiable=$verifiable" >> "$GITHUB_OUTPUT"`,
`echo "verify=$verify" >> "$GITHUB_OUTPUT"`,
`echo "save=$save" >> "$GITHUB_OUTPUT"`,
],
outputs: ["zip", "verifiable"] as const,
});
outputs: ["zip", "verify", "save"] as const,
}).dependsOn(resolve).comesAfter(restoreDownload);

const verify = step({
name: "Verify dprint attestation",
if: download.outputs.verifiable.equals("true"),
if: download.outputs.verify.equals("true"),
env: {
ZIP: download.outputs.zip,
GH_TOKEN: expr("github.token"),
Expand All @@ -110,8 +179,22 @@ const verify = step({
],
}).dependsOn(download);

// runs after the verification so a download that fails it is never cached;
// whether to verify is up to the user, and a cached download is checked
// against the release's digest on every hit either way
const saveDownload = step({
name: "Save dprint download",
if: cacheDownload.and(download.outputs.save.equals("true")),
uses: "actions/cache/save@v5",
with: {
path: downloadDir,
key: resolve.outputs["cache-key"],
},
}).dependsOn(download).comesAfter(verify);

const install = step({
name: "Install dprint",
id: "install",
env: { ZIP: download.outputs.zip },
run: [
`bin_dir="$HOME/.dprint/bin"`,
Expand All @@ -123,9 +206,12 @@ const install = step({
`else`,
` echo "$bin_dir" >> "$GITHUB_PATH"`,
`fi`,
`"$bin_dir/dprint" --version`,
`version=$("$bin_dir/dprint" --version | cut -d ' ' -f 2)`,
`echo "Installed dprint $version."`,
`echo "version=$version" >> "$GITHUB_OUTPUT"`,
],
}).dependsOn(download).comesAfter(verify);
outputs: ["version"] as const,
}).dependsOn(download).comesAfter(verify, saveDownload);

// the hash of the config file the check will use, or of every config file in
// the repo when dprint discovers the config itself (a remote config url can't
Expand Down Expand Up @@ -279,6 +365,10 @@ action({
author: "the dprint authors",
inputs,
outputs: {
"dprint-version": {
description: "The version of dprint that was installed",
value: install.outputs.version,
},
"cache-matched-key": {
description: "Key of the cache entry that was restored, if any",
value: restoreCache.outputs["cache-matched-key"],
Expand All @@ -289,7 +379,19 @@ action({
},
},
defaults: { run: { shell: "bash" } },
steps: [download, verify, install, restoreCache, hashCacheBefore, check, hashCacheAfter, saveCache],
steps: [
resolve,
restoreDownload,
download,
verify,
saveDownload,
install,
restoreCache,
hashCacheBefore,
check,
hashCacheAfter,
saveCache,
],
branding: { icon: "check-circle", color: "gray-dark" },
}).writeOrLint({
filePath: new URL("../../action.yml", import.meta.url),
Expand Down
70 changes: 35 additions & 35 deletions .github/workflows/ci.generated.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,14 @@ jobs:
- name: Check formatting latest
uses: ./
- name: Check formatting specific version
id: specific-version
uses: ./
with:
dprint-version: 0.57.1
- name: Verify the specific version was installed
env:
VERSION: "${{ steps.specific-version.outputs.dprint-version }}"
run: test "$VERSION" = "0.57.1"
- name: Check formatting specific config
uses: ./
with:
Expand All @@ -46,6 +51,24 @@ jobs:
with:
cache: true
dprint-version: 0.57.1
- name: Check formatting without attestation verification
uses: ./
with:
verify-attestation: false
- name: Check formatting without annotations
uses: ./
with:
annotations: false
- name: Check formatting with an unattested version
id: unattested-version
uses: ./
with:
dprint-version: 0.56.1
config-path: tests/legacy/dprint.json
- name: Verify the unattested version was installed
env:
VERSION: "${{ steps.unattested-version.outputs.dprint-version }}"
run: test "$VERSION" = "0.56.1"
- name: Make poorly-formatted json file
run: 'echo ''{"a": 1, "b": 2}'' > poorly-formatted.json'
- name: Check formatting with excludes
Expand All @@ -57,7 +80,7 @@ jobs:
test -n "$DPRINT_CACHE_DIR"
test -d "$DPRINT_CACHE_DIR/plugins"
dprint --version
cache-prime:
cache:
runs-on: "${{ matrix.os }}"
defaults:
run:
Expand All @@ -82,54 +105,31 @@ jobs:
} > cache-test.json
echo '{"a": 1, "b": 2}' > poorly-formatted.json
- name: Check formatting (expected to fail)
id: check
id: prime
uses: ./
continue-on-error: true
with:
cache: true
config-path: cache-test.json
- name: Verify the check failed and the cache was saved
env:
CACHE_CHANGED: "${{ steps.check.outputs.cache-changed }}"
CACHE_CHANGED: "${{ steps.prime.outputs.cache-changed }}"
run: |-
test "${{ steps.check.outcome }}" = "failure"
test "${{ steps.prime.outcome }}" = "failure"
test "$CACHE_CHANGED" = "true"
cache-hit:
needs:
- cache-prime
runs-on: "${{ matrix.os }}"
defaults:
run:
shell: bash
strategy:
matrix:
os:
- ubuntu-latest
- macOS-latest
- windows-latest
steps:
- name: Use LF line endings
run: |-
git config --global core.autocrlf false
git config --global core.eol lf
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Recreate unique config
run: |-
{
echo "// ${{ github.run_id }}-${{ github.run_attempt }}"
cat dprint.json
} > cache-test.json
- name: Check formatting
id: check
- name: Remove the poorly-formatted file
run: rm poorly-formatted.json
- name: Check formatting again
id: hit
uses: ./
with:
cache: true
config-path: cache-test.json
- name: Verify the cache saved by the failed cache-prime job was restored and not saved again
- name: Verify the cache saved by the failed check was restored and not saved again
env:
MATCHED_KEY: "${{ steps.check.outputs.cache-matched-key }}"
CACHE_CHANGED: "${{ steps.check.outputs.cache-changed }}"
EXPECTED_KEY: 'dprint-cache-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles(''cache-test.json'') }}-cache-prime-${{ github.run_id }}-${{ github.run_attempt }}'
MATCHED_KEY: "${{ steps.hit.outputs.cache-matched-key }}"
CACHE_CHANGED: "${{ steps.hit.outputs.cache-changed }}"
EXPECTED_KEY: 'dprint-cache-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles(''cache-test.json'') }}-${{ github.job }}-${{ github.run_id }}-${{ github.run_attempt }}'
run: |-
echo "matched key: $MATCHED_KEY"
echo "expected: $EXPECTED_KEY"
Expand Down
Loading
Loading