Skip to content

Commit

Permalink
feat: improve version detection (#41)
Browse files Browse the repository at this point in the history
  • Loading branch information
nhedger authored Jan 13, 2024
1 parent db0d1fa commit 7702017
Show file tree
Hide file tree
Showing 20 changed files with 350 additions and 417 deletions.
17 changes: 13 additions & 4 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ on:
pull_request:
workflow_dispatch:

env:
# This version is used when running the tests to ensure that the correct version is installed.
# It is the version that the test expect to find during automatic version detection. It is important
# that this version is NOT the latest version of the CLI because some tests may still pass if the
# automatic version detection fails and falls back to the latest version.
BIOME_EXPECTED_VERSION: 1.5.0

jobs:
test-specific:
name: Specific version
Expand Down Expand Up @@ -56,7 +63,7 @@ jobs:
- name: Check equality
shell: bash
run: |
if [ "Version: 1.5.1" == "${{ steps.version.outputs.version }}" ]; then
if [ "Version: ${{ env.BIOME_EXPECTED_VERSION }}" == "${{ steps.version.outputs.version }}" ]; then
exit 0
else
echo "Versions do not match"
Expand Down Expand Up @@ -84,7 +91,7 @@ jobs:
- name: Check equality
shell: bash
run: |
if [ "Version: 1.5.1" == "${{ steps.version.outputs.version }}" ]; then
if [ "Version: ${{ env.BIOME_EXPECTED_VERSION }}" == "${{ steps.version.outputs.version }}" ]; then
exit 0
else
echo "Versions do not match"
Expand Down Expand Up @@ -112,7 +119,7 @@ jobs:
- name: Check equality
shell: bash
run: |
if [ "Version: 1.5.1" == "${{ steps.version.outputs.version }}" ]; then
if [ "Version: ${{ env.BIOME_EXPECTED_VERSION }}" == "${{ steps.version.outputs.version }}" ]; then
exit 0
else
echo "Versions do not match"
Expand Down Expand Up @@ -140,7 +147,7 @@ jobs:
- name: Check equality
shell: bash
run: |
if [ "Version: 1.5.1" == "${{ steps.version.outputs.version }}" ]; then
if [ "Version: ${{ env.BIOME_EXPECTED_VERSION }}" == "${{ steps.version.outputs.version }}" ]; then
exit 0
else
echo "Versions do not match"
Expand All @@ -159,6 +166,8 @@ jobs:
uses: actions/checkout@v4
- name: Setup Biome CLI
uses: ./
with:
working-dir: "test/fixtures/fallback"
- name: Retrieve the version
id: version
shell: bash
Expand Down
15 changes: 5 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,8 @@ The following inputs are supported.
To automatically determine the version of Biome to install based on the project's dependencies, you can simply omit the `version` input.

The action will search for the version of the `@biomejs/biome` dependency in the lockfiles of popular package managers such as npm, yarn, pnpm, and bun. It will then install that specific version of the Biome CLI.

> [!IMPORTANT]
> <img src="https://bun.sh/logo.svg" width="16"> [Bun](https://bun.sh) users must configure Bun to output a yarn lockfile because this action cannot yet read bun's binary lockfile format.
> An easy way to do this is to add the following to your `bunfig.toml` file:
> ```toml
> [install.lockfile]
> print = "yarn"

If no version of the Biome CLI is found in the lockfiles, the action will install the latest version of the Biome CLI.
The action will look for the version of the `@biomejs/biome` dependency in the lockfiles of popular package managers such as npm, yarn, pnpm, and bun. If the version cannot be found in the lockfiles, the action will attempt to retrieve the version from the `package.json` file, and as a last
resort, it will install the latest version of the Biome CLI.

```yaml
- name: Setup Biome CLI
Expand All @@ -60,6 +52,9 @@ If no version of the Biome CLI is found in the lockfiles, the action will instal
run: biome ci .
```

> [!IMPORTANT]
> We recommend that you *pin* the version of `@biomejs/biome` in your project's dependencies. If you provide a semver range, and automatic version detection falls back to reading the `package.json file`, the highest version within the range will be used. See the [versioning documentation](https://biomejs.dev/internals/versioning/) for more information.

### Latest version

Setup the latest version of the Biome CLI.
Expand Down
56 changes: 50 additions & 6 deletions dist/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -22447,7 +22447,7 @@ const getInput = (name) => {
return (0,core.getInput)(name) === "" ? void 0 : (0,core.getInput)(name);
};

const getBiomeVersion = async () => {
const getBiomeVersion = async (octokit) => {
let root = getInput("working-dir");
if (!root) {
root = process.cwd();
Expand All @@ -22461,7 +22461,7 @@ const getBiomeVersion = async () => {
"The specified working directory does not exist. Using the current working directory instead."
);
}
return getInput("version") ?? await extractVersionFromNpmLockFile(root) ?? await extractVersionFromPnpmLockFile(root) ?? await extractVersionFromYarnLockFile(root) ?? "latest";
return getInput("version") ?? await extractVersionFromNpmLockFile(root) ?? await extractVersionFromPnpmLockFile(root) ?? await extractVersionFromYarnLockFile(root) ?? await extractVersionFromPackageManifest(root, octokit) ?? "latest";
};
const extractVersionFromNpmLockFile = async (root) => {
try {
Expand Down Expand Up @@ -22496,15 +22496,59 @@ const extractVersionFromYarnLockFile = async (root) => {
return void 0;
}
};
const extractVersionFromPackageManifest = async (root, octokit) => {
try {
const manifest = JSON.parse(
await (0,promises_namespaceObject.readFile)((0,external_node_path_namespaceObject.join)(root, "package.json"), "utf8")
);
const versionSpecifier = manifest.devDependencies?.["@biomejs/biome"] ?? manifest.dependencies?.["@biomejs/biome"];
if (!versionSpecifier) {
return void 0;
}
if ((0,semver.valid)(versionSpecifier)) {
return versionSpecifier;
}
if ((0,semver.validRange)(versionSpecifier)) {
(0,core.warning)(
`Please consider pinning the version of @biomejs/biome in your package.json file.
See https://biomejs.dev/internals/versioning/ for more information.`,
{ title: "Biome version range detected" }
);
const versions = await fetchBiomeVersions(octokit);
if (!versions) {
return void 0;
}
return (0,semver.maxSatisfying)(versions, versionSpecifier)?.version ?? void 0;
}
} catch {
return void 0;
}
};
const fetchBiomeVersions = async (octokit) => {
try {
const releases = await octokit.paginate(
"GET /repos/{owner}/{repo}/releases",
{
owner: "biomejs",
repo: "biome"
}
);
const versions = releases.filter((release) => release.tag_name.startsWith("cli/")).map((release) => (0,semver.coerce)(release.tag_name));
return (0,semver.rsort)(versions);
} catch {
return void 0;
}
};

(async () => {
const octokit = new rest_dist_node.Octokit({
auth: (await (0,dist_node.createActionAuth)()()).token
});
await setup({
version: await getBiomeVersion(),
version: await getBiomeVersion(octokit),
platform: process.platform,
architecture: process.arch,
octokit: new rest_dist_node.Octokit({
auth: (await (0,dist_node.createActionAuth)()()).token
})
octokit
});
})();

Expand Down
10 changes: 6 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import { setup } from "./setup";
import { getBiomeVersion } from "./version";

(async () => {
const octokit = new Octokit({
auth: (await createActionAuth()()).token,
});

await setup({
version: await getBiomeVersion(),
version: await getBiomeVersion(octokit),
platform: process.platform as "linux" | "darwin" | "win32",
architecture: process.arch as "x64" | "arm64",
octokit: new Octokit({
auth: (await createActionAuth()()).token,
}),
octokit: octokit,
});
})();
98 changes: 97 additions & 1 deletion src/version.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import { existsSync } from "node:fs";
import { join } from "node:path";
import { info, warning } from "@actions/core";
import { Octokit } from "@octokit/rest";
import { readFile } from "fs/promises";
import {
SemVer,
coerce,
maxSatisfying,
rsort,
valid,
validRange,
} from "semver";
import { parse } from "yaml";
import { getInput } from "./helpers";

Expand All @@ -16,7 +25,7 @@ import { getInput } from "./helpers";
*
* @param projectRoot The root directory of the project. Defaults to the current working directory.
*/
export const getBiomeVersion = async (): Promise<string> => {
export const getBiomeVersion = async (octokit: Octokit): Promise<string> => {
let root = getInput("working-dir");

// If the working directory is not specified, we fallback to the current
Expand All @@ -42,6 +51,7 @@ export const getBiomeVersion = async (): Promise<string> => {
(await extractVersionFromNpmLockFile(root)) ??
(await extractVersionFromPnpmLockFile(root)) ??
(await extractVersionFromYarnLockFile(root)) ??
(await extractVersionFromPackageManifest(root, octokit)) ??
"latest"
);
};
Expand Down Expand Up @@ -102,3 +112,89 @@ const extractVersionFromYarnLockFile = async (
return undefined;
}
};

/**
* Extracts the Biome CLI version from the project's package.json file.
*
* This function attempts to extract the version of the `@biomejs/biome`
* package from the `package.json` file. If the package is not installed,
* or the version cannot be extracted, this function will return undefined.
*
* If the version is specified as a range, this function will return the
* highest available version that satisfies the range, if it exists, or
* undefined otherwise.
*/
const extractVersionFromPackageManifest = async (
root: string,
octokit: Octokit,
): Promise<string | undefined> => {
try {
const manifest = JSON.parse(
await readFile(join(root, "package.json"), "utf8"),
);

// The package should be installed as a devDependency, but we'll check
// both dependencies and devDependencies just in case.
const versionSpecifier =
manifest.devDependencies?.["@biomejs/biome"] ??
manifest.dependencies?.["@biomejs/biome"];

// Biome is not a dependency of the project.
if (!versionSpecifier) {
return undefined;
}

// If the version is specific, we return it directly.
if (valid(versionSpecifier)) {
return versionSpecifier;
}

// If the version is a range, return the highest available version.
if (validRange(versionSpecifier)) {
warning(
`Please consider pinning the version of @biomejs/biome in your package.json file.
See https://biomejs.dev/internals/versioning/ for more information.`,
{ title: "Biome version range detected" },
);

const versions = await fetchBiomeVersions(octokit);

if (!versions) {
return undefined;
}

return maxSatisfying(versions, versionSpecifier)?.version ?? undefined;
}
} catch {
return undefined;
}
};

/**
* Fetches the available versions of the Biome CLI from GitHub.
*
* This function will return the versions of the Biome CLI that are available
* on GitHub. This includes all versions that have been released, including
* pre-releases and draft releases.
*/
const fetchBiomeVersions = async (
octokit: Octokit,
): Promise<SemVer[] | undefined> => {
try {
const releases = await octokit.paginate(
"GET /repos/{owner}/{repo}/releases",
{
owner: "biomejs",
repo: "biome",
},
);

const versions = releases
.filter((release) => release.tag_name.startsWith("cli/"))
.map((release) => coerce(release.tag_name));

return rsort(versions as SemVer[]);
} catch {
return undefined;
}
};
Loading

0 comments on commit 7702017

Please sign in to comment.