import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { bootstrap } from "../index";
function makeTmpDir(): string {
const dir = join(
tmpdir(),
`maina-detect-stack-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
);
mkdirSync(dir, { recursive: true });
return dir;
}
describe("detectStack — languages field", () => {
let tmpDir: string;
beforeEach(() => {
tmpDir = makeTmpDir();
});
afterEach(() => {
rmSync(tmpDir, { recursive: true, force: true });
});
test("empty directory returns languages with 'unknown'", async () => {
const result = await bootstrap(tmpDir);
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.value.detectedStack.languages).toBeDefined();
expect(Array.isArray(result.value.detectedStack.languages)).toBe(true);
expect(result.value.detectedStack.languages).toContain("unknown");
}
});
test("TypeScript project includes 'typescript' in languages", async () => {
writeFileSync(
join(tmpDir, "package.json"),
JSON.stringify({ devDependencies: { typescript: "^5.0.0" } }),
);
writeFileSync(join(tmpDir, "tsconfig.json"), "{}");
const result = await bootstrap(tmpDir);
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.value.detectedStack.languages).toContain("typescript");
expect(result.value.detectedStack.languages).not.toContain("unknown");
}
});
test("JavaScript project includes 'javascript' in languages", async () => {
writeFileSync(
join(tmpDir, "package.json"),
JSON.stringify({ dependencies: { express: "^4.0.0" } }),
);
const result = await bootstrap(tmpDir);
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.value.detectedStack.languages).toContain("javascript");
expect(result.value.detectedStack.languages).not.toContain("unknown");
}
});
test("go.mod detected as 'go' language", async () => {
writeFileSync(
join(tmpDir, "go.mod"),
"module example.com/foo\n\ngo 1.21\n",
);
const result = await bootstrap(tmpDir);
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.value.detectedStack.languages).toContain("go");
}
});
test("Cargo.toml detected as 'rust' language", async () => {
writeFileSync(
join(tmpDir, "Cargo.toml"),
'[package]\nname = "test"\nversion = "0.1.0"\n',
);
const result = await bootstrap(tmpDir);
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.value.detectedStack.languages).toContain("rust");
}
});
test("pyproject.toml detected as 'python' language", async () => {
writeFileSync(join(tmpDir, "pyproject.toml"), "[project]\nname = 'test'\n");
const result = await bootstrap(tmpDir);
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.value.detectedStack.languages).toContain("python");
}
});
test("requirements.txt detected as 'python' language", async () => {
writeFileSync(join(tmpDir, "requirements.txt"), "flask==2.0.0\n");
const result = await bootstrap(tmpDir);
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.value.detectedStack.languages).toContain("python");
}
});
test("setup.py detected as 'python' language", async () => {
writeFileSync(join(tmpDir, "setup.py"), "from setuptools import setup\n");
const result = await bootstrap(tmpDir);
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.value.detectedStack.languages).toContain("python");
}
});
test("pom.xml detected as 'java' language", async () => {
writeFileSync(join(tmpDir, "pom.xml"), "\n");
const result = await bootstrap(tmpDir);
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.value.detectedStack.languages).toContain("java");
}
});
test("build.gradle detected as 'java' language", async () => {
writeFileSync(join(tmpDir, "build.gradle"), "apply plugin: 'java'\n");
const result = await bootstrap(tmpDir);
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.value.detectedStack.languages).toContain("java");
}
});
test("build.gradle.kts detected as 'java' language", async () => {
writeFileSync(join(tmpDir, "build.gradle.kts"), 'plugins { id("java") }\n');
const result = await bootstrap(tmpDir);
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.value.detectedStack.languages).toContain("java");
}
});
test(".csproj file detected as 'dotnet' language", async () => {
writeFileSync(join(tmpDir, "MyApp.csproj"), "\n");
const result = await bootstrap(tmpDir);
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.value.detectedStack.languages).toContain("dotnet");
}
});
test(".sln file detected as 'dotnet' language", async () => {
writeFileSync(join(tmpDir, "MyApp.sln"), "Microsoft Visual Studio\n");
const result = await bootstrap(tmpDir);
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.value.detectedStack.languages).toContain("dotnet");
}
});
test(".fsproj file detected as 'dotnet' language", async () => {
writeFileSync(join(tmpDir, "MyApp.fsproj"), "\n");
const result = await bootstrap(tmpDir);
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.value.detectedStack.languages).toContain("dotnet");
}
});
test("multi-language project detects all languages", async () => {
// TypeScript + Python
writeFileSync(
join(tmpDir, "package.json"),
JSON.stringify({ devDependencies: { typescript: "^5.0.0" } }),
);
writeFileSync(join(tmpDir, "tsconfig.json"), "{}");
writeFileSync(join(tmpDir, "requirements.txt"), "flask==2.0.0\n");
const result = await bootstrap(tmpDir);
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.value.detectedStack.languages).toContain("typescript");
expect(result.value.detectedStack.languages).toContain("python");
expect(
result.value.detectedStack.languages.length,
).toBeGreaterThanOrEqual(2);
}
});
test("TypeScript + Go + Rust multi-language detection", async () => {
writeFileSync(
join(tmpDir, "package.json"),
JSON.stringify({ devDependencies: { typescript: "^5.0.0" } }),
);
writeFileSync(join(tmpDir, "tsconfig.json"), "{}");
writeFileSync(join(tmpDir, "go.mod"), "module test\n\ngo 1.21\n");
writeFileSync(join(tmpDir, "Cargo.toml"), '[package]\nname = "test"\n');
const result = await bootstrap(tmpDir);
expect(result.ok).toBe(true);
if (result.ok) {
const langs = result.value.detectedStack.languages;
expect(langs).toContain("typescript");
expect(langs).toContain("go");
expect(langs).toContain("rust");
expect(langs.length).toBeGreaterThanOrEqual(3);
}
});
test("languages does not contain duplicates", async () => {
writeFileSync(
join(tmpDir, "package.json"),
JSON.stringify({ devDependencies: { typescript: "^5.0.0" } }),
);
writeFileSync(join(tmpDir, "tsconfig.json"), "{}");
const result = await bootstrap(tmpDir);
expect(result.ok).toBe(true);
if (result.ok) {
const langs = result.value.detectedStack.languages;
const unique = [...new Set(langs)];
expect(langs.length).toBe(unique.length);
}
});
});