diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index abb6474..c19b8ce 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,6 +60,84 @@ jobs: - name: Run BATS tests run: bats --recursive tests/ + markdownlint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install markdownlint-cli + run: npm install -g markdownlint-cli + - name: Run markdownlint + run: | + set -euo pipefail + ERROR_FOUND=0 + + if [ ! -f ".markdownlint.yml" ]; then + echo "::error::Config file .markdownlint.yml not found" + exit 1 + fi + + mapfile -t md_files < <(find . -type f -name "*.md" \ + -not -path "./node_modules/*" \ + -not -path "./_site/*" \ + -not -path "./.git/*" \ + -not -path "./rules/*") + + if [ ${#md_files[@]} -eq 0 ]; then + echo "⚠️ No .md files found. Skipping." + exit 0 + fi + + for file in "${md_files[@]}"; do + echo "ℹ️ Checking ${file#./}..." + markdownlint "$file" || ERROR_FOUND=1 + done + + if [[ $ERROR_FOUND -eq 0 ]]; then + echo "✅ All markdown files passed markdownlint checks!" + else + echo "❌ markdownlint found issues!" + exit 1 + fi + + yamllint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install yamllint + run: pip install yamllint + - name: Run yamllint + run: | + set -euo pipefail + ERROR_FOUND=0 + + if [ ! -f ".yamllint.yml" ]; then + echo "::error::Config file .yamllint.yml not found" + exit 1 + fi + + mapfile -t yaml_files < <(find . -type f \( -name "*.yml" -o -name "*.yaml" \) \ + -not -path "./_site/*" \ + -not -path "./node_modules/*" \ + -not -path "./.git/*" \ + -not -name ".yamllint.yml") + + if [ ${#yaml_files[@]} -eq 0 ]; then + echo "⚠️ No YAML files found. Skipping." + exit 0 + fi + + for file in "${yaml_files[@]}"; do + echo "ℹ️ Checking ${file#./}..." + yamllint "$file" || ERROR_FOUND=1 + done + + if [[ $ERROR_FOUND -eq 0 ]]; then + echo "✅ All YAML files passed yamllint checks!" + else + echo "❌ yamllint found issues!" + exit 1 + fi + assemble: runs-on: ubuntu-latest steps: @@ -70,6 +148,7 @@ jobs: bash scripts/assemble-ci.sh scripts/CI/linters scripts/shell/linters CI/linters bash scripts/assemble-ci.sh scripts/CI/static_analysis scripts/shell/static_analysis CI/static_analysis bash scripts/assemble-ci.sh scripts/CI/tests scripts/shell/tests CI/tests + bash scripts/assemble-ci.sh scripts/CI/build scripts/shell/build CI/build - name: Verify CI/ is up to date run: | diff --git a/.markdownlint.yml b/.markdownlint.yml new file mode 100644 index 0000000..a045d91 --- /dev/null +++ b/.markdownlint.yml @@ -0,0 +1,6 @@ +default: true + +MD013: + line_length: 120 +MD033: false +MD060: false diff --git a/.markdownlintignore b/.markdownlintignore new file mode 100644 index 0000000..9f4f5a0 --- /dev/null +++ b/.markdownlintignore @@ -0,0 +1 @@ +rules/ diff --git a/.yamllint.yml b/.yamllint.yml new file mode 100644 index 0000000..2f53800 --- /dev/null +++ b/.yamllint.yml @@ -0,0 +1,10 @@ +extends: default + +rules: + line-length: + max: 120 + truthy: + allowed-values: ['true', 'false', 'on', 'off'] + comments: + min-spaces-from-content: 1 + document-start: disable diff --git a/CI/build/mermaid.yml b/CI/build/mermaid.yml new file mode 100644 index 0000000..6098126 --- /dev/null +++ b/CI/build/mermaid.yml @@ -0,0 +1,41 @@ +mermaid-build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install mermaid-cli + run: npm install -g @mermaid-js/mermaid-cli + - name: Render Mermaid diagrams + run: | + set -euo pipefail + ERROR_FOUND=0 + + mapfile -t mmd_files < <(find . -type f -name "*.mmd" \ + -not -path "./node_modules/*" \ + -not -path "./.git/*") + + if [ ${#mmd_files[@]} -eq 0 ]; then + echo "⚠️ No .mmd files found. Skipping." + exit 0 + fi + + PUPPETEER_CONFIG=$(mktemp /tmp/puppeteer-XXXXXX.json) + echo '{"args":["--no-sandbox"]}' > "$PUPPETEER_CONFIG" + trap 'rm -f "$PUPPETEER_CONFIG"' EXIT + + for file in "${mmd_files[@]}"; do + out="${file%.mmd}.svg" + echo "ℹ️ Rendering ${file#./} → ${out#./}..." + mmdc -i "$file" -o "$out" -p "$PUPPETEER_CONFIG" || ERROR_FOUND=1 + done + + if [[ $ERROR_FOUND -eq 0 ]]; then + echo "✅ All .mmd files rendered successfully!" + else + echo "❌ mermaid-cli failed to render some files!" + exit 1 + fi + - name: Upload SVG artifacts + uses: actions/upload-artifact@v4 + with: + name: mermaid-diagrams + path: "**/*.svg" diff --git a/CI/linters/eslint.yml b/CI/linters/eslint.yml index 62c29dc..ff5248b 100644 --- a/CI/linters/eslint.yml +++ b/CI/linters/eslint.yml @@ -9,8 +9,12 @@ eslint: set -euo pipefail ERROR_FOUND=0 + ESLINT_CONFIGS=( + ".eslintrc.json" ".eslintrc.js" ".eslintrc.yml" ".eslintrc.yaml" + "eslint.config.js" "eslint.config.mjs" "eslint.config.cjs" + ) CONFIG_FOUND=0 - for config in ".eslintrc.json" ".eslintrc.js" ".eslintrc.yml" ".eslintrc.yaml" "eslint.config.js" "eslint.config.mjs" "eslint.config.cjs"; do + for config in "${ESLINT_CONFIGS[@]}"; do if [ -f "$config" ]; then CONFIG_FOUND=1 break @@ -22,7 +26,9 @@ eslint: exit 1 fi - mapfile -t js_files < <(find . -type f \( -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" -o -name "*.mjs" -o -name "*.cjs" \) \ + mapfile -t js_files < <(find . -type f \ + \( -name "*.js" -o -name "*.jsx" -o -name "*.ts" \ + -o -name "*.tsx" -o -name "*.mjs" -o -name "*.cjs" \) \ -not -path "./_site/*" \ -not -path "./node_modules/*" \ -not -path "./.git/*") diff --git a/CI/linters/hadolint.yml b/CI/linters/hadolint.yml index 30c2d68..520aabb 100644 --- a/CI/linters/hadolint.yml +++ b/CI/linters/hadolint.yml @@ -5,7 +5,8 @@ hadolint: - name: Install Hadolint run: | - sudo wget -O /usr/local/bin/hadolint https://github.com/hadolint/hadolint/releases/latest/download/hadolint-Linux-x86_64 + sudo wget -O /usr/local/bin/hadolint \ + https://github.com/hadolint/hadolint/releases/latest/download/hadolint-Linux-x86_64 sudo chmod +x /usr/local/bin/hadolint - name: Run Hadolint on Dockerfiles diff --git a/CI/linters/markdownlint.yml b/CI/linters/markdownlint.yml index 2758652..c662228 100644 --- a/CI/linters/markdownlint.yml +++ b/CI/linters/markdownlint.yml @@ -16,7 +16,9 @@ markdownlint: mapfile -t md_files < <(find . -type f -name "*.md" \ -not -path "./node_modules/*" \ - -not -path "./_site/*") + -not -path "./_site/*" \ + -not -path "./.git/*" \ + -not -path "./rules/*") if [ ${#md_files[@]} -eq 0 ]; then echo "⚠️ No .md files found. Skipping." diff --git a/CI/linters/mermaid.yml b/CI/linters/mermaid.yml new file mode 100644 index 0000000..45656cb --- /dev/null +++ b/CI/linters/mermaid.yml @@ -0,0 +1,38 @@ +mermaid: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install mermaid-cli + run: npm install -g @mermaid-js/mermaid-cli + - name: Run mermaid syntax check + run: | + set -euo pipefail + ERROR_FOUND=0 + + mapfile -t mmd_files < <(find . -type f -name "*.mmd" \ + -not -path "./node_modules/*" \ + -not -path "./.git/*") + + if [ ${#mmd_files[@]} -eq 0 ]; then + echo "⚠️ No .mmd files found. Skipping." + exit 0 + fi + + PUPPETEER_CONFIG=$(mktemp /tmp/puppeteer-XXXXXX.json) + echo '{"args":["--no-sandbox"]}' > "$PUPPETEER_CONFIG" + trap 'rm -f "$PUPPETEER_CONFIG"' EXIT + + OUT_DIR=$(mktemp -d /tmp/mermaid-XXXXXX) + trap 'rm -rf "$OUT_DIR"' EXIT + + for file in "${mmd_files[@]}"; do + echo "ℹ️ Checking ${file#./}..." + mmdc -i "$file" -o "${OUT_DIR}/out.svg" -p "$PUPPETEER_CONFIG" || ERROR_FOUND=1 + done + + if [[ $ERROR_FOUND -eq 0 ]]; then + echo "✅ All .mmd files passed mermaid syntax check!" + else + echo "❌ mermaid-cli found syntax errors!" + exit 1 + fi diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..111d3a9 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,108 @@ +# Contributing + +## Adding a new snippet + +Each snippet is one tool, one category. Follow these four steps: + +**1. Write the bash script** — `scripts/shell//.sh` + +```bash +#!/usr/bin/env bash +set -euo pipefail +ERROR_FOUND=0 + +# Validate config exists +if [ ! -f ".toolrc" ]; then + echo "::error::Config file .toolrc not found" + exit 1 +fi + +# Collect files +mapfile -t files < <(find . -type f -name "*.ext" \ + -not -path "./_site/*" \ + -not -path "./node_modules/*" \ + -not -path "./.git/*") + +# Graceful skip +if [ ${#files[@]} -eq 0 ]; then + echo "⚠️ No files found. Skipping." + exit 0 +fi + +# Check all files, accumulate errors +for file in "${files[@]}"; do + echo "ℹ️ Checking ${file#./}..." + tool "$file" || ERROR_FOUND=1 +done + +if [[ $ERROR_FOUND -eq 0 ]]; then + echo "✅ All files passed!" +else + echo "❌ Issues found!" + exit 1 +fi +``` + +**2. Write BATS tests** — `tests//.bats` + +Cover: missing config, no files found, all pass, one fails. +See existing tests in `tests/linters/` for examples. + +**3. Create the source YAML** — `scripts/CI//.yml` + +```yaml +: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install + run: ... + - name: Run + run: bash scripts/shell//.sh +``` + +**4. Assemble and verify** — generate the output file and check it's committed: + +```bash +bash scripts/assemble-ci.sh scripts/CI/ scripts/shell/ CI/ +git diff CI/ # must be empty +``` + +--- + +## Mandatory rules + +See `rules/process/ci-cd.md` for the full reference. The short version: + +- `runs-on: ubuntu-latest` always +- `actions/checkout@v4` must be the first step +- Actions pinned to major version tag (`@v4`, `@v2`) — never `@latest` +- Config validated with `::error::` before the tool runs +- No files → `⚠️ ... Skipping.` + `exit 0` +- Multi-file checks use the `ERROR_FOUND=0` accumulator (never `set -e` as a substitute) +- Node.js: `npm ci`, never `npm install` +- PHP: `shivammathur/setup-php@v2` + `actions/cache@v4` for Composer +- Output emoji: `✅` pass · `❌` fail · `⚠️` skip · `ℹ️` per-file progress + +--- + +## Running tests locally + +```bash +# Install bats-core (macOS) +brew install bats-core + +# Run all tests +bats --recursive tests/ +``` + +--- + +## Pull request checklist + +- [ ] Script is in `scripts/shell//` +- [ ] Source YAML is in `scripts/CI//` +- [ ] Assembled output is committed to `CI//` +- [ ] `git diff CI/` is clean after running the assembler +- [ ] BATS tests added and passing +- [ ] All mandatory rules from `rules/process/ci-cd.md` satisfied diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9894e06 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Ilya Lyashchuk + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e2e6f89 --- /dev/null +++ b/README.md @@ -0,0 +1,285 @@ +# Workflows + +Reusable GitHub Actions job snippets for code quality checks. Each snippet is a self-contained job definition +that projects compose into their own workflows. + +--- + +## How it works + +The project uses a three-layer architecture: + +```text +scripts/shell//.sh ← bash logic (tested, standalone) + ↓ assembled by scripts/assemble-ci.sh +scripts/CI//.yml ← source YAML (references the script) + ↓ +CI//.yml ← assembled output (script inlined, ready to use) +``` + +**Shell scripts** (`scripts/shell/`) contain the actual linting/testing logic. +They run standalone in CI without needing anything from this repo's structure. + +**Source YAMLs** (`scripts/CI/`) define the GitHub Actions job: install the tool, +then call the script with `run: bash scripts/shell//.sh`. + +**Assembled YAMLs** (`CI/`) are generated by the assembler. The `run: bash ...` line is replaced with +`run: |` followed by the full script body inlined. These are the files downstream projects import. + +--- + +## Project structure + +```text +Workflows/ +├── scripts/ +│ ├── assemble-ci.sh # assembles source YAMLs → CI/ +│ ├── CI/ # source YAML templates +│ │ ├── linters/ +│ │ │ ├── hadolint.yml +│ │ │ ├── htmlhint.yml +│ │ │ ├── markdownlint.yml +│ │ │ ├── shellcheck.yml +│ │ │ ├── stylelint.yml +│ │ │ └── yamllint.yml +│ │ ├── static_analysis/ +│ │ │ └── phpstan.yml +│ │ └── tests/ +│ │ ├── bats.yml +│ │ └── laravel_tests.yml +│ └── shell/ # bash scripts (one per tool) +│ └── linters/ +│ ├── hadolint.sh +│ ├── htmlhint.sh +│ ├── markdownlint.sh +│ ├── shellcheck.sh +│ ├── stylelint.sh +│ └── yamllint.sh +│ +├── CI/ # assembled output (ready to use) +│ ├── linters/ +│ ├── static_analysis/ +│ └── tests/ +│ +├── tests/ +│ ├── assemble-ci.bats # tests for the assembler +│ ├── linters/ # unit tests for each shell script +│ │ ├── hadolint.bats +│ │ ├── htmlhint.bats +│ │ ├── markdownlint.bats +│ │ ├── shellcheck.bats +│ │ ├── stylelint.bats +│ │ └── yamllint.bats +│ └── helpers/ +│ └── common.bash # shared test utilities (mocks, temp dirs) +│ +└── rules/ # living documentation for contributors + ├── README.md + ├── _meta/how-to-write-rules.md + └── process/ + ├── architecture-design.md + └── ci-cd.md +``` + +--- + +## Assembly + +Run the assembler for each category: + +```bash +bash scripts/assemble-ci.sh scripts/CI/linters scripts/shell/linters CI/linters +bash scripts/assemble-ci.sh scripts/CI/static_analysis scripts/shell/static_analysis CI/static_analysis +bash scripts/assemble-ci.sh scripts/CI/tests scripts/shell/tests CI/tests +``` + +The assembler takes three arguments: ` `. + +For each `.yml` in the source dir it looks for a matching `.sh` in the scripts dir. If found, the `run: bash ...` +line is replaced with `run: |` and the script body is inlined (shebang stripped, indentation preserved). +If not found, the YAML is copied as-is. + +**Example transformation:** + +Source (`scripts/CI/linters/shellcheck.yml`): + +```yaml +shellcheck: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install ShellCheck + run: sudo apt-get update && sudo apt-get install -y shellcheck + - name: Run ShellCheck on scripts + run: bash scripts/shell/linters/shellcheck.sh +``` + +Assembled (`CI/linters/shellcheck.yml`): + +```yaml +shellcheck: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install ShellCheck + run: sudo apt-get update && sudo apt-get install -y shellcheck + - name: Run ShellCheck on scripts + run: | + ERROR_FOUND=0 + DIRS=("scripts" "docker/scripts") + for DIR in "${DIRS[@]}"; do + ... + done + ... +``` + +--- + +## Available snippets + +### Linters + +| Snippet | Tool | What it checks | +|---------|------|----------------| +| `CI/linters/hadolint.yml` | [hadolint](https://github.com/hadolint/hadolint) | Dockerfiles | +| `CI/linters/htmlhint.yml` | [htmlhint](https://htmlhint.com) | HTML files | +| `CI/linters/markdownlint.yml` | [markdownlint](https://github.com/DavidAnson/markdownlint) | Markdown files | +| `CI/linters/shellcheck.yml` | [shellcheck](https://www.shellcheck.net) | Shell scripts | +| `CI/linters/stylelint.yml` | [stylelint](https://stylelint.io) | CSS / SCSS / LESS | +| `CI/linters/yamllint.yml` | [yamllint](https://yamllint.readthedocs.io) | YAML files | + +### Static analysis + +| Snippet | Tool | What it checks | +|---------|------|----------------| +| `CI/static_analysis/phpstan.yml` | [PHPStan](https://phpstan.org) | PHP (level from `phpstan.neon`) | + +### Tests + +| Snippet | What it runs | +|---------|--------------| +| `CI/tests/bats.yml` | BATS tests (`tests/` directory) | +| `CI/tests/laravel_tests.yml` | Laravel test suite (PHP 8.2, SQLite, parallel) | + +--- + +## Using snippets in a project + +Each assembled YAML defines a single top-level job key. Copy the content into your workflow under `jobs:`: + +```yaml +# .github/workflows/ci.yml +name: CI + +on: [push, pull_request] + +jobs: + shellcheck: # paste content of CI/linters/shellcheck.yml here + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install ShellCheck + run: sudo apt-get update && sudo apt-get install -y shellcheck + - name: Run ShellCheck on scripts + run: | + ERROR_FOUND=0 + ... + + phpstan: # paste content of CI/static_analysis/phpstan.yml here + ... +``` + +--- + +## Shell script conventions + +Every script in `scripts/shell/` follows the same patterns defined in `rules/process/ci-cd.md`: + +**Error accumulator** — check all files before exiting, so one failure doesn't hide others: + +```bash +ERROR_FOUND=0 +for file in "${files[@]}"; do + some-tool "$file" || ERROR_FOUND=1 +done +if [[ $ERROR_FOUND -eq 0 ]]; then + echo "✅ All files passed!" +else + echo "❌ Issues found!" + exit 1 +fi +``` + +**Config validation** — hard fail early if a required config is missing: + +```bash +if [ ! -f ".yamllint.yml" ]; then + echo "::error::Config file .yamllint.yml not found" + exit 1 +fi +``` + +**Graceful skip** — exit 0 when there are no files to check: + +```bash +if [ ${#files[@]} -eq 0 ]; then + echo "⚠️ No .yml files found. Skipping." + exit 0 +fi +``` + +**Output conventions:** + +- `✅` — all checks passed +- `❌` — issues found +- `⚠️` — skipped or non-critical warning +- `ℹ️` — currently processing a file +- `::error::` — GitHub Actions error annotation (hard failures) + +--- + +## Running tests locally + +Tests use [BATS](https://bats-core.readthedocs.io). Each shell script has a corresponding `.bats` file +that mocks external tools and tests success, failure, and edge cases in isolation. + +```bash +# Install (macOS) +brew install bats-core + +# Run all tests +bats --recursive tests/ +``` + +Test helpers (`tests/helpers/common.bash`) provide: + +- `setup_test_dir` / `teardown_test_dir` — create and clean up a temp working directory +- `mock_tool ` — stub an external binary to always exit with a given code +- `mock_tool_conditional ` — stub that exits 1 only when its argument matches `$MOCK_FAIL_PATTERN` + +--- + +## Adding a new snippet + +1. **Write the bash script** in `scripts/shell//.sh` + Follow the error accumulator pattern and output conventions above. + +2. **Write tests** in `tests//.bats` + Cover: missing config, no files found, all pass, one fails, multiple files with one failing. + +3. **Create the source YAML** in `scripts/CI//.yml` + Install the tool, then call the script: `run: bash scripts/shell//.sh` + +4. **Run the assembler** to generate the output file in `CI/`. + +5. **Run tests** to verify everything works. + +--- + +## Rules and documentation + +The `rules/` directory is the authoritative guide for contributors: + +- `rules/process/architecture-design.md` — directory layout, file naming, snippet scope +- `rules/process/ci-cd.md` — mandatory patterns for every job (error accumulator, config validation, action versions, etc.) +- `rules/_meta/how-to-write-rules.md` — how to write and format rules files diff --git a/scripts/CI/build/mermaid.yml b/scripts/CI/build/mermaid.yml new file mode 100644 index 0000000..c32cc21 --- /dev/null +++ b/scripts/CI/build/mermaid.yml @@ -0,0 +1,13 @@ +mermaid-build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install mermaid-cli + run: npm install -g @mermaid-js/mermaid-cli + - name: Render Mermaid diagrams + run: bash scripts/shell/build/mermaid.sh + - name: Upload SVG artifacts + uses: actions/upload-artifact@v4 + with: + name: mermaid-diagrams + path: "**/*.svg" diff --git a/scripts/CI/linters/hadolint.yml b/scripts/CI/linters/hadolint.yml index 31593cb..2c1164d 100644 --- a/scripts/CI/linters/hadolint.yml +++ b/scripts/CI/linters/hadolint.yml @@ -5,7 +5,8 @@ hadolint: - name: Install Hadolint run: | - sudo wget -O /usr/local/bin/hadolint https://github.com/hadolint/hadolint/releases/latest/download/hadolint-Linux-x86_64 + sudo wget -O /usr/local/bin/hadolint \ + https://github.com/hadolint/hadolint/releases/latest/download/hadolint-Linux-x86_64 sudo chmod +x /usr/local/bin/hadolint - name: Run Hadolint on Dockerfiles diff --git a/scripts/CI/linters/mermaid.yml b/scripts/CI/linters/mermaid.yml new file mode 100644 index 0000000..b31b9ce --- /dev/null +++ b/scripts/CI/linters/mermaid.yml @@ -0,0 +1,8 @@ +mermaid: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install mermaid-cli + run: npm install -g @mermaid-js/mermaid-cli + - name: Run mermaid syntax check + run: bash scripts/shell/linters/mermaid.sh diff --git a/scripts/shell/build/mermaid.sh b/scripts/shell/build/mermaid.sh new file mode 100644 index 0000000..4b3d378 --- /dev/null +++ b/scripts/shell/build/mermaid.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +set -euo pipefail +ERROR_FOUND=0 + +mapfile -t mmd_files < <(find . -type f -name "*.mmd" \ + -not -path "./node_modules/*" \ + -not -path "./.git/*") + +if [ ${#mmd_files[@]} -eq 0 ]; then + echo "⚠️ No .mmd files found. Skipping." + exit 0 +fi + +PUPPETEER_CONFIG=$(mktemp /tmp/puppeteer-XXXXXX.json) +echo '{"args":["--no-sandbox"]}' > "$PUPPETEER_CONFIG" +trap 'rm -f "$PUPPETEER_CONFIG"' EXIT + +for file in "${mmd_files[@]}"; do + out="${file%.mmd}.svg" + echo "ℹ️ Rendering ${file#./} → ${out#./}..." + mmdc -i "$file" -o "$out" -p "$PUPPETEER_CONFIG" || ERROR_FOUND=1 +done + +if [[ $ERROR_FOUND -eq 0 ]]; then + echo "✅ All .mmd files rendered successfully!" +else + echo "❌ mermaid-cli failed to render some files!" + exit 1 +fi diff --git a/scripts/shell/linters/eslint.sh b/scripts/shell/linters/eslint.sh index 334b211..5655310 100644 --- a/scripts/shell/linters/eslint.sh +++ b/scripts/shell/linters/eslint.sh @@ -2,8 +2,12 @@ set -euo pipefail ERROR_FOUND=0 +ESLINT_CONFIGS=( + ".eslintrc.json" ".eslintrc.js" ".eslintrc.yml" ".eslintrc.yaml" + "eslint.config.js" "eslint.config.mjs" "eslint.config.cjs" +) CONFIG_FOUND=0 -for config in ".eslintrc.json" ".eslintrc.js" ".eslintrc.yml" ".eslintrc.yaml" "eslint.config.js" "eslint.config.mjs" "eslint.config.cjs"; do +for config in "${ESLINT_CONFIGS[@]}"; do if [ -f "$config" ]; then CONFIG_FOUND=1 break @@ -15,7 +19,9 @@ if [ $CONFIG_FOUND -eq 0 ]; then exit 1 fi -mapfile -t js_files < <(find . -type f \( -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" -o -name "*.mjs" -o -name "*.cjs" \) \ +mapfile -t js_files < <(find . -type f \ + \( -name "*.js" -o -name "*.jsx" -o -name "*.ts" \ + -o -name "*.tsx" -o -name "*.mjs" -o -name "*.cjs" \) \ -not -path "./_site/*" \ -not -path "./node_modules/*" \ -not -path "./.git/*") diff --git a/scripts/shell/linters/markdownlint.sh b/scripts/shell/linters/markdownlint.sh index 8787f0c..389cf63 100644 --- a/scripts/shell/linters/markdownlint.sh +++ b/scripts/shell/linters/markdownlint.sh @@ -9,7 +9,9 @@ fi mapfile -t md_files < <(find . -type f -name "*.md" \ -not -path "./node_modules/*" \ - -not -path "./_site/*") + -not -path "./_site/*" \ + -not -path "./.git/*" \ + -not -path "./rules/*") if [ ${#md_files[@]} -eq 0 ]; then echo "⚠️ No .md files found. Skipping." diff --git a/scripts/shell/linters/mermaid.sh b/scripts/shell/linters/mermaid.sh new file mode 100644 index 0000000..d408b07 --- /dev/null +++ b/scripts/shell/linters/mermaid.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +set -euo pipefail +ERROR_FOUND=0 + +mapfile -t mmd_files < <(find . -type f -name "*.mmd" \ + -not -path "./node_modules/*" \ + -not -path "./.git/*") + +if [ ${#mmd_files[@]} -eq 0 ]; then + echo "⚠️ No .mmd files found. Skipping." + exit 0 +fi + +PUPPETEER_CONFIG=$(mktemp /tmp/puppeteer-XXXXXX.json) +echo '{"args":["--no-sandbox"]}' > "$PUPPETEER_CONFIG" +trap 'rm -f "$PUPPETEER_CONFIG"' EXIT + +OUT_DIR=$(mktemp -d /tmp/mermaid-XXXXXX) +trap 'rm -rf "$OUT_DIR"' EXIT + +for file in "${mmd_files[@]}"; do + echo "ℹ️ Checking ${file#./}..." + mmdc -i "$file" -o "${OUT_DIR}/out.svg" -p "$PUPPETEER_CONFIG" || ERROR_FOUND=1 +done + +if [[ $ERROR_FOUND -eq 0 ]]; then + echo "✅ All .mmd files passed mermaid syntax check!" +else + echo "❌ mermaid-cli found syntax errors!" + exit 1 +fi