Skip to content

Commit 82ece87

Browse files
author
faridevnz
committed
chore: add cosmokeeper 🚦 to lint and beautify
1 parent 92099d3 commit 82ece87

File tree

15 files changed

+522
-265
lines changed

15 files changed

+522
-265
lines changed

‎.cosmokeeper.json‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"lint": {
3+
"eslint": true,
4+
"prettier": true,
5+
"matches": "(.ts)$"
6+
},
7+
"patterns": {
8+
"branch": "/(w+/w+)/g"
9+
}
10+
}
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
import * as fs from "fs";
2+
import { spawnSync } from "child_process";
3+
import * as rl from "readline";
4+
5+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
6+
export type ParamsOf<T extends (...params: readonly any[]) => any> = T extends (
7+
...params: infer P
8+
) => // eslint-disable-next-line @typescript-eslint/no-explicit-any
9+
any
10+
? P
11+
: never;
12+
13+
const question = (q: string): Promise<string> => {
14+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
15+
return new Promise<string>((resolve, _) => {
16+
readline.question(q, res => {
17+
resolve(res);
18+
});
19+
});
20+
};
21+
22+
// read interface
23+
const readline = rl.createInterface({
24+
input: process.stdin,
25+
output: process.stdout
26+
});
27+
28+
const normalizePath = (path: string): string => {
29+
return path.replace(/^\.\//g, "");
30+
};
31+
32+
export const controlledSpawn = (...params: ParamsOf<typeof spawnSync>) => {
33+
params[2] && !params[2].encoding && (params[2].encoding = "utf8");
34+
const output = spawnSync(...params);
35+
if (output.status !== 0) {
36+
console.error(output.stderr?.toString() ?? "");
37+
process.exit(1);
38+
}
39+
40+
return output.stdout.toString();
41+
};
42+
43+
// RUN
44+
const run = async () => {
45+
const GIT_ROOT = controlledSpawn("git", [
46+
"rev-parse",
47+
"--show-toplevel"
48+
]).trim();
49+
50+
console.log("reading", `${GIT_ROOT}/.cosmokeeper.json`);
51+
// read the configuration
52+
if (!fs.existsSync(`${GIT_ROOT}/.cosmokeeper.json`)) {
53+
console.error(
54+
"missing .cosmokeeper configuration file, please run 'npx @futura-dev/cosmokeeper init' to create it."
55+
);
56+
process.exit(1);
57+
}
58+
const config = JSON.parse(
59+
fs.readFileSync(`${GIT_ROOT}/.cosmokeeper.json`, { encoding: "utf8" })
60+
);
61+
const package_json = JSON.parse(
62+
fs.readFileSync(`${GIT_ROOT}/package.json`, { encoding: "utf8" })
63+
);
64+
65+
// TODO: validate package.json
66+
// TODO: validate config
67+
68+
const IS_MONO_REPO = !!package_json.workspaces;
69+
const STAGED_FILES = controlledSpawn("git", [
70+
"diff",
71+
"--cached",
72+
"--name-only"
73+
]).split(/(\s|\n|\r|\r\n)/);
74+
75+
// MONOREPO
76+
if (IS_MONO_REPO) {
77+
const packages: string[] = package_json.workspaces;
78+
// find all the package slugs
79+
const visited_slugs = new Set<string>();
80+
const slugs = packages
81+
.map((pkg: string) => `${pkg}/package.json`)
82+
.map((pkg_json: string) => {
83+
const json = JSON.parse(
84+
fs.readFileSync(pkg_json, { encoding: "utf8" })
85+
);
86+
if (json.slug === undefined) {
87+
console.error(`missing slug in ${pkg_json}.`);
88+
process.exit(1);
89+
}
90+
if (visited_slugs.has(json.slug)) {
91+
console.error(`slug ${json.slug} must be unique in your packages.`);
92+
process.exit(1);
93+
}
94+
visited_slugs.add(json.slug);
95+
return json.slug;
96+
});
97+
98+
// Check if the branch name contains any slug
99+
const current_branch = controlledSpawn("git", [
100+
"symbolic-ref",
101+
"--short",
102+
"HEAD"
103+
]).trim();
104+
const branch_name_contains_a_slug = slugs.reduce((acc, slug) => {
105+
return (
106+
acc ||
107+
new RegExp(`${slug}@${config.patterns.branch}`).test(current_branch) ||
108+
["main", "master"].includes(current_branch)
109+
);
110+
}, false);
111+
if (!branch_name_contains_a_slug) {
112+
console.error(
113+
`branch name \\'${current_branch}\\' does not respect the pattern '[slug]@${config.patterns.branch}|main|master'`
114+
);
115+
process.exit(1);
116+
}
117+
118+
/**
119+
* - take staged files
120+
* - foreach staged file take the corresponding package
121+
* - save the package ( path or name )
122+
* - check if is unique
123+
*/
124+
const visited_packages = new Set<string>();
125+
for (const file of STAGED_FILES) {
126+
// take the package
127+
const pkg =
128+
packages.find(pkg =>
129+
new RegExp(`${normalizePath(pkg)}`).test(normalizePath(file))
130+
) ?? "root";
131+
visited_packages.add(pkg);
132+
}
133+
134+
if (visited_packages.size > 1) {
135+
// ask
136+
console.log("Warning: Modified files found in multiple packages.");
137+
const input = await question("Do you want to allow this commit? (y/N):");
138+
if (input.toLowerCase() !== "y") {
139+
console.error("execution stopped");
140+
process.exit(1);
141+
}
142+
}
143+
} else {
144+
// Check if the branch name matches the configured patters
145+
const current_branch = controlledSpawn("git", [
146+
"symbolic-ref",
147+
"--short",
148+
"HEAD"
149+
]);
150+
if (new RegExp(`${config.patterns.branch}`).test(current_branch)) {
151+
console.error(
152+
`branch name \\'${current_branch}\\' does not respect the pattern '${config.patterns.branch}'`
153+
);
154+
process.exit(1);
155+
}
156+
}
157+
158+
// COMMON
159+
// ( lint, prettier )
160+
const TO_LINT = STAGED_FILES.filter(file =>
161+
new RegExp(`${config.lint.matches}`).test(file)
162+
);
163+
164+
if (config.lint.eslintb && TO_LINT.length > 0)
165+
controlledSpawn("npx", ["eslint", ...TO_LINT, "--fix"]);
166+
167+
if (config.lint.prettier && TO_LINT.length > 0)
168+
controlledSpawn("npx", ["prettier", ...TO_LINT, "--write"]);
169+
170+
// add linted & beautified files
171+
controlledSpawn("git", ["add", ...TO_LINT]);
172+
process.exit(0);
173+
};
174+
run();

‎.cosmokeeper/commit-msg‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#! /bin/sh
2+
3+
# TODO: insert here an explanation
4+
# TODO: insert here the checks ( node installed, correct node version, ...)
5+
6+
GIT_ROOT=$(git rev-parse --show-toplevel)
7+
8+
exec < /dev/tty || true
9+
10+
if ! npx --no tsx $GIT_ROOT/.cosmokeeper/bin/commit-msg.ts;
11+
then
12+
exit 1
13+
fi
14+
15+
# Check conventional commit
16+
if ! npx --no -- commitlint --edit "$1";
17+
then
18+
exit 1
19+
fi
20+
21+
exit 0

‎.eslintrc‎

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
{
22
"extends": [
3-
"eslint:recommended",
4-
"plugin:@typescript-eslint/recommended"
5-
],
6-
"parser": "@typescript-eslint/parser",
7-
"plugins": [
8-
"@typescript-eslint"
3+
"@futura-dev/typescript/.eslintrc.js"
94
]
105
}

‎.idea/prettier.xml‎

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

‎.prettierrc.js‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = {
2+
...require('@futura-dev/eslint-config-typescript/.prettierrc'),
3+
}

‎commitlint.config.js‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module.exports = {
2+
extends: ["@commitlint/config-conventional"],
3+
rules: {
4+
"body-max-length": [0]
5+
}
6+
};

‎package.json‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,11 @@
2222
"@futura-dev/cosmodrome": "dist/cli.js"
2323
},
2424
"scripts": {
25-
"lint": "eslint . --ext .ts --config .eslintrc",
26-
"build": "rm -rf dist && npx cosmofactory build"
25+
"build": "rm -rf dist && npx cosmofactory build",
26+
"lint": "eslint ./src --ext .ts --config .eslintrc",
27+
"lint:fix": "npm run lint -- --fix",
28+
"release": "cosmodrome release",
29+
"lint:beautify": "npm run lint:fix && prettier ./src --write"
2730
},
2831
"dependencies": {
2932
"@inquirer/prompts": "1.1.3",
@@ -36,12 +39,11 @@
3639
},
3740
"devDependencies": {
3841
"@futura-dev/cosmofactory": "^0.2.0",
42+
"@futura-dev/cosmokeeper": "^0.1.7",
43+
"@futura-dev/eslint-config-typescript": "^0.1.3",
3944
"@types/commander": "^2.12.2",
4045
"@types/mute-stream": "^0.0.1",
4146
"@types/node": "^16.18.29",
42-
"@typescript-eslint/eslint-plugin": "^5.59.6",
43-
"@typescript-eslint/parser": "^5.59.6",
44-
"eslint": "^7.32.0",
4547
"fp-ts": "^2.15.0",
4648
"ts-node": "^10.9.1",
4749
"typescript": "^4.9.5"

‎src/cli.ts‎

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,33 @@
11
#! /usr/bin/env node
22

3-
import { program } from 'commander'
4-
import {release} from './commands/release'
5-
import {init} from './commands/init'
6-
import * as process from 'process'
3+
import { program } from "commander";
4+
import { release } from "./commands/release";
5+
import { init } from "./commands/init";
6+
import * as process from "process";
77

88
// program definition
99
program
10-
.name('@futura-dev/cosmodrome')
11-
.description('Cosmodrome 🚀')
12-
.version(process.env.npm_package_version ?? '0.0.0')
10+
.name("@futura-dev/cosmodrome")
11+
.description("Cosmodrome 🚀")
12+
.version(process.env.npm_package_version ?? "0.0.0");
1313

1414
// 'release' command definition
1515
program
16-
.command('release')
17-
.option('-c, --config <path>', 'path to configuration file', './.cosmodrome.json')
16+
.command("release")
17+
.option(
18+
"-c, --config <path>",
19+
"path to configuration file",
20+
"./.cosmodrome.json"
21+
)
1822
.action(async (...args: any[]) => {
1923
// parse and validate input
2024
const [{ config }] = args;
2125
// call the command
22-
const res = await release({ config })
23-
})
26+
await release({ config });
27+
});
2428

2529
// 'init' command definition
26-
program
27-
.command('init')
28-
.action(init)
30+
program.command("init").action(init);
2931

3032
// parse program
31-
program.parse(process.argv)
33+
program.parse(process.argv);

‎src/commands/init.ts‎

Lines changed: 38 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,50 @@
1-
import * as fs from 'fs'
2-
import {Signale, SignaleOptions} from 'signale'
3-
import {z} from 'zod'
1+
import * as fs from "fs";
2+
import { Signale, SignaleOptions } from "signale";
43

54
const baseSignalOptions: SignaleOptions = {
65
types: {
76
complete: {
8-
badge: '**',
9-
color: 'cyan',
10-
label: 'completed',
11-
},
7+
badge: "**",
8+
color: "cyan",
9+
label: "completed"
10+
}
1211
},
1312
config: {
14-
displayScope: false,
15-
},
16-
}
13+
displayScope: false
14+
}
15+
};
1716
const s = new Signale({
1817
...baseSignalOptions,
19-
types: {...baseSignalOptions.types, newline: {badge: '', label: '', color: 'black'}},
20-
})
18+
types: {
19+
...baseSignalOptions.types,
20+
newline: { badge: "", label: "", color: "black" }
21+
}
22+
});
2123

2224
export const init = (): Promise<void> => {
23-
fs.writeFileSync('./.cosmodrome.json', JSON.stringify(
24-
{
25-
'github': {
26-
'owner': '',
27-
'repo': '',
28-
'token': '',
25+
fs.writeFileSync(
26+
"./.cosmodrome.json",
27+
JSON.stringify(
28+
{
29+
github: {
30+
owner: "",
31+
repo: "",
32+
token: ""
33+
}
34+
// eslint-disable-next-line comma-dangle
2935
},
30-
// eslint-disable-next-line comma-dangle
31-
}, null, 2),
32-
)
33-
s.complete('.cosmodrome.json file was successfully created')
34-
const gitignore = fs.readFileSync('.gitignore', {encoding: 'utf8'})
35-
fs.writeFileSync('.gitignore', gitignore + '\n# cosmodrome\n.cosmodrome.json\n', {flag: 'w'})
36-
s.complete('added .cosmodrome.json to .gitignore')
36+
null,
37+
2
38+
)
39+
);
40+
s.complete(".cosmodrome.json file was successfully created");
41+
const gitignore = fs.readFileSync(".gitignore", { encoding: "utf8" });
42+
fs.writeFileSync(
43+
".gitignore",
44+
gitignore + "\n# cosmodrome\n.cosmodrome.json\n",
45+
{ flag: "w" }
46+
);
47+
s.complete("added .cosmodrome.json to .gitignore");
3748
// return
38-
return Promise.resolve()
39-
}
49+
return Promise.resolve();
50+
};

0 commit comments

Comments
 (0)