diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..f4c7245 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,40 @@ +# EditorConfig — https://editorconfig.org +# Keeps whitespace consistent across editors and IDEs before +# Prettier / rustfmt / ruff get a chance to run. + +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +# Markdown: trailing spaces are significant (hard line-break). +# Let Prettier handle wrapping; don't trim here. +trim_trailing_whitespace = false + +[*.{yml,yaml}] +indent_size = 2 + +[*.{json,jsonc}] +indent_size = 2 + +[*.toml] +indent_size = 4 + +[Makefile] +indent_style = tab + +[*.rs] +# rustfmt handles Rust formatting; EditorConfig just sets the baseline. +indent_size = 4 + +[*.py] +indent_size = 4 + +[*.{cpp,cc,c,h,hpp}] +indent_size = 4 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..e372190 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,37 @@ +# Lint CI for mukoko-dev/.github. +# +# Calls the shared lint workflow in nyuchi/.github. That repository is +# public, so a reusable workflow in it can be called from any repository +# in any organisation — including private ones. This is the same wiring +# bundu-labs and mzizi-dev already use, and it is deliberate: the org's +# lint policy lives in exactly one place and is not copied per org. +# +# The job MUST be called `lint`. A reusable-workflow call publishes its +# checks as "/", which is what produces the five +# contexts the mukoko-dev org ruleset requires: +# +# lint / actionlint lint / markdownlint +# lint / JSON validity lint / yamllint +# lint / prettier +# +# Every repository in this org needs a file exactly like this one, or its +# PRs can never merge. + +name: Lint + +on: + pull_request: + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + lint: + uses: nyuchi/.github/.github/workflows/reusable-lint.yml@main diff --git a/.github/workflows/pr-title-lint.yml b/.github/workflows/pr-title-lint.yml new file mode 100644 index 0000000..a7c3c40 --- /dev/null +++ b/.github/workflows/pr-title-lint.yml @@ -0,0 +1,23 @@ +# Conventional-Commits check on the PR title. +# +# Both org rulesets allow squash merges only, so the PR title becomes the +# commit subject on main. Linting it here is what keeps main's history +# machine-readable for release tooling. + +name: PR title + +on: + pull_request_target: + types: [opened, edited, reopened, synchronize] + +permissions: + pull-requests: read + statuses: write + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + lint: + uses: nyuchi/.github/.github/workflows/reusable-pr-title-lint.yml@main diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000..7a1c929 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,16 @@ +# Marks abandoned issues and PRs stale on a schedule. + +name: Stale + +on: + schedule: + - cron: '23 1 * * *' + workflow_dispatch: + +permissions: + issues: write + pull-requests: write + +jobs: + stale: + uses: nyuchi/.github/.github/workflows/reusable-stale.yml@main diff --git a/.markdownlint.jsonc b/.markdownlint.jsonc new file mode 100644 index 0000000..fc3a0ce --- /dev/null +++ b/.markdownlint.jsonc @@ -0,0 +1,39 @@ +// markdownlint-cli2 config for mukoko-dev/.github. +// +// Relaxed defaults that accommodate: +// - Long prose lines in docs (we format for readability, not width) +// - Inline HTML in the org profile (
) +// - Files that start with an HTML comment or a
instead of an H1 +// - Fenced code blocks without a language tag +// +// See https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md +{ + "default": true, + + // Line length — we wrap for readability, not at a fixed column. + "MD013": false, + + // Inline HTML — profile/README.md uses
for the + // landing-page layout and that's intentional. + "MD033": false, + + // First line must be a top-level heading — profile/README.md opens + // with an HTML comment, which is correct. + "MD041": false, + + // Fenced code blocks without a language — acceptable for output + // blocks and plain-text fences. + "MD040": false, + + // Duplicate headings are fine as long as they aren't siblings + // (e.g. "## Summary" appears in multiple docs; that's expected). + "MD024": { "siblings_only": true }, + + // Unordered list indentation — 2 spaces, matching our house style. + "MD007": { "indent": 2 }, + + // Table-column-style — re-enabled now that Prettier is in CI and + // auto-formats tables. Prettier's table output should satisfy + // MD060's "aligned" style. + "MD060": true, +} diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..176e105 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,22 @@ +# Prettier ignore for mukoko-dev/.github. + +# Standard ignores +node_modules/ +.git/ + +# Files with structural meaning that Prettier would harm +LICENSE +CODEOWNERS +.github/CODEOWNERS + +# YAML is handled by yamllint and actionlint, not Prettier — they have +# stronger guarantees about GitHub Actions semantics that Prettier does +# not understand (e.g. the `on:` truthy quirk). +*.yml +*.yaml + +# Lockfiles, generated output +*-lock.json +*-lock.yaml +package-lock.json +pnpm-lock.yaml diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..333987c --- /dev/null +++ b/.prettierrc @@ -0,0 +1,16 @@ +{ + "printWidth": 80, + "proseWrap": "preserve", + "tabWidth": 2, + "useTabs": false, + "endOfLine": "lf", + "trailingComma": "all", + "overrides": [ + { + "files": ["*.md", "*.mdx"], + "options": { + "embeddedLanguageFormatting": "off" + } + } + ] +} diff --git a/.yamllint.yaml b/.yamllint.yaml new file mode 100644 index 0000000..702213a --- /dev/null +++ b/.yamllint.yaml @@ -0,0 +1,57 @@ +# yamllint config for mukoko-dev/.github. +# +# Relaxed defaults that accommodate: +# - GitHub Actions workflows (which use `on:` — a YAML 1.1 boolean) +# - Long lines in shell heredocs and comment blocks inside workflows +# - Files without `---` document-start markers +# +# See https://yamllint.readthedocs.io/en/stable/configuration.html + +extends: default + +ignore: | + node_modules/ + +rules: + # 140-char limit. Workflow files have legitimately long shell URLs + # (the actionlint download script, action references) and issue- + # template `about:` strings render as a single line in the picker, + # so wrapping them would change user-visible behaviour. 140 is + # tight enough to flag truly excessive lines without forcing + # awkward breaks on URLs. Strict CI fails on any violation. + line-length: + max: 140 + level: error + allow-non-breakable-words: true + allow-non-breakable-inline-mappings: true + + # GitHub Actions uses `on:` as a key, which YAML 1.1 treats as a + # boolean. Disable the truthy check on keys to stop yamllint from + # rejecting every workflow. + truthy: + check-keys: false + level: error + + # We don't prefix YAML files with `---`. + document-start: disable + + # Be lenient about how many spaces follow `#` in comments. + comments: + min-spaces-from-content: 1 + + # Comments-indentation flags standalone comments that don't match the + # indent of surrounding content. Real-world configs use comments to + # delimit sections (long `# ----` separator banners) and to mark + # commented-out blocks at the level of the code they replace, neither + # of which fits the rule cleanly. Disabled. + comments-indentation: disable + + # GitHub issue forms have deeply nested lists and mappings. + indentation: + spaces: 2 + indent-sequences: consistent + + # Stops a warning on the trailing-empty-line check for files that + # end cleanly with a single newline. + empty-lines: + max-end: 1 diff --git a/README.md b/README.md index a46ae92..abb5d3d 100644 --- a/README.md +++ b/README.md @@ -1 +1,86 @@ -# .github \ No newline at end of file +# mukoko-dev/.github + +Org-wide defaults for `mukoko-dev`: the shared CI wiring, the lint +configuration every repository inherits, and the notes explaining why both +are shaped the way they are. + +## CI is not written here + +The reusable workflow library lives in [`nyuchi/.github`][hub] and is shared +across the whole estate. That repository is **public**, and a reusable +workflow in a public repository can be called from any repository in any +organisation — private callers included. So `mukoko-dev` repositories call +those workflows directly rather than keeping a second copy of them. + +This is already how `bundu-labs` and `mzizi-dev` consume the library. Copying +the workflows into this org would double the maintenance surface and let the +two copies drift apart silently, which is the failure this arrangement is +meant to avoid. + +Only add a workflow file here when the behaviour genuinely differs from the +shared one. If you ever do copy a reusable workflow into this org, say so in +the PR and record why, so the divergence is deliberate and visible. + +[hub]: https://github.com/nyuchi/.github/tree/main/.github/workflows + +## Every repository needs `lint.yml` + +The org ruleset `org-wide-main-protection` requires five status checks on the +default branch of every repository: + +```text +lint / actionlint +lint / JSON validity +lint / prettier +lint / markdownlint +lint / yamllint +``` + +Those names are not free-form. A job that calls a reusable workflow publishes +its checks as ` / `, so the five strings above are +produced by a job named exactly `lint` calling `reusable-lint.yml`, whose own +jobs are named `actionlint`, `JSON validity`, and so on. + +Defining the five as ordinary top-level jobs does **not** work. They then +report as bare `actionlint`, `JSON validity`, … , the five required contexts +never report at all, and a required context that never reports is +permanently pending — every pull request in the repository is blocked +forever, with every visible check green. Copy +[`.github/workflows/lint.yml`](.github/workflows/lint.yml) verbatim. + +## Per-repository CI + +Beyond lint, pick the reusable workflow that matches the project and call it +from a workflow in that repository: + +| project shape | reusable workflow | +| -------------------------------------- | ------------------------------------------------------- | +| Next.js app or pnpm/Turborepo monorepo | `reusable-ci-nextjs-monorepo.yml` | +| TypeScript application | `reusable-ci-typescript.yml` | +| Published TypeScript package | `reusable-ci-typescript-lib.yml` | +| any repository | `reusable-codeql.yml`, `reusable-dependency-review.yml` | + +## Lint configuration + +`.prettierrc`, `.prettierignore`, `.markdownlint.jsonc`, `.yamllint.yaml` and +`.editorconfig` in this repository are the org baseline. The lint tools +auto-discover them from the repository root, so copy them into each +repository rather than pointing at these. + +Two adjustments come up often: + +- **`.yamllint.yaml` must ignore your lockfile.** `pnpm-lock.yaml` has lines + far past any sane width, and the reusable runs `yamllint -s`, which + promotes warnings to errors. +- **Prettier and `.mdx` do not mix.** Prettier still parses `.mdx` with its + legacy MDX1 parser and rewrites MDX2 expression comments — `{/* … */}` + becomes `{/_ … _/}` — corrupting the file. If a repository has `.mdx` + content, pass a `prettier-glob` that excludes it. + +## Merge method + +The org ruleset allows **squash merges only**, alongside +`required_linear_history` and `required_signatures`. Note that the +repository-level settings in this org still leave merge-commit and rebase +switched on; the ruleset is what actually decides, and it permits squash +alone.