From 726e833482d1d45aceda6403fbd593e3ea6153f3 Mon Sep 17 00:00:00 2001 From: Yurii Shynbuiev Date: Thu, 9 Apr 2026 22:00:06 +0700 Subject: [PATCH 1/7] feat: add reusable file linting workflow and config templates Add a reusable CI workflow (lint-files.yml) with editorconfig-checker, ShellCheck, markdownlint, yamllint, and gitleaks. Include canonical .gitattributes (LF normalization), .editorconfig (charset/indent/whitespace), and .markdownlint.yml for consistent file hygiene across all Identus repos. The editorconfig-checker job catches BOM and CRLF issues like the one found in hyperledger-identus#168. Closes hyperledger-identus/hyperledger-identus#172 (Phase 1) Signed-off-by: Yurii Shynbuiev Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Yurii Shynbuiev --- .editorconfig | 24 +++++++++++++++ .gitattributes | 46 +++++++++++++++++++++++++++ .github/workflows/lint-files.yml | 53 ++++++++++++++++++++++++++++++++ .github/workflows/lint.yml | 10 ++++++ .markdownlint.yml | 17 ++++++++++ 5 files changed, 150 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .github/workflows/lint-files.yml create mode 100644 .github/workflows/lint.yml create mode 100644 .markdownlint.yml diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..e90a431 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,24 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 2 + +[*.{scala,java,kt,kts}] +indent_size = 2 + +[*.swift] +indent_size = 4 + +[*.{rs,go}] +indent_size = 4 + +[*.md] +trim_trailing_whitespace = false + +[Makefile] +indent_style = tab diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..2e2bb58 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,46 @@ +# Normalize all text files to LF +* text=auto eol=lf + +# Scripts -- must be LF (executed in Docker/Linux) +*.sh text eol=lf +*.bash text eol=lf + +# Data/config +*.sql text eol=lf +*.yaml text eol=lf +*.yml text eol=lf +*.json text eol=lf +*.toml text eol=lf +*.properties text eol=lf + +# Source code +*.scala text eol=lf +*.kt text eol=lf +*.kts text eol=lf +*.java text eol=lf +*.swift text eol=lf +*.ts text eol=lf +*.js text eol=lf +*.rs text eol=lf +*.go text eol=lf + +# Docs +*.md text eol=lf +*.txt text eol=lf + +# Docker +Dockerfile text eol=lf +docker-compose*.yml text eol=lf + +# Binary -- never touch +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.jar binary +*.zip binary +*.tar.gz binary +*.woff binary +*.woff2 binary +*.ttf binary diff --git a/.github/workflows/lint-files.yml b/.github/workflows/lint-files.yml new file mode 100644 index 0000000..5ff6633 --- /dev/null +++ b/.github/workflows/lint-files.yml @@ -0,0 +1,53 @@ +name: Lint Files + +on: + workflow_call: + +jobs: + file-hygiene: + name: File Hygiene (editorconfig) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: EditorConfig Checker + uses: editorconfig-checker/action-editorconfig-checker@main + + shell-lint: + name: Shell Scripts + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: ShellCheck + uses: ludeeus/action-shellcheck@master + with: + severity: warning + + markdown-lint: + name: Markdown + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: markdownlint + uses: DavidAnson/markdownlint-cli2-action@v19 + with: + globs: "**/*.md" + + yaml-lint: + name: YAML + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: yamllint + uses: frenck/action-yamllint@v1 + + secret-scan: + name: Secret Scanning + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Gitleaks + uses: gitleaks/gitleaks-action@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..21289a1 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,10 @@ +name: Lint + +on: + pull_request: + push: + branches: [main] + +jobs: + lint: + uses: hyperledger-identus/.github/.github/workflows/lint-files.yml@main diff --git a/.markdownlint.yml b/.markdownlint.yml new file mode 100644 index 0000000..60f19d0 --- /dev/null +++ b/.markdownlint.yml @@ -0,0 +1,17 @@ +# markdownlint configuration +# https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md + +default: true + +# Allow long lines (common in tables, URLs, and generated content) +MD013: false + +# Allow multiple top-level headings (common in multi-section docs) +MD025: false + +# Allow inline HTML (needed for badges, details/summary, etc.) +MD033: false + +# Allow duplicate headings in different sections +MD024: + siblings_only: true From 4c0eb069fecd326e306c331f19c91d31bab0ccd9 Mon Sep 17 00:00:00 2001 From: Yurii Shynbuiev Date: Thu, 9 Apr 2026 23:42:40 +0700 Subject: [PATCH 2/7] fix: update all GitHub Actions to latest versions - actions/checkout v4 -> v6 - editorconfig-checker @main -> @v2 - ludeeus/action-shellcheck @master -> @2.0.0 - DavidAnson/markdownlint-cli2-action v19 -> v23 - frenck/action-yamllint v1 (already latest) - gitleaks/gitleaks-action v2 (already latest) Signed-off-by: Yurii Shynbuiev Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Yurii Shynbuiev --- .github/workflows/lint-files.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/lint-files.yml b/.github/workflows/lint-files.yml index 5ff6633..09aff5f 100644 --- a/.github/workflows/lint-files.yml +++ b/.github/workflows/lint-files.yml @@ -8,17 +8,17 @@ jobs: name: File Hygiene (editorconfig) runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: EditorConfig Checker - uses: editorconfig-checker/action-editorconfig-checker@main + uses: editorconfig-checker/action-editorconfig-checker@v2 shell-lint: name: Shell Scripts runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: ShellCheck - uses: ludeeus/action-shellcheck@master + uses: ludeeus/action-shellcheck@2.0.0 with: severity: warning @@ -26,9 +26,9 @@ jobs: name: Markdown runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: markdownlint - uses: DavidAnson/markdownlint-cli2-action@v19 + uses: DavidAnson/markdownlint-cli2-action@v23 with: globs: "**/*.md" @@ -36,7 +36,7 @@ jobs: name: YAML runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: yamllint uses: frenck/action-yamllint@v1 @@ -44,7 +44,7 @@ jobs: name: Secret Scanning runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: fetch-depth: 0 - name: Gitleaks From 6a1652439f4920c8f657a1574977fc3bf9e1fa17 Mon Sep 17 00:00:00 2001 From: Yurii Shynbuiev Date: Thu, 9 Apr 2026 23:56:21 +0700 Subject: [PATCH 3/7] chore: remove secret-scan job from lint workflow Signed-off-by: Yurii Shynbuiev Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Yurii Shynbuiev --- .github/workflows/lint-files.yml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/.github/workflows/lint-files.yml b/.github/workflows/lint-files.yml index 09aff5f..5d967e6 100644 --- a/.github/workflows/lint-files.yml +++ b/.github/workflows/lint-files.yml @@ -39,15 +39,3 @@ jobs: - uses: actions/checkout@v6 - name: yamllint uses: frenck/action-yamllint@v1 - - secret-scan: - name: Secret Scanning - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - name: Gitleaks - uses: gitleaks/gitleaks-action@v2 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 8c5a3e4537b03f32f37914f4f30e177444674b9d Mon Sep 17 00:00:00 2001 From: Yurii Shynbuiev Date: Fri, 10 Apr 2026 00:00:32 +0700 Subject: [PATCH 4/7] chore: remove redundant editorconfig block, add yamllint config - Remove [*.{scala,java,kt,kts}] section that duplicated the default indent_size = 2 from [*] - Add .yamllint.yml with relaxed defaults: disable line-length, allow 'on' truthy value (GitHub Actions), relax comment indentation Signed-off-by: Yurii Shynbuiev Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Yurii Shynbuiev --- .editorconfig | 3 --- .yamllint.yml | 13 +++++++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 .yamllint.yml diff --git a/.editorconfig b/.editorconfig index e90a431..d9393ed 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,9 +8,6 @@ trim_trailing_whitespace = true indent_style = space indent_size = 2 -[*.{scala,java,kt,kts}] -indent_size = 2 - [*.swift] indent_size = 4 diff --git a/.yamllint.yml b/.yamllint.yml new file mode 100644 index 0000000..189ed94 --- /dev/null +++ b/.yamllint.yml @@ -0,0 +1,13 @@ +--- +extends: default + +rules: + # Allow long lines (common in CI workflows and docker-compose) + line-length: disable + + # Allow truthy values like 'on' (used in GitHub Actions triggers) + truthy: + allowed-values: ["true", "false", "yes", "no", "on"] + + # Relaxed comment indentation + comments-indentation: disable From 4c25978f650ee67ec649e810aab7181adbc430d6 Mon Sep 17 00:00:00 2001 From: Yurii Shynbuiev Date: Fri, 10 Apr 2026 00:16:03 +0700 Subject: [PATCH 5/7] feat: add script to audit file hygiene configs across all repos Python script that compares .gitattributes, .editorconfig, .markdownlint.yml, and .yamllint.yml in every org repo against the canonical templates in this repo. Reports MISSING, OUTDATED, or OK for each file in each repo. Requires only `gh` CLI. Run with: python3 scripts/check-file-hygiene.py Refs: hyperledger-identus/hyperledger-identus#172 Signed-off-by: Yurii Shynbuiev Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Yurii Shynbuiev --- scripts/check-file-hygiene.py | 122 ++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100755 scripts/check-file-hygiene.py diff --git a/scripts/check-file-hygiene.py b/scripts/check-file-hygiene.py new file mode 100755 index 0000000..3198614 --- /dev/null +++ b/scripts/check-file-hygiene.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +"""Check that all Identus repos contain the canonical hygiene config files. + +Compares .gitattributes, .editorconfig, .markdownlint.yml, and .yamllint.yml +in every repo against the templates in the .github repo. + +Requires: gh CLI authenticated with access to hyperledger-identus org. + +Usage: + python3 scripts/check-file-hygiene.py +""" + +import argparse +import base64 +import json +import subprocess +import sys +from pathlib import Path + +ORG = "hyperledger-identus" +TEMPLATE_REPO = ".github" +FILES = [".gitattributes", ".editorconfig", ".markdownlint.yml", ".yamllint.yml"] + +# Repos to skip (the template repo itself) +SKIP_REPOS = {TEMPLATE_REPO} + + +def gh_api(endpoint: str) -> dict | list | None: + """Call the GitHub API via gh CLI.""" + result = subprocess.run( + ["gh", "api", endpoint, "--paginate"], + capture_output=True, + text=True, + ) + if result.returncode != 0: + return None + return json.loads(result.stdout) + + +def get_repo_names() -> list[str]: + """Get all repo names in the org.""" + repos = gh_api(f"orgs/{ORG}/repos?per_page=100") + if not repos: + print("Error: could not fetch repos. Is `gh` authenticated?", file=sys.stderr) + sys.exit(1) + return sorted(r["name"] for r in repos if not r["archived"]) + + +def get_file_content(repo: str, path: str, ref: str = "main") -> str | None: + """Fetch a file's content from a repo via the GitHub API.""" + data = gh_api(f"repos/{ORG}/{repo}/contents/{path}?ref={ref}") + if not data or "content" not in data: + return None + return base64.b64decode(data["content"]).decode("utf-8") + + +def load_template(path: str) -> str: + """Load a template file from the local .github repo.""" + template_path = Path(__file__).resolve().parent.parent / path + if not template_path.exists(): + print(f"Error: template {template_path} not found", file=sys.stderr) + sys.exit(1) + return template_path.read_text() + + +def main(): + parser = argparse.ArgumentParser(description="Check file hygiene across Identus repos") + parser.parse_args() + + repos = get_repo_names() + templates = {f: load_template(f) for f in FILES} + + # Status tracking + results: dict[str, dict[str, str]] = {} + + for repo in repos: + if repo in SKIP_REPOS: + continue + results[repo] = {} + for filename in FILES: + content = get_file_content(repo, filename) + if content is None: + results[repo][filename] = "MISSING" + elif content == templates[filename]: + results[repo][filename] = "OK" + else: + results[repo][filename] = "OUTDATED" + + # Print table + col_width = max(len(r) for r in results) + 2 + file_widths = {f: max(len(f), 8) + 2 for f in FILES} + + header = "Repo".ljust(col_width) + "".join(f.ljust(file_widths[f]) for f in FILES) + print(header) + print("-" * len(header)) + + all_ok = True + for repo, statuses in results.items(): + row = repo.ljust(col_width) + for filename in FILES: + status = statuses[filename] + if status != "OK": + all_ok = False + row += status.ljust(file_widths[filename]) + print(row) + + # Summary + print() + total = len(results) * len(FILES) + ok_count = sum(1 for r in results.values() for s in r.values() if s == "OK") + missing = sum(1 for r in results.values() for s in r.values() if s == "MISSING") + outdated = sum(1 for r in results.values() for s in r.values() if s == "OUTDATED") + print(f"Total: {ok_count}/{total} OK, {missing} missing, {outdated} outdated") + + if not all_ok: + sys.exit(1) + + +if __name__ == "__main__": + main() From 2187b21a81f52db0c1ae3986c7852b5c07d208fe Mon Sep 17 00:00:00 2001 From: Yurii Shynbuiev Date: Fri, 10 Apr 2026 11:46:14 +0700 Subject: [PATCH 6/7] feat: use prefix matching in file hygiene check Use startswith instead of exact match so repos can append local overrides (e.g., ktlint rules) after the canonical baseline and still pass the audit. The canonical template must appear verbatim at the top of the file. Signed-off-by: Yurii Shynbuiev Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Yurii Shynbuiev --- scripts/check-file-hygiene.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/check-file-hygiene.py b/scripts/check-file-hygiene.py index 3198614..c35afb6 100755 --- a/scripts/check-file-hygiene.py +++ b/scripts/check-file-hygiene.py @@ -6,6 +6,11 @@ Compares .gitattributes, .editorconfig, .markdownlint.yml, and .yamllint.yml in every repo against the templates in the .github repo. +Uses prefix matching: a repo's file is considered OK if it starts with the +canonical template content. This allows repos to append local overrides +(e.g., ktlint rules in .editorconfig) while still enforcing the shared +baseline. + Requires: gh CLI authenticated with access to hyperledger-identus org. Usage: @@ -83,7 +88,7 @@ def main(): content = get_file_content(repo, filename) if content is None: results[repo][filename] = "MISSING" - elif content == templates[filename]: + elif content.startswith(templates[filename].rstrip()): results[repo][filename] = "OK" else: results[repo][filename] = "OUTDATED" From e961d38125694ec065d59a9dd918c13aa9b905ee Mon Sep 17 00:00:00 2001 From: Yurii Shynbuiev Date: Fri, 10 Apr 2026 13:27:22 +0700 Subject: [PATCH 7/7] fix: pin all GitHub Actions to full commit SHAs Replace tag/version references with full commit SHA hashes for supply-chain security. Version comments kept for readability. Resolves SonarQube finding: "Use full commit SHA hash for this dependency" Signed-off-by: Yurii Shynbuiev Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Yurii Shynbuiev --- .github/workflows/lint-files.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/lint-files.yml b/.github/workflows/lint-files.yml index 5d967e6..995132e 100644 --- a/.github/workflows/lint-files.yml +++ b/.github/workflows/lint-files.yml @@ -8,17 +8,17 @@ jobs: name: File Hygiene (editorconfig) runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: EditorConfig Checker - uses: editorconfig-checker/action-editorconfig-checker@v2 + uses: editorconfig-checker/action-editorconfig-checker@d2ed4fd072ae6f887e9407c909af0f585d2ad9f4 # v2 shell-lint: name: Shell Scripts runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: ShellCheck - uses: ludeeus/action-shellcheck@2.0.0 + uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # 2.0.0 with: severity: warning @@ -26,9 +26,9 @@ jobs: name: Markdown runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: markdownlint - uses: DavidAnson/markdownlint-cli2-action@v23 + uses: DavidAnson/markdownlint-cli2-action@ce4853d43830c74c1753b39f3cf40f71c2031eb9 # v23 with: globs: "**/*.md" @@ -36,6 +36,6 @@ jobs: name: YAML runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: yamllint - uses: frenck/action-yamllint@v1 + uses: frenck/action-yamllint@e3e8cdef144eeb914883bd3c77f0333227140ffc # v1