I dont know about you but every time I have to write a CI script or modify an existing one my stomach fills with dread. I just know im in for a long frustrating session of fighting with some tortured mutant DSL written in YAML only to wait 15 minutes between iterations of cryptic error messages.
Why oh why cant things be better?
Problems
In in the web development world some of the most popular CI platforms today are Github Actions, Jenkins, CircleCI and TravisCI. The one thing they all have in common is they use the flaming ball of excrement that is YAML.
Why on earth to these CI platform all choose YAML? I have no idea.
The thing is, if it was just left at YAML that would be bad enough (for lots of reasons) but what invariably ends up happening is the platform developers add features things such as variables, functions and string interpolation. These are concepts that really dont belong in a declarative language and end up as hacky messy balls of confusing text.
Dont belive me? Go take a look at the tortured syntactical hoops you have to jump through to define a “reusable” function that can take parameters in CircleCI.
Don’t even get me started about the nightmare that is string interpolation in CircleCI or Github Actions YAML. Inserting environment variables into build commands and other scripts should be the bread and butter of any CI system but its unnecessarily complex and convoluted and error prone in a declarative format such as YAML.
I posted the question to Stack Overflow “Why do most CI / CD platforms use YAML for their pipelines?” but was unable to get an answer from anyone.
If you know of a reason please do let me know in the comments.
Solution
What I really think think would make things better is to use a langauge that is more flexible and better suited to the problem at hand.
If we need functions, variables and string interpolation why not use a language that has all of those things natively available?
You could use C#, Java, Go or any other language you like. The CI developer could build simple SDKs for each requested language.
Using my personal favourite language Typescript a simple build script could look something like:
import { exec, defineJob, persist, restore, defineWorkflow } from "ci";
import * as netlify from "netlify";
function doSomeCommonPreJobStuff() {
console.log(`starting job..`);
}
const install = defineJob({
label: `install`,
description: `installs dependencies for the project`,
handler: async () => {
doSomeCommonPreJobStuff();
await exec(`yarn install`, { label: "installs dependencies using yarn" });
await persist([`./node_modules`]);
},
});
const build = defineJob({
label: `build`,
description: `builds the project`,
machine: `4x-16gb`,
handler: async () => {
doSomeCommonPreJobStuff();
await restore();
await exec(`yarn build`);
await persist([`./dist`]);
},
});
const test = defineJob({
label: `test`,
image: `cimg/node:16.15.1`,
description: `tests the project`,
handler: async () => {
doSomeCommonPreJobStuff();
await restore();
await exec(`yarn test`);
},
});
const deploy = defineJob({
label: `deploy`,
description: `deploys the project to netlify`,
handler: async () => {
doSomeCommonPreJobStuff