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
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ jobs:
with:
bun-version: 1.3.10
- run: bun install --frozen-lockfile
- run: bunx depcheck --ignores depcheck,@types/bun,@types/bun-types,bun:test
- run: bunx depcheck --ignores depcheck,@types/bun,@types/bun-types,vitest,@vitest/coverage-v8

test:
runs-on: ubuntu-24.04
Expand Down
8 changes: 4 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@ CLI tool to run GitLab CI pipelines locally. Written in TypeScript, built with B

## Build & Runtime

- **Runtime**: Bun (not Node.js). All scripts use `bun`/`bun test`/`bun run`.
- **Runtime**: Bun (not Node.js). All scripts use `bun`/`bun run`. Tests use vitest.
- **npm publish**: Still uses `npm publish --provenance` because Bun doesn't support provenance.
- **`bin` field**: Points to `dist/index.js` (Node.js-compatible bundle built by `bun run build:node`), not `src/index.ts`. This keeps `npm install -g` working without Bun.
- **Standalone binaries**: Built with `bun build --compile` for linux-amd64, linux-arm64, macos-x64, macos-arm64, win.
- **Version**: Hardcoded as `0.0.0` in `package.json`. CI replaces it via `sed` before build/publish. At runtime, `src/index.ts` reads it from `package.json` import.

## Testing

- **Never run the full test suite** (`bun test`), it takes too long. Always run targeted tests: `bun test --timeout 60000 tests/test-cases/<name>/`
- **Timeout**: `bunfig.toml` timeout setting does not work. The `--timeout 60000` flag in package.json scripts is required.
- **Never run the full test suite** (`bun run test`), it takes too long. Always run targeted tests: `bunx vitest run tests/test-cases/<name>/`
- **Timeout**: Configured in `vitest.config.ts` (`testTimeout: 60_000`).
- **Docker tests**: Tests under `dind-*` require Docker and are slow.
- **depcheck ignores**: `depcheck,@types/bun,@types/bun-types,bun:test`
- **depcheck ignores**: `depcheck,@types/bun,@types/bun-types,vitest,@vitest/coverage-v8`

## Schema

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -416,10 +416,10 @@ You need [bun](https://bun.sh/) installed.
bun install

# Run all tests
bun test
bun run test

# Run individual test-case
bun test tests/test-cases/cache-paths-not-array
bunx vitest run tests/test-cases/cache-paths-not-array
```

![example](./docs/images/example.png)
Expand Down
194 changes: 191 additions & 3 deletions bun.lock

Large diffs are not rendered by default.

10 changes: 6 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
"build-all": "bun run build:linux-amd64 && bun run build:linux-arm64 && bun run build:win && bun run build:macos-x64 && bun run build:macos-arm64",
"check-all": "bun run lint && bun run coverage",
"lint": "eslint .",
"test": "FORCE_COLOR=1 bun test --timeout 60000",
"test-except-dind": "bun test --timeout 60000 --filter '(?!.*dind)'",
"coverage": "FORCE_COLOR=1 bun test --timeout 60000 --coverage --coverage-reporter=text --coverage-reporter=lcov --verbose",
"test": "vitest run",
"test-except-dind": "vitest run --exclude '**/dind-*/**'",
"coverage": "vitest run --coverage",
"start": "bun src/index.ts --cwd examples/docker-compose-nodejs",
"typecheck": "tsc --noEmit",
"fetch-schema": "curl https://gitlab.com/gitlab-org/gitlab/-/raw/master/app/assets/javascripts/editor/schema/ci.json -sf > src/schema.json"
Expand Down Expand Up @@ -62,11 +62,13 @@
"@types/semver": "7.x.x",
"@types/split2": "4.x.x",
"@types/yargs": "17.x.x",
"@vitest/coverage-v8": "^4.0.18",
"axios-mock-adapter": "2.x",
"depcheck": "1.x.x",
"eslint": "10.x",
"typescript": "5.x.x",
"typescript-eslint": "8.x.x"
"typescript-eslint": "8.x.x",
"vitest": "^4.0.18"
},
"repository": {
"type": "git",
Expand Down
4 changes: 2 additions & 2 deletions tests/cases.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {spyOn, beforeAll, afterAll, test, expect} from "bun:test";
import {vi} from "vitest";
import {WriteStreamsMock} from "../src/write-streams.js";
import chalk from "chalk-template";
import {handler} from "../src/handler.js";
Expand All @@ -16,7 +16,7 @@ afterAll(() => {
});

test("--completion", async () => {
const spy = spyOn(console, "log").mockImplementation(() => {});
const spy = vi.spyOn(console, "log").mockImplementation(() => {});
const writeStreams = new WriteStreamsMock();
await handler({
completion: true,
Expand Down
10 changes: 5 additions & 5 deletions tests/mocks/utils.mock.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {spyOn, expect} from "bun:test";
import {vi, expect} from "vitest";
import {Utils} from "../../src/utils.js";

const originalBash = Utils.bash.bind(Utils);
Expand All @@ -21,7 +21,7 @@ let spawnRejectMocks: {cmdArgs: any[]; rejection: any}[] = [];

export function initBashSpy (spyMocks: {cmd: any; returnValue: any}[]) {
bashMocks = spyMocks;
const spy = spyOn(Utils, "bash");
const spy = vi.spyOn(Utils, "bash");
spy.mockImplementation(async (cmd: string, cwd?: string) => {
for (const spyMock of bashMocks) {
if (matches(cmd, spyMock.cmd)) return spyMock.returnValue;
Expand All @@ -33,7 +33,7 @@ export function initBashSpy (spyMocks: {cmd: any; returnValue: any}[]) {

export function initSyncSpawnSpy (spyMocks: {cmdArgs: string[]; returnValue: any}[]) {
syncSpawnMocks = spyMocks;
const spy = spyOn(Utils, "syncSpawn");
const spy = vi.spyOn(Utils, "syncSpawn");
spy.mockImplementation((cmdArgs: string[], cwd?: string) => {
for (const spyMock of syncSpawnMocks) {
if (matches(cmdArgs, spyMock.cmdArgs)) return spyMock.returnValue;
Expand All @@ -56,14 +56,14 @@ async function spawnMockImpl (cmdArgs: string[], cwd?: string) {
export function initSpawnSpy (spyMocks: {cmdArgs: any[]; returnValue: any}[]) {
spawnResolveMocks = spyMocks;
spawnRejectMocks = [];
const spy = spyOn(Utils, "spawn");
const spy = vi.spyOn(Utils, "spawn");
spy.mockImplementation(spawnMockImpl);
return spy;
}

export function initSpawnSpyReject (spyMocks: {cmdArgs: string[]; rejection: any}[]) {
spawnRejectMocks = spyMocks;
const spy = spyOn(Utils, "spawn");
const spy = vi.spyOn(Utils, "spawn");
spy.mockImplementation(spawnMockImpl);
return spy;
}
6 changes: 3 additions & 3 deletions tests/process-write-streams.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {spyOn, afterEach, afterAll, test, expect} from "bun:test";
import {vi} from "vitest";
import {WriteStreamsProcess} from "../src/write-streams.js";

const spyStdout = spyOn(process.stdout, "write").mockImplementation(() => true);
const spyStderr = spyOn(process.stderr, "write").mockImplementation(() => true);
const spyStdout = vi.spyOn(process.stdout, "write").mockImplementation(() => true);
const spyStderr = vi.spyOn(process.stderr, "write").mockImplementation(() => true);

afterEach(() => {
spyStdout.mockClear();
Expand Down
8 changes: 4 additions & 4 deletions tests/rules-regex.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {spyOn, mock, beforeEach, describe, test, expect} from "bun:test";
import {vi} from "vitest";
import "../src/global.js";
import {Argv} from "../src/argv";
import {Utils} from "../src/utils";
Expand All @@ -13,7 +13,7 @@ beforeEach(async () => {
writeStreams = new WriteStreamsMock();
gitData = await GitData.init("tests", writeStreams);
argv = await Argv.build({}, writeStreams);
mock.restore();
vi.restoreAllMocks();
});

/* eslint-disable @stylistic/quotes */
Expand Down Expand Up @@ -128,8 +128,8 @@ describe("gitlab rules regex", () => {
.forEach((t) => {
test(`- if: '${t.rule}'\n\t => ${t.evalResult}`, async () => {
const rules = [ {if: t.rule} ];
const evalSpy = spyOn(global, "eval");
const evaluateRuleIfSpy = spyOn(Utils, "evaluateRuleIf");
const evalSpy = vi.spyOn(global, "eval");
const evaluateRuleIfSpy = vi.spyOn(Utils, "evaluateRuleIf");

Utils.getRulesResult({argv, cwd: "", rules, variables: {}}, gitData);
expect(evaluateRuleIfSpy).toHaveReturnedWith(t.evalResult);
Expand Down
4 changes: 2 additions & 2 deletions tests/test-cases/predefined-variables/integration.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {spyOn, type Mock, beforeAll, beforeEach, afterEach, afterAll, describe, test, expect} from "bun:test";
import {vi, type Mock} from "vitest";
import {WriteStreamsMock} from "../../../src/write-streams.js";
import {handler} from "../../../src/handler.js";
import chalk from "chalk-template";
Expand Down Expand Up @@ -91,7 +91,7 @@ beforeAll(() => {
returnValue: {stdout: "git@gitlab.com:GCL/predefined-variables.git"},
};
initSpawnSpy([...WhenStatics.all, spyGitRemote]);
jobIdSpy = spyOn(
jobIdSpy = vi.spyOn(
Job.prototype as any,
"generateJobId",
);
Expand Down
8 changes: 4 additions & 4 deletions tests/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {spyOn, describe, test, expect, beforeAll, afterAll} from "bun:test";
import {vi} from "vitest";
import {GitData} from "../src/git-data.js";
import {Utils} from "../src/utils.js";

Expand Down Expand Up @@ -128,7 +128,7 @@ describe("evaluateRuleChanges", () => {
];
tests.forEach((t) => {
test.concurrent(`${t.description} \t\t [input: ${t.input} pattern: ${t.pattern} hasChanges: ${t.hasChanges}]`, () => {
const spy = spyOn(GitData, "changedFiles");
const spy = vi.spyOn(GitData, "changedFiles");
spy.mockReturnValue(t.input);
expect(Utils.evaluateRuleChanges("origin/master", t.pattern, ".")).toBe(t.hasChanges);
spy.mockRestore();
Expand All @@ -137,9 +137,9 @@ describe("evaluateRuleChanges", () => {
});

describe("isSubPath where process.cwd() have been mocked to return /home/user/gitlab-ci-local", () => {
let cwdSpy: ReturnType<typeof spyOn>;
let cwdSpy: ReturnType<typeof vi.spyOn>;
beforeAll(() => {
cwdSpy = spyOn(process, "cwd");
cwdSpy = vi.spyOn(process, "cwd");
cwdSpy.mockReturnValue("/home/user/gitlab-ci-local");
});
afterAll(() => {
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"types": ["bun-types"],
"types": ["bun-types", "vitest/globals"],
}
}
17 changes: 17 additions & 0 deletions vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {configDefaults, defineConfig} from "vitest/config";

export default defineConfig({
test: {
globals: true,
testTimeout: 60_000,
exclude: [...configDefaults.exclude, "**/.gitlab-ci-local*/**"],
env: {
FORCE_COLOR: "1",
},
coverage: {
provider: "v8",
reporter: ["text", "lcov"],
reportsDirectory: "./coverage",
},
},
});