Skip to content
Closed
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
59 changes: 55 additions & 4 deletions src/detect.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,51 @@ async function detectGoMod(root, output) {
}
}

async function detectCargoToml(root, output) {
const content = await read(root, "Cargo.toml");
if (!content) return;
const lines = content.split(/\r?\n/);

for (const [index, line] of lines.entries()) {
const match = line.match(/^\s*rust-version\s*=\s*["']([^"']+)["']/);
if (match) {
add(output, "rust", "Cargo.toml", match[1], index + 1);
}
}
}

async function detectRustToolchain(root, output) {
const content = await read(root, "rust-toolchain.toml");
if (!content) return;
const lines = content.split(/\r?\n/);

for (const [index, line] of lines.entries()) {
const match = line.match(/^\s*channel\s*=\s*["']([^"']+)["']/);
if (match) {
add(output, "rust", "rust-toolchain.toml", match[1], index + 1);
}
}
}

async function detectGoMod(root, output) {
const content = await read(root, "go.mod");
if (!content) return;

const lines = content.split(/\r?\n/);

for (const [index, line] of lines.entries()) {
const go = line.match(/^\s*go\s+(\S+)/);
if (go) {
add(output, "go", "go.mod", go[1], index + 1);
}

const toolchain = line.match(/^\s*toolchain\s+go(\S+)/);
if (toolchain) {
add(output, "go", "go.mod", toolchain[1], index + 1);
}
}
}

async function detectToolVersions(root, output) {
const content = await read(root, ".tool-versions");
if (!content) return;
Expand Down Expand Up @@ -115,7 +160,7 @@ async function detectDockerfiles(root, output) {
const content = await read(root, entry.name);
for (const [index, line] of content.split(/\r?\n/).entries()) {
const match = line.match(
/^\s*FROM\s+(?:[^/\s]+\/)?(node|python|golang):([^\s@]+)/i,
/^\s*FROM\s+(?:[^/\s]+\/)?(node|python|golang|rust):([^\s@]+)/i,
);
if (match)
add(
Expand Down Expand Up @@ -151,9 +196,11 @@ async function detectGithubActions(root, output) {
let remaining = 0;

for (const [index, line] of lines.entries()) {
const action = line.match(/uses:\s*actions\/setup-(node|python|go)@/i);
const action = line.match(
/uses:\s*(?:actions\/setup-(node|python|go)|dtolnay\/rust-toolchain)@/i,
);
if (action) {
runtime = action[1].toLowerCase();
runtime = action[1] ? action[1].toLowerCase() : "rust";
remaining = 12;
continue;
}
Expand All @@ -162,7 +209,9 @@ async function detectGithubActions(root, output) {
continue;
}
remaining -= 1;
const declared = line.match(/(?:node-version|python-version|go-version):\s*["']?([^"'#\s]+)/i);
const declared = line.match(
/(?:node-version|python-version|go-version|toolchain):\s*["']?([^"'#\s]+)/i,
);
if (runtime && declared && !declared[1].startsWith("${{")) {
add(output, runtime, file, declared[1], index + 1);
runtime = null;
Expand All @@ -178,6 +227,8 @@ export async function detectDeclarations(root) {
detectPackageJson(root, output),
detectPyproject(root, output),
detectGoMod(root, output),
detectCargoToml(root, output),
detectRustToolchain(root, output),
detectToolVersions(root, output),
detectMise(root, output),
detectDockerfiles(root, output),
Expand Down
1 change: 1 addition & 0 deletions src/format.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const LABELS = {
go: "Go",
node: "Node.js",
python: "Python",
rust: "Rust",
};

export function formatHuman(result) {
Expand Down
15 changes: 12 additions & 3 deletions test/detect.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,39 @@ import { inspect } from "../src/index.js";

const here = path.dirname(fileURLToPath(import.meta.url));

test("finds aligned Node.js and Python declarations", async () => {
test("finds aligned Node.js, golang, Rust, and Python declarations", async () => {
const result = await inspect(path.join(here, "fixtures", "aligned"));

assert.equal(result.ok, true);
assert.deepEqual(
result.runtimes.map((runtime) => runtime.runtime),
["go", "node", "python"],
["go", "node", "python", "rust"],
);
assert.equal(result.runtimes[0].declarations.length, 3);
assert.equal(result.runtimes[1].declarations.length, 5);
assert.equal(result.runtimes[2].declarations.length, 5);
assert.equal(result.runtimes[3].declarations.length, 4);
});

test("finds Docker and project-file drift", async () => {
const result = await inspect(path.join(here, "fixtures", "drift"));
assert.equal(result.ok, false);
assert.deepEqual(
result.runtimes.map((runtime) => runtime.runtime),
["go", "node"],
["go", "node", "rust"],
);
// Node.js, golang, and Rust are all misaligned
const node = result.runtimes.find((runtime) => runtime.runtime === "node");
assert.ok(node);
assert.equal(node.ok, false);
const go = result.runtimes.find((runtime) => runtime.runtime === "go");

assert.ok(go);
assert.equal(go.ok, false);

const rust = result.runtimes.find((runtime) => runtime.runtime === "rust");
assert.ok(rust);
assert.equal(rust.ok, false);
});

test("returns an empty success for a directory with no declarations", async () => {
Expand Down
5 changes: 5 additions & 0 deletions test/fixtures/aligned/.github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,8 @@ jobs:
- uses: actions/setup-go@v5
with:
go-version: "1.24"
rust:
steps:
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: "1.81.0"
5 changes: 5 additions & 0 deletions test/fixtures/aligned/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[package]
name = "example"
version = "0.1.0"
edition = "2021"
rust-version = "1.81"
1 change: 1 addition & 0 deletions test/fixtures/aligned/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
FROM node:22-alpine AS web
FROM python:3.12-slim AS worker
FROM rust:1.81.0
2 changes: 2 additions & 0 deletions test/fixtures/aligned/rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[toolchain]
channel = "1.81.0"
5 changes: 5 additions & 0 deletions test/fixtures/drift/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[package]
name = "example"
version = "0.1.0"
edition = "2021"
rust-version = "1.81"
1 change: 1 addition & 0 deletions test/fixtures/drift/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
FROM node:20-alpine
FROM golang:1.23
FROM rust:1.82
2 changes: 2 additions & 0 deletions test/fixtures/drift/rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[toolchain]
channel = "1.81.0"
Loading