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: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@
/.gitlab-ci-local/
.DS_Store
.vscode
.history
/tmp/
/.gitlab-ci.yml
52 changes: 32 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,46 +180,58 @@ The command `gitlab-ci-local --list` will return pretty output and will also fil
to `when: never`.

```text
name description stage when allow_failure needs
test-job Run Tests test on_success false
build-job build on_success true [test-job]
name description stage when allow_failure needs
test-job Run Tests test on_success false
build-job build on_success true [test-job]
exit-codes-job build on_success [42,137] []
deploy-job deploy on_success [1]
```

- **description**: always shown, empty when not set
- **allow_failure**: `true`, `false`, or `[exit_code1,exit_code2]` when specific exit codes are allowed to fail
- **needs**: omitted when not specified (job follows stage ordering), `[]` when explicitly set to no dependencies (job starts immediately)

#### --list-all

Same as `--list` but will also print out jobs which are set to `when: never` (directly and implicit e.g. via rules).

```text
name description stage when allow_failure needs
test-job Run Tests test on_success false
build-job build on_success true [test-job]
deploy-job deploy never false [build-job]
name description stage when allow_failure needs
test-job Run Tests test on_success false
build-job build on_success true [test-job]
exit-codes-job build on_success [42,137] []
deploy-job deploy on_success [1]
never-job test never false
```

#### --list-csv

The command `gitlab-ci-local --list-csv` will output the pipeline jobs as csv formatted list and will also filter all
jobs which are set
to `when: never`.
The description will always be wrapped in quotes (even if there is none) to prevent semicolons in the description
disturb the csv structure.
The command `gitlab-ci-local --list-csv` will output the pipeline jobs as a CSV-formatted list and will also filter all
jobs which are set to `when: never`.

```text
name;description;stage;when;allow_failure;needs
test-job;"Run Tests";test;on_success;false;[]
build-job;"";build;on_success;true;[test-job]
name;stage;when;allowFailure;needs
test-job;test;on_success;false;
build-job;build;on_success;true;[test-job]
exit-codes-job;build;on_success;[42,137];[]
deploy-job;deploy;on_success;[1];
```

- **allowFailure**: `true`, `false`, or `[exit_code1,exit_code2]` when specific exit codes are allowed to fail
- **needs**: empty when not specified (job follows stage ordering), `[]` when explicitly set to no dependencies (job starts immediately)

#### --list-csv-all

Same as `--list-csv-all` but will also print out jobs which are set to `when: never` (directly and implicit e.g. via
Same as `--list-csv` but will also print out jobs which are set to `when: never` (directly and implicit e.g. via
rules).

```text
name;description;stage;when;allow_failure;needs
test-job;"Run Tests";test;on_success;false;[]
build-job;"";build;on_success;true;[test-job]
deploy-job;"";deploy;never;false;[build-job]
name;stage;when;allowFailure;needs
test-job;test;on_success;false;
build-job;build;on_success;true;[test-job]
exit-codes-job;build;on_success;[42,137];[]
deploy-job;deploy;on_success;[1];
never-job;test;never;false;
```

## Quirks
Expand Down
31 changes: 25 additions & 6 deletions src/commander.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,11 +224,11 @@ export class Commander {
writeStreams.stdout(chalk`{grey allow_failure needs}\n`);

const renderLine = (job: Job) => {
const needs = job.needs?.filter(n => !n.project && !n.pipeline).map(n => n.job);
const allowFailure = job.allowFailure ? "true " : "false ";
const needs = Commander.formatNeeds(job);
const allowFailure = Commander.formatAllowFailure(job.allowFailure);
let jobLine = chalk`{blueBright ${job.name.padEnd(jobNamePad)}} ${job.description.padEnd(descriptionPadEnd)} `;
jobLine += chalk`{yellow ${job.stage.padEnd(stagePadEnd)}} ${job.when.padEnd(whenPadEnd)} ${allowFailure.padEnd(11)}`;
if (needs) {
if (needs !== null) {
jobLine += chalk` [{blueBright ${needs}}]`;
}
writeStreams.stdout(`${jobLine}\n`);
Expand Down Expand Up @@ -267,10 +267,14 @@ export class Commander {
jobs = jobs.filter(j => j.when !== "never");
}

writeStreams.stdout("name;description;stage;when;allowFailure;needs\n");
writeStreams.stdout("name;stage;when;allowFailure;needs\n");

jobs.forEach((job) => {
const needs = job.needs?.filter(n => !n.project && !n.pipeline).map(n => n.job).join(",") ?? [];
writeStreams.stdout(`${job.name};"${job.description}";${job.stage};${job.when};${job.allowFailure ? "true" : "false"};[${needs}]\n`);
const needs = Commander.formatNeeds(job);
const needsStr = needs === null ? "" : `[${needs.join(",")}]`;
const allowFailure = Commander.formatAllowFailure(job.allowFailure);
const row = [job.name, job.stage, job.when, allowFailure, needsStr].join(";");
writeStreams.stdout(`${row}\n`);
});
}

Expand All @@ -294,4 +298,19 @@ export class Commander {
}
}
}

/** Returns `[code1,code2]` for exit_codes, `true`/`false` otherwise. */
private static formatAllowFailure (allowFailure: Job["allowFailure"]): string {
if (typeof allowFailure === "object") {
const codes = Array.isArray(allowFailure.exit_codes) ? allowFailure.exit_codes : [allowFailure.exit_codes];
return `[${codes.join(",")}]`;
}
return allowFailure ? "true" : "false";
}

/** Returns `[job1, job2]` when needs lists jobs, `[]` when explicitly set to no dependencies, or nothing when unset (job follows stage ordering). */
private static formatNeeds (job: Job): string[] | null {
if (job.needs === null) return null;
return job.needs.filter(n => !n.project && !n.pipeline).map(n => n.job);
}
}
17 changes: 16 additions & 1 deletion tests/test-cases/list-csv-case/.gitlab-ci.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
stages: [test, build]
stages: [test, build, deploy]

# @Description Run Tests
test-job:
Expand All @@ -14,3 +14,18 @@ build-job:
allow_failure: true
script:
- 'echo "Build something"'

exit-codes-job:
stage: build
needs: []
allow_failure:
exit_codes: [42, 137]
script:
- 'echo "Exit codes"'

deploy-job:
stage: deploy
allow_failure:
exit_codes: 1
script:
- 'echo "Deploy"'
34 changes: 28 additions & 6 deletions tests/test-cases/list-csv-case/integration.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {WriteStreamsMock} from "../../../src/write-streams.js";
import {handler} from "../../../src/handler.js";
import chalk from "chalk-template";
import {initSpawnSpy} from "../../mocks/utils.mock.js";
import {WhenStatics} from "../../mocks/when-statics.js";

Expand All @@ -16,9 +17,11 @@ test.concurrent("list-csv-case --list-csv", async () => {
}, writeStreams);

const expected = [
"name;description;stage;when;allowFailure;needs",
"test-job;\"Run Tests\";test;on_success;false;[]",
"build-job;\"\";build;on_success;true;[test-job]",
"name;stage;when;allowFailure;needs",
"test-job;test;on_success;false;",
"build-job;build;on_success;true;[test-job]",
"exit-codes-job;build;on_success;[42,137];[]",
"deploy-job;deploy;on_success;[1];",
];
expect(writeStreams.stdoutLines).toEqual(expect.arrayContaining(expected));
});
Expand All @@ -34,10 +37,29 @@ test.concurrent("list-csv-case --list-csv colon should add process descriptors w
}, writeStreams);

const expected = [
"name;description;stage;when;allowFailure;needs",
"test-job;\"Run;Tests\";test;on_success;false;[]",
"build-job;\"\";build;on_success;true;[test-job]",
"name;stage;when;allowFailure;needs",
"test-job;test;on_success;false;",
"build-job;build;on_success;true;[test-job]",
];
expect(writeStreams.stdoutLines).toEqual(expect.arrayContaining(expected));
});


test.concurrent("list-csv-case --list", async () => {
const writeStreams = new WriteStreamsMock();
await handler({
cwd: "tests/test-cases/list-csv-case/",
list: true,
stateDir: ".gitlab-ci-local-list-csv-case-list",
}, writeStreams);

// jobNamePad=14 (exit-codes-job), descriptionPadEnd=11, stagePadEnd=6 (deploy), whenPadEnd=10
const expected = [
chalk`{grey ${"name".padEnd(14)} ${"description".padEnd(11)}} {grey ${"stage".padEnd(6)} ${"when".padEnd(10)}} {grey allow_failure needs}`,
chalk`{blueBright ${"test-job".padEnd(14)}} ${"Run Tests".padEnd(11)} {yellow ${"test".padEnd(6)}} ${"on_success".padEnd(10)} ${"false".padEnd(11)}`,
chalk`{blueBright ${"build-job".padEnd(14)}} ${"".padEnd(11)} {yellow ${"build".padEnd(6)}} ${"on_success".padEnd(10)} ${"true".padEnd(11)} [{blueBright test-job}]`,
chalk`{blueBright ${"exit-codes-job".padEnd(14)}} ${"".padEnd(11)} {yellow ${"build".padEnd(6)}} ${"on_success".padEnd(10)} ${"[42,137]".padEnd(11)} [{blueBright }]`,
chalk`{blueBright ${"deploy-job".padEnd(14)}} ${"".padEnd(11)} {yellow ${"deploy".padEnd(6)}} ${"on_success".padEnd(10)} ${"[1]".padEnd(11)}`,
];
expect(writeStreams.stdoutLines).toEqual(expect.arrayContaining(expected));
});
Loading