feat: add linting (#51)
Co-authored-by: Muhammad Atif Ali <atif@coder.com>
This commit is contained in:
7
.github/workflows/ci.yaml
vendored
7
.github/workflows/ci.yaml
vendored
@@ -21,11 +21,14 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
bun-version: latest
|
bun-version: latest
|
||||||
- run: bun test
|
- run: bun test
|
||||||
fmt:
|
pretty:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: oven-sh/setup-bun@v1
|
- uses: oven-sh/setup-bun@v1
|
||||||
with:
|
with:
|
||||||
bun-version: latest
|
bun-version: latest
|
||||||
- run: bun fmt:ci
|
- name: Format
|
||||||
|
run: bun fmt:ci
|
||||||
|
- name: Lint
|
||||||
|
run: bun install && bun lint
|
||||||
|
|||||||
@@ -11,14 +11,14 @@ tags: [helper]
|
|||||||
|
|
||||||
<!-- Describes what this module does -->
|
<!-- Describes what this module does -->
|
||||||
|
|
||||||
<!-- Add a screencast or screenshot here put them in .images directory -->
|
|
||||||
|
|
||||||
```hcl
|
```hcl
|
||||||
module "MODULE_NAME" {
|
module "MODULE_NAME" {
|
||||||
source = "https://registry.coder.com/modules/MODULE_NAME"
|
source = "https://registry.coder.com/modules/MODULE_NAME"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
<!-- Add a screencast or screenshot here put them in .images directory -->
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
### Example 1
|
### Example 1
|
||||||
|
|||||||
96
lint.ts
Normal file
96
lint.ts
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
import { readFile, readdir, stat } from "fs/promises";
|
||||||
|
import * as path from "path";
|
||||||
|
import * as marked from "marked";
|
||||||
|
import grayMatter from "gray-matter";
|
||||||
|
|
||||||
|
const files = await readdir(".", { withFileTypes: true });
|
||||||
|
const dirs = files.filter(
|
||||||
|
(f) => f.isDirectory() && !f.name.startsWith(".") && f.name !== "node_modules"
|
||||||
|
);
|
||||||
|
|
||||||
|
let badExit = false;
|
||||||
|
|
||||||
|
// error reports an error to the console and sets badExit to true
|
||||||
|
// so that the process will exit with a non-zero exit code.
|
||||||
|
const error = (...data: any[]) => {
|
||||||
|
console.error(...data);
|
||||||
|
badExit = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensures that each README has the proper format.
|
||||||
|
// Exits with 0 if all is good!
|
||||||
|
for (const dir of dirs) {
|
||||||
|
const readme = path.join(dir.name, "README.md");
|
||||||
|
// Ensure exists
|
||||||
|
try {
|
||||||
|
await stat(readme);
|
||||||
|
} catch (ex) {
|
||||||
|
throw new Error(`Missing README.md in ${dir.name}`);
|
||||||
|
}
|
||||||
|
const content = await readFile(readme, "utf8");
|
||||||
|
const matter = grayMatter(content);
|
||||||
|
const data = matter.data as {
|
||||||
|
display_name?: string;
|
||||||
|
description?: string;
|
||||||
|
icon?: string;
|
||||||
|
maintainer_github?: string;
|
||||||
|
partner_github?: string;
|
||||||
|
verified?: boolean;
|
||||||
|
tags?: string[];
|
||||||
|
};
|
||||||
|
if (!data.display_name) {
|
||||||
|
error(dir.name, "missing display_name");
|
||||||
|
}
|
||||||
|
if (!data.description) {
|
||||||
|
error(dir.name, "missing description");
|
||||||
|
}
|
||||||
|
if (!data.icon) {
|
||||||
|
error(dir.name, "missing icon");
|
||||||
|
}
|
||||||
|
if (!data.maintainer_github) {
|
||||||
|
error(dir.name, "missing maintainer_github");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await stat(path.join(".", dir.name, data.icon));
|
||||||
|
} catch (ex) {
|
||||||
|
error(dir.name, "icon does not exist", data.icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokens = marked.lexer(content);
|
||||||
|
// Ensure there is an h1 and some text, then a code block
|
||||||
|
|
||||||
|
let h1 = false;
|
||||||
|
let code = false;
|
||||||
|
let paragraph = false;
|
||||||
|
|
||||||
|
for (const token of tokens) {
|
||||||
|
if (token.type === "heading" && token.depth === 1) {
|
||||||
|
h1 = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (h1 && token.type === "heading") {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (token.type === "paragraph") {
|
||||||
|
paragraph = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (token.type === "code") {
|
||||||
|
code = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!h1) {
|
||||||
|
error(dir.name, "missing h1");
|
||||||
|
}
|
||||||
|
if (!paragraph) {
|
||||||
|
error(dir.name, "missing paragraph after h1");
|
||||||
|
}
|
||||||
|
if (!code) {
|
||||||
|
error(dir.name, "missing example code block after paragraph");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (badExit) {
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
@@ -3,10 +3,13 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "bun test",
|
"test": "bun test",
|
||||||
"fmt": "bun x prettier -w **/*.ts **/*.md *.md && terraform fmt **/*.tf",
|
"fmt": "bun x prettier -w **/*.ts **/*.md *.md && terraform fmt **/*.tf",
|
||||||
"fmt:ci": "bun x prettier --check **/*.ts **/*.md *.md && terraform fmt -check **/*.tf"
|
"fmt:ci": "bun x prettier --check **/*.ts **/*.md *.md && terraform fmt -check **/*.tf",
|
||||||
|
"lint": "bun run lint.ts"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"bun-types": "^1.0.3"
|
"bun-types": "^1.0.3",
|
||||||
|
"gray-matter": "^4.0.3",
|
||||||
|
"marked": "^9.0.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"typescript": "^5.0.0"
|
"typescript": "^5.0.0"
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "esnext",
|
"target": "esnext",
|
||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"moduleResolution": "nodenext",
|
||||||
"types": ["bun-types"]
|
"types": ["bun-types"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user