Skip to content

Commit 4ead952

Browse files
committed
Renamed to gitlab-ci-local
Finished work on log output into working directory.
1 parent 778b3a0 commit 4ead952

File tree

10 files changed

+92
-78
lines changed

10 files changed

+92
-78
lines changed
File renamed without changes.

.idea/modules.xml

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,35 +27,35 @@ It lets you simulate a CI pipeline on your local machine.
2727
* [Features](#features)
2828
* [Unsupported tags, will be implemented in order](#unsupported-tags-will-be-implemented-in-order)
2929
* [Docker specfic tags. (Only shell working now)](#docker-specfic-tags-only-shell-working-now)
30-
* [Gitlab CI only, will not be used by gitlab-local-pipeline](#gitlab-ci-only-will-not-be-used-by-gitlab-local-pipeline)
30+
* [Gitlab CI only, will not be used by gitlab-ci-local](#gitlab-ci-only-will-not-be-used-by-gitlab-ci-local)
3131
* [Undecided](#undecided)
3232

3333
# Installation
3434
## Linux
3535
Download and put binary in `/usr/bin`
3636

3737
$ sudo su `must be installed as root, if placed in /usr/bin/`
38-
$ curl -L https://github.com/firecow/gitlab-local-pipeline/releases/latest/download/linux.gz | gunzip -c > /usr/bin/gitlab-local-pipeline
39-
$ chmod +x /usr/bin/gitlab-local-pipeline
38+
$ curl -L https://github.com/firecow/gitlab-ci-local/releases/latest/download/linux.gz | gunzip -c > /usr/bin/gitlab-ci-local
39+
$ chmod +x /usr/bin/gitlab-ci-local
4040

4141
## Windows (Git bash)
4242
Install [gitbash](https://git-scm.com/downloads)
4343

4444
Download and put binary in `C:\Program Files\Git\mingw64\bin`
4545

46-
$ curl -L https://github.com/firecow/gitlab-local-pipeline/releases/latest/download/win.gz | gunzip -c > /c/Program\ Files/Git/mingw64/bin/gitlab-local-pipeline.exe
46+
$ curl -L https://github.com/firecow/gitlab-ci-local/releases/latest/download/win.gz | gunzip -c > /c/Program\ Files/Git/mingw64/bin/gitlab-ci-local.exe
4747

4848
## Macos
4949
TODO: Fill this
5050

5151
# Usage
5252
## Example
5353
$ cd /home/user/workspace/myproject
54-
$ gitlab-local-pipeline
54+
$ gitlab-ci-local
5555

5656
## Convinience
5757
### Bash alias
58-
$ echo "alias glp='gitlab-local-pipeline'" >> ~/.bashrc
58+
$ echo "alias gcl='gitlab-ci-local'" >> ~/.bashrc
5959
### Bash completion
6060
TODO: Fill this
6161

@@ -106,7 +106,7 @@ Artifacts works right now, as along as you don't overwrite tracked files and as
106106
- services
107107
- image
108108

109-
## Gitlab CI only, will not be used by gitlab-local-pipeline
109+
## Gitlab CI only, will not be used by gitlab-ci-local
110110
- cache
111111
- pages
112112
- resource_group

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
2-
"name": "gitlab-local-pipeline",
2+
"name": "gitlab-ci-local",
33
"main": "index.js",
44
"scripts": {
5-
"build-linux": "pkg dist/index.js -t node12-linux-x64 -o ./bin/linux/gitlab-local-pipeline && gzip -c bin/linux/gitlab-local-pipeline > bin/linux.gz",
6-
"build-macos": "pkg dist/index.js -t node12-macos-x64 -o ./bin/macos/gitlab-local-pipeline && gzip -c bin/macos/gitlab-local-pipeline > bin/macos.gz",
7-
"build-win": "pkg dist/index.js -t node12-win-x64 -o ./bin/win/gitlab-local-pipeline && gzip -c bin/win/gitlab-local-pipeline.exe > bin/win.gz",
5+
"build-linux": "pkg dist/index.js -t node12-linux-x64 -o ./bin/linux/gitlab-ci-local && gzip -c bin/linux/gitlab-ci-local > bin/linux.gz",
6+
"build-macos": "pkg dist/index.js -t node12-macos-x64 -o ./bin/macos/gitlab-ci-local && gzip -c bin/macos/gitlab-ci-local > bin/macos.gz",
7+
"build-win": "pkg dist/index.js -t node12-win-x64 -o ./bin/win/gitlab-ci-local && gzip -c bin/win/gitlab-ci-local.exe > bin/win.gz",
88
"build-all": "npm run build-linux && npm run build-macos && npm run build-win",
99
"build": "tsc",
1010
"check-all": "npm run build && npm run --silent lint && npm audit --parseable && npm run ncu",

src/index.ts

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,6 @@ Array.prototype.first = function() {
2020
return this[0];
2121
};
2222

23-
const makeid = (length: number): string => {
24-
let result = "";
25-
const characters = "abcdefghijklmnopqrstuvwxyz0123456789";
26-
const charactersLength = characters.length;
27-
for (let i = 0; i < length; i = i + 1) {
28-
result += characters.charAt(Math.floor(Math.random() * charactersLength));
29-
}
30-
31-
return result;
32-
};
33-
3423
const argv = yargs.argv;
3524
const cwd = String(argv.cwd || process.cwd());
3625
const m: any = argv.m;
@@ -62,12 +51,12 @@ const runJobs = async () => {
6251
console.log(`=> ${c.yellow(`${stageName}`)} > ${c.blueBright(`${jobNames}`)} ${c.magentaBright("starting")}...`);
6352
for (const job of jobs) {
6453
if (job.isManual() && !manualArgs.includes(job.name)) {
65-
console.log(`${c.blueBright(`${job.name}`)} skipped. when:manual`);
54+
console.log(`${job.getJobNameString()} skipped. when:manual`);
6655
continue;
6756
}
6857

6958
if (job.isNever()) {
70-
console.log(`${c.blueBright(`${job.name}`)} skipped. when:never`);
59+
console.log(`${job.getJobNameString()} skipped. when:never`);
7160
continue;
7261
}
7362

@@ -117,18 +106,22 @@ process.on("uncaughtException", (err) => {
117106
console.log(err);
118107
});
119108

109+
// Ensure gitlab-ci-local working directory and assets.
110+
const pipelinesStatePath = `${cwd}/.gitlab-ci-local/state.yml`;
111+
fs.ensureFileSync(pipelinesStatePath);
112+
let pipelinesState: any = yaml.safeLoad(fs.readFileSync(pipelinesStatePath, "utf8"));
113+
if (!pipelinesState) {
114+
pipelinesState = {};
115+
}
116+
pipelinesState.pipelineId = pipelinesState.pipelineId !== undefined ? Number(pipelinesState.pipelineId) : 0;
117+
console.log(firstArg);
118+
if (["pipeline", "manual"].includes(firstArg)) {
119+
pipelinesState.pipelineId += 1;
120+
}
121+
fs.writeFileSync(pipelinesStatePath, yaml.safeDump(pipelinesState), {});
122+
process.env.CI_PIPELINE_ID = pipelinesState.pipelineId;
123+
120124
if (["pipeline", "manual"].includes(firstArg)) {
121-
const pipelinesPath = `${cwd}/.gitlab-ci-local/pipelines.yml`;
122-
const configPath = `${cwd}/.gitlab-ci-local/config.yml`;
123-
fs.ensureFileSync(configPath);
124-
fs.ensureFileSync(pipelinesPath);
125-
let config: any = yaml.safeLoad(fs.readFileSync(pipelinesPath, "utf8"));
126-
if (!config) {
127-
config = {};
128-
}
129-
config.pipelineId = config.pipelineId !== undefined ? Number(config.pipelineId) + 1 : 0;
130-
fs.writeFileSync(pipelinesPath, yaml.safeDump(config), {});
131-
process.env.CI_PIPELINE_ID = config.pipelineId;
132125
runJobs().catch();
133126
} else if (firstArg === "exec") {
134127
runExecJobs().catch();

src/job.ts

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as c from "ansi-colors";
22
import * as deepExtend from "deep-extend";
33
import * as fs from "fs-extra";
44
import * as glob from "glob";
5+
import * as path from "path";
56
import * as prettyHrtime from "pretty-hrtime";
67
import * as shelljs from "shelljs";
78

@@ -17,24 +18,21 @@ if (process.env.EXEPATH) {
1718

1819
export class Job {
1920
public readonly name: string;
20-
2121
public readonly stage: string;
22-
private readonly afterScripts: string[] = [];
2322

23+
private readonly afterScripts: string[] = [];
2424
private readonly allowFailure: boolean;
25-
2625
private readonly beforeScripts: string[] = [];
27-
2826
private readonly cwd: any;
2927
private readonly globals: any;
30-
28+
private readonly maxJobNameLength: number;
3129
private running = false;
3230
private readonly scripts: string[] = [];
33-
3431
private readonly variables: { [key: string]: string };
3532
private readonly when: string;
3633

37-
public constructor(jobData: any, name: string, cwd: any, globals: any) {
34+
public constructor(jobData: any, name: string, cwd: any, globals: any, maxJobNameLength: number) {
35+
this.maxJobNameLength = maxJobNameLength;
3836
this.name = name;
3937
this.cwd = cwd;
4038
this.globals = globals;
@@ -67,6 +65,14 @@ export class Job {
6765
this.variables = jobData.variables || {};
6866
}
6967

68+
public getJobNameString() {
69+
return `${c.blueBright(`${this.name.padEnd(this.maxJobNameLength + 1)}`)}`;
70+
}
71+
72+
public getOutputFilesPath() {
73+
return `${this.cwd}/.gitlab-ci-local/output/${this.getEnvs().CI_PIPELINE_ID}/${this.name}.log`;
74+
}
75+
7076
public isManual(): boolean {
7177
return this.when === "manual";
7278
}
@@ -76,10 +82,12 @@ export class Job {
7682
}
7783

7884
public async start(): Promise<void> {
85+
const jobNameStr = this.getJobNameString();
86+
7987
this.running = true;
8088

8189
if (this.scripts.length === 0) {
82-
console.error(`${c.blueBright(`${this.name}`)} ${c.red("must have script specified")}`);
90+
console.error(`${jobNameStr} ${c.red("must have script specified")}`);
8391
process.exit(1);
8492
}
8593

@@ -131,11 +139,12 @@ export class Job {
131139
}
132140

133141
private async exec(script: string): Promise<number> {
134-
const outputFilePath = `${this.cwd}/.gitlab-ci-local/pipe_logs/${this.getEnvs().CI_PIPELINE_ID}/${this.name}.log`;
135-
fs.ensureFileSync(outputFilePath);
136-
fs.truncateSync(outputFilePath);
142+
fs.ensureFileSync(this.getOutputFilesPath());
143+
fs.truncateSync(this.getOutputFilesPath());
137144

138145
return new Promise<any>((resolve, reject) => {
146+
const jobNameStr = this.getJobNameString();
147+
const outputFilesPath = this.getOutputFilesPath();
139148
const child = shelljs.exec(`${script}`, {
140149
cwd: this.cwd,
141150
env: this.getEnvs(),
@@ -145,17 +154,17 @@ export class Job {
145154
});
146155

147156
child.on("error", (e) => {
148-
reject(`${c.blueBright(`${this.name}`)} ${c.red(`error ${String(e)}`)}`);
157+
reject(`${jobNameStr} ${c.red(`error ${String(e)}`)}`);
149158
});
150159

151160
if (child.stdout) {
152161
child.stdout.on("data", (buf) => {
153-
fs.appendFileSync(outputFilePath, `${buf}`);
162+
fs.appendFileSync(outputFilesPath, `${buf}`);
154163
});
155164
}
156165
if (child.stderr) {
157166
child.stderr.on("data", (buf) => {
158-
fs.appendFileSync(outputFilePath, `${c.red(`${buf}`)}`);
167+
fs.appendFileSync(outputFilesPath, `${c.red(`${buf}`)}`);
159168
});
160169
}
161170

@@ -168,15 +177,20 @@ export class Job {
168177
}
169178

170179
private getExitedString(code: number, warning: boolean = false, prependString: string = "") {
171-
const mistakeStr = warning ? c.yellowBright(`warning with code ${code}`) + prependString : c.red(`exited with code ${code}`) + prependString;
180+
const seeLogStr = `See ${path.resolve(this.getOutputFilesPath())}`;
181+
const jobNameStr = this.getJobNameString();
182+
if (warning) {
183+
return `${jobNameStr} ${c.yellowBright(`warning with code ${code}`)} ${prependString} ${seeLogStr}`;
184+
}
172185

173-
return `${c.blueBright(`${this.name}`)} ${mistakeStr}`;
186+
return `${jobNameStr} ${c.red(`exited with code ${code}`)} ${prependString} ${seeLogStr}`;
174187
}
175188

176189
private getFinishedString(startTime: [number, number]) {
177190
const endTime = process.hrtime(startTime);
178191
const timeStr = prettyHrtime(endTime);
192+
const jobNameStr = this.getJobNameString();
179193

180-
return `${c.blueBright(`${this.name}`)} ${c.magentaBright("finished")} in ${c.magenta(`${timeStr}`)}`;
194+
return `${jobNameStr} ${c.magentaBright("finished")} in ${c.magenta(`${timeStr}`)}`;
181195
}
182196
}

src/parser.ts

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,53 +8,51 @@ import { Stage } from "./stage";
88

99
export class Parser {
1010

11+
private static loadYaml(filePath: string): any {
12+
const gitlabCiLocalYmlPath = `${filePath}`;
13+
if (!fs.existsSync(gitlabCiLocalYmlPath)) {
14+
return {};
15+
}
16+
17+
return yaml.safeLoad(fs.readFileSync(`${filePath}`, "utf8"));
18+
}
19+
1120
private readonly illigalJobNames = [
1221
"include", "local_configuration", "image", "services",
1322
"stages", "pages", "types", "before_script", "default",
1423
"after_script", "variables", "cache", "include",
1524
];
1625
private readonly jobs: Map<string, Job> = new Map();
26+
27+
private readonly maxJobNameLength: number = 0;
1728
private readonly stages: Map<string, Stage> = new Map();
1829

1930
public constructor(cwd: any) {
20-
// Fail if .gitlab-ci.yml missing
21-
const gitlabCiYmlPath = `${cwd}/.gitlab-ci.yml`;
22-
if (!fs.existsSync(gitlabCiYmlPath)) {
23-
console.error(`Could not find ${gitlabCiYmlPath}`);
24-
process.exit(1);
25-
}
26-
27-
// Fail if .gitlab-ci.local.yml missing
28-
const gitlabCiLocalYmlPath = `${cwd}/.gitlab-ci.local.yml`;
29-
if (!fs.existsSync(gitlabCiLocalYmlPath)) {
30-
console.error(`Could not find ${gitlabCiLocalYmlPath}`);
31-
process.exit(1);
32-
}
3331

3432
const orderedVariables = [];
3533
const orderedYml = [];
3634

37-
// Parse .gitlab-ci.yml
38-
orderedYml.push(yaml.safeLoad(fs.readFileSync(gitlabCiYmlPath, "utf8")));
39-
if (!orderedYml.last()) { // Print if empty
35+
// Add .gitlab-ci.yml
36+
orderedYml.push(Parser.loadYaml(`${cwd}/.gitlab-ci.yml`));
37+
if (!orderedYml.last()) { // Fail if empty
4038
console.error(`${cwd}/.gitlab-ci.yml is empty`);
4139
process.exit(1);
4240
}
4341
orderedVariables.push(orderedYml.last().variables);
4442

45-
// Parse .gitlab-ci.local.yml
46-
orderedYml.push(yaml.safeLoad(fs.readFileSync(gitlabCiLocalYmlPath, "utf8")) || {});
43+
// Add .gitlab-ci.yml
44+
orderedYml.push(Parser.loadYaml(`${cwd}/.gitlab-ci-local.yml`));
4745
orderedVariables.push(orderedYml.last().variables || {});
4846

49-
// Parse yamls included by other ci files.
47+
// Add included yaml's.
5048
orderedYml.unshift({});
5149
const includes = deepExtend.apply(this, orderedYml).include || [];
5250
for (const value of includes) {
5351
if (!value.local) {
5452
continue;
5553
}
5654

57-
orderedYml.unshift(yaml.safeLoad(fs.readFileSync(`${cwd}/${value.local}`, "utf8")));
55+
orderedYml.unshift(Parser.loadYaml(`${cwd}/${value.local}`));
5856
orderedVariables.unshift(orderedYml.first().variables || {});
5957
}
6058

@@ -73,13 +71,21 @@ export class Parser {
7371
this.stages.set(value, new Stage(value));
7472
}
7573

74+
// Find longest job name
75+
for (const key of Object.keys(gitlabData)) {
76+
if (this.illigalJobNames.includes(key) || key[0] === ".") {
77+
continue;
78+
}
79+
this.maxJobNameLength = Math.max(this.maxJobNameLength, key.length);
80+
}
81+
7682
// Generate jobs and put them into stages
7783
for (const [key, value] of Object.entries(gitlabData)) {
7884
if (this.illigalJobNames.includes(key) || key[0] === ".") {
7985
continue;
8086
}
8187

82-
const job = new Job(value, key, cwd, gitlabData);
88+
const job = new Job(value, key, cwd, gitlabData, this.maxJobNameLength);
8389
const stage = this.stages.get(job.stage);
8490
if (stage) {
8591
stage.addJob(job);

tests/test-yml-spec/.gitlab-ci.local.yml renamed to tests/test-yml-spec/.gitlab-ci-local.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@ never_job:
1212
when: never
1313
stage: startup
1414
script:
15-
- echo "I'm when:never i could have only been called by 'gitlab-local-pipeline exec never_job'"
15+
- echo "I'm when:never i could have only been called by 'gitlab-ci-local exec never_job'"

tests/test-yml-spec/.gitlab-ci.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ var_in_another_file:
5656
after_script_test:
5757
<<: *tags
5858
stage: last
59+
needs: [var_in_another_file]
5960
script:
6061
- echo "Throwing an error"
6162
- exit 1
@@ -76,4 +77,4 @@ manual_job_invoked_from_cli:
7677
when: manual
7778
stage: last
7879
script:
79-
- echo "I was invoked manually, because 'gitlab-local-pipeline manual manual_job_invoked_from_cli' was called"
80+
- echo "I was invoked manually, because 'gitlab-ci-local manual manual_job_invoked_from_cli' was called"

0 commit comments

Comments
 (0)