Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ ast-grep's rules (`sgconfig.yml`, in `tools/ast-grep/rules/` with their cases in

`cargo remove` also deletes the root `Cargo.toml`'s `[profile.dev.package.*]` entries, with their comments, for every crate no member depends on directly: its clean-up after a removal ([rust-lang/cargo#11194](https://github.com/rust-lang/cargo/pull/11194), since Cargo 1.67) only keeps the entries for direct dependencies. `tools/policy.sh` fails while one is missing and its crate is still in `Cargo.lock`, so put it back with `git checkout -p Cargo.toml`. A new entry goes in `dev_optimised` in `tools/policy/cargo.rego` too.

CI runs a tool's checks only when its files (or the shared ones) change. Each workflow ends with a "… passed" check that reports either way, and those are the checks `main` requires before a merge. On a draft pull request, the Rust workspace's and the website's checks wait until you mark it ready for review, so open a pull request as a draft while it isn't next to merge (a stacked one, say). Their "… passed" checks fail on a draft that has something for them to check, since none of it ran. Marking it ready starts a new run, and if one doesn't show up, re-run the workflow. The slow suites run weekly in the `*-scheduled.yml` workflows, and you can start them on any branch from the Actions tab. A weekly run that fails opens an issue labelled `scheduled-failure`, and the next weekly run that passes closes it.
CI (`ci.yml`) runs a tool's checks only when its files (or the shared ones) change, and ends with a "CI passed" check that reports either way, the check `main` requires before a merge. On a draft pull request, nothing runs until you mark it ready for review, so open a pull request as a draft while it isn't next to merge (a stacked one, say). "CI passed" fails on a draft, since none of its checks ran. Marking it ready starts a new run, and if one doesn't show up, re-run the workflow. The slow suites run weekly in the `*-scheduled.yml` workflows, and you can start them on any branch from the Actions tab. A weekly run that fails opens an issue labelled `scheduled-failure`, and the next weekly run that passes closes it.

On a pull request, each tool's workflow keeps one comment up to date with its coverage: the Rust workspace's, with the Hegel property tests' alone beside it, and the website's and the extension's against `main`, with each changed file's. The website's and the extension's comments also show how the change moves what they ship against `main`: the website's client JavaScript and CSS, and the extension's zip. A pull request from a fork can't be commented on, so it finds the same in the run's summary. The website's Lighthouse scores are in the run's summary too, when the change touches what its pages are built from.

Expand Down
22 changes: 11 additions & 11 deletions .github/NEW_PROJECT.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This covers what the repository runs on and every place a new project has to be

Most of the steps are checked, so a missed one fails CI with a message that says what to add:

- `tools/policy.sh` (the Repo checks workflow) runs Rego policies in `tools/policy/` over the config files with [conftest](https://www.conftest.dev). They check each TypeScript app's scripts, `package.json` fields and git hooks; that every workflow on pull requests (but the benchmarks, the release plan and dependency review) has the `changes`, work and `passed` jobs and no `paths` in its trigger, and that one of them runs when each app, package, crate or plugin changes; that `.github/rulesets/protect-main.json` requires every `passed` check; that the labels, labeler and issue forms know every app and every file gets a label; that Rust packages share the workspace's settings and the dev profile keeps its entries for the crates in `Cargo.lock`; that lexicons are filed by NSID; and that an app with a Dockerfile has a Railway service in `.railway/railway.ts`, which has to type-check against the railway SDK. It needs Docker, and Node with the root's dev dependencies locally. The policies' own tests have to cover every line.
- `tools/policy.sh` (the Repo checks workflow) runs Rego policies in `tools/policy/` over the config files with [conftest](https://www.conftest.dev). They check each TypeScript app's scripts, `package.json` fields and git hooks; that every workflow on pull requests (but the benchmarks, the release plan and dependency review) has the `changes`, work and `passed` jobs and no `paths` in its trigger, that the workflows it calls have no concurrency group or push or pull request trigger and have report jobs that can't fail the call, and that one of them runs when each app, package, crate or plugin changes; that `.github/rulesets/protect-main.json` requires every `passed` check; that the labels, labeler and issue forms know every app and every file gets a label; that Rust packages share the workspace's settings and the dev profile keeps its entries for the crates in `Cargo.lock`; that lexicons are filed by NSID; and that an app with a Dockerfile has a Railway service in `.railway/railway.ts`, which has to type-check against the railway SDK. It needs Docker, and Node with the root's dev dependencies locally. The policies' own tests have to cover every line.
- The website's tests check that every folder in `apps/` has a tool page and is named in the root README, CONTRIBUTING, SECURITY, the website's README and the `web: tools` label (`apps/web/src/lib/tools/docs.test.ts`).
- locallibrary's build fails while a folder in `plugins/` isn't one of its dependencies, or a folder in `plugins/accounts/` has no manifest (`apps/locallibrary/build.rs`), and its tests check that every one ships and is in its docs (`apps/locallibrary/tests/it/plugins.rs`).
- clippy's `cargo_common_metadata` wants full metadata on every crate that isn't `publish = false`, and the release workflow's plan fails while it would release anything but locallibrary.
Expand Down Expand Up @@ -61,15 +61,15 @@ Logging: the website logs with [pino](https://getpino.io) (`apps/web/src/lib/ser

### CI

Every workflow the ruleset requires has the same three parts. Copy `localtoolbox-ci.yml`, the smallest:
Pull requests and pushes to `main` run one workflow, `ci.yml`, in three parts:

1. a `changes` job that runs [dorny/paths-filter](https://github.com/dorny/paths-filter) over the files the app depends on, including the shared ones (the lockfile, `.node-version`, the lint and format configs, the workflow itself). On a push it diffs against the last commit the workflow passed on (`.github/actions/last-passed`), so a run that a newer push cancelled, or one that failed, leaves its files to the next run;
2. the jobs that do the work, each with `needs: changes` and skipped when nothing matched;
3. a `passed` job named "`<tool>` CI passed", with `if: always()`, that fails if any job failed or was cancelled, or was skipped while the `changes` output it runs on was true (a job whose build didn't finish is skipped too).
1. a `changes` job that runs [dorny/paths-filter](https://github.com/dorny/paths-filter) over each tool's files, including the shared ones it depends on (the lockfile, `.node-version`, the lint and format configs, its workflow), with one output per tool. On a push it diffs against the last commit the workflow passed on (`.github/actions/last-passed`), so a run that a newer push cancelled, or one that failed, leaves its files to the next run. A draft pull request runs nothing until it's marked ready for review;
2. a job per tool that calls its workflow, `<tool>-ci.yml`, when the tool's output is true. Copy `localtoolbox-ci.yml`, the smallest: it triggers only on `workflow_call`, has no concurrency group (a call gets the caller's, and would cancel it), and its jobs do the work;
3. a `passed` job named "CI passed", with `if: always()`, that fails if any call failed or was cancelled, was skipped while its output was true, or the pull request was a draft. A call's result covers every job in it, so a job skipped because one it needs didn't finish fails its tool's call.

Jobs that only report stay out of the `passed` job's needs, so they never block a merge: the coverage `report` jobs that comment on the pull request, and locallibrary-ci's `semver`. Each workflow's concurrency group cancels the run a new push supersedes, on `main` too. locallibrary-ci and web-ci, the two that take the most runners, also skip their work on draft pull requests until they're marked ready for review.
Jobs that only report have `continue-on-error: true`, so they never fail their call or block a merge: the coverage `report` jobs that comment on the pull request, and locallibrary-ci's `semver`. `ci.yml`'s concurrency group cancels the run a new push supersedes, on `main` too.

The "Protect Main" ruleset requires the `passed` jobs, which report either way, so a path-filtered workflow never blocks a pull request that doesn't touch it. The ruleset is `.github/rulesets/protect-main.json`: an admin applies it with `tools/ruleset.sh apply`, and the Repo checks workflow fails on `main` while GitHub's copy differs. It requires Web CI passed, locallibrary CI passed, localtoolbox CI passed, Mix report passed, npm advisories passed, Workflow lint passed, Repo checks passed and atstore CI passed, and code scanning, which is `codeql.yml`'s CodeQL analysis of the workflows, the TypeScript and the Rust, leaving out the Rust tests (`.github/codeql/codeql-config.yml`).
The "Protect Main" ruleset requires "CI passed", which reports either way, so a tool's checks never block a pull request that doesn't touch it. The ruleset is `.github/rulesets/protect-main.json`: an admin applies it with `tools/ruleset.sh apply` once the pull request that changes it merges, and the Repo checks workflow fails on `main` while GitHub's copy differs. A pull request that renames or drops a required check is the exception: GitHub won't merge it while the old name is required, so the admin applies the file from its branch first, and open pull requests then merge `main` to report the new name. It requires CI passed and code scanning, which is `codeql.yml`'s CodeQL analysis of the workflows, the TypeScript and the Rust, leaving out the Rust tests (`.github/codeql/codeql-config.yml`).

Changes reach `main` only through a pull request, merged as a merge commit. Each one needs an approval from a code owner (`.github/CODEOWNERS`) given after its last push, and a new push dismisses earlier approvals, so an agent's pull request waits for a person to approve it.

Expand All @@ -78,7 +78,7 @@ Workflow conventions, which `workflow-lint.yml` checks with [actionlint](https:/
- actions pinned to a commit SHA with the version in a comment;
- `permissions: contents: read` unless a job needs more, and `persist-credentials: false` on checkout;
- Node from `.node-version` with `actions/setup-node`, pnpm from `packageManager` with `pnpm/action-setup`, and `pnpm install --frozen-lockfile`;
- a concurrency group per ref that cancels superseded runs.
- a concurrency group per ref that cancels superseded runs, except in a workflow `ci.yml` calls.

Slow suites (mutation testing, fuzzing, every browser, release builds) run weekly in `<tool>-scheduled.yml`, on a Monday cron plus `workflow_dispatch` so they can run on any branch. They have no `passed` job and no path filter.

Expand Down Expand Up @@ -113,8 +113,8 @@ localtoolbox has no release workflow. `pnpm zip` builds `localtoolbox.zip`, peop
2. Give its `package.json` `"type": "module"`, `"private": true`, the root's `engines.node`, `oxlint` at the root's exact version, and these scripts, which the root's `pnpm -r` scripts and CI call: `dev`, `build`, `check`, `lint` (`oxlint --type-aware`), `format` and `format:check` (`oxfmt`), `knip`, `test`, `test:coverage`, and `verify`, which runs everything CI's verify job runs.
3. Add an `.oxlintrc.json` that extends `../../.oxlintrc.json`, a strict `tsconfig.json`, a `knip.json`, a `.gitignore` for its build output, and coverage thresholds just under its current numbers. If its build output isn't already in `.oxfmtrc.json`'s ignores, add it.
4. Add `format <tool>` and `lint <tool>` jobs under `pre-commit` and `typecheck <tool>` and `tests <tool>` jobs under `pre-push` in `lefthook.yml`, with `root: apps/<tool>/`.
5. Add `.github/workflows/<tool>-ci.yml` from `localtoolbox-ci.yml`, with its own paths and a `passed` job named "`<tool>` CI passed", and `<tool>-scheduled.yml` for anything too slow for every pull request.
6. Add "`<tool>` CI passed" to the required checks in `.github/rulesets/protect-main.json`. Once the pull request merges, an admin runs `tools/ruleset.sh apply`.
5. Add `.github/workflows/<tool>-ci.yml` from `localtoolbox-ci.yml`, and `<tool>-scheduled.yml` for anything too slow for every pull request.
6. In `ci.yml`, add the tool's paths to the `changes` job's filter and an output for them, a job that calls `<tool>-ci.yml` when the output is true, and that job to `passed`'s needs and its skipped check.
7. If it runs as a service, give it a `Dockerfile` that builds from the repository root, re-include its files in `.dockerignore` (which allows only what the website needs), and have CI build and smoke-test the image as `web-ci.yml`'s `docker` job does. Add its service to `.railway/railway.ts`, building its Dockerfile from `main` once its checks pass, and run `railway config apply`.

Then do the steps every app shares, below.
Expand All @@ -123,7 +123,7 @@ Then do the steps every app shares, below.

1. Create `apps/<tool>/` and add it to `members` in the root `Cargo.toml`. Inherit `edition`, `rust-version`, `license` and `repository` from the workspace, but give it a `version` of its own: the workspace's moves with locallibrary's releases. Add `[lints] workspace = true`. Take shared dependencies from `[workspace.dependencies]` (`cargo add --package <name>`).
2. Put unit tests in each module's own `tests.rs` (`#[cfg(test)] mod tests;`), so coverage leaves them out, and integration tests in one binary, `tests/it/`.
3. `locallibrary-ci.yml` already checks the whole workspace (fmt, clippy with `-D warnings`, the tests on macOS, Linux and Windows, `cargo coverage` and its thresholds, the MSRV build on 1.91, the docs and `cargo deny`), but only when its paths change. Add `apps/<tool>/**` to its `rust` filter. The new app's code counts toward the shared coverage thresholds and has to build on the MSRV. Anything only the app needs goes in a `<tool>-ci.yml` with a `passed` job, added to the ruleset file.
3. `locallibrary-ci.yml` already checks the whole workspace (fmt, clippy with `-D warnings`, the tests on macOS, Linux and Windows, `cargo coverage` and its thresholds, the MSRV build on 1.91, the docs and `cargo deny`), but only when its paths change. Add `apps/<tool>/**` to the `locallibrary` filter in `ci.yml`. The new app's code counts toward the shared coverage thresholds and has to build on the MSRV. Anything only the app needs goes in a `<tool>-ci.yml` that `ci.yml` calls, as for a TypeScript app.
4. `locallibrary-scheduled.yml` runs cargo-mutants weekly over every workspace package but locallibrary's library and CLI, so the app is in it. If its tests are too slow to run once per mutant, leave it out there as those two are.
5. Set `publish = false` unless it's meant for crates.io: clippy's `cargo_common_metadata` wants full metadata on anything publishable, and dist (which releases on `locallibrary` tags) leaves unpublished packages out. When it gets a release of its own, set `[package.metadata.dist] dist = true`, as `apps/locallibrary/Cargo.toml` does.

Expand Down
9 changes: 6 additions & 3 deletions .github/actions/size-report/action.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
name: Size report
description: >-
A table of what this run measured (a JSON object of names and sizes in bytes, from its `sizes`
artifact) beside the same from main's latest push run that has one, written to the run's
A table of what this run measured (a JSON object of names and sizes in bytes, from its
`artifact`) beside the same from main's latest push run that has one, written to the run's
summary and to `output` for the pull request comment. Needs `actions: read`.

inputs:
sizes:
description: This run's sizes.json.
required: true
artifact:
description: The artifact sizes.json is uploaded in, which main's runs keep too.
required: true
output:
description: Where to write the Markdown.
required: true
Expand All @@ -18,7 +21,7 @@ runs:
- id: main
uses: ./.github/actions/main-artifact
with:
name: sizes
name: ${{ inputs.artifact }}
path: ${{ runner.temp }}/main-sizes
- shell: bash
env:
Expand Down
2 changes: 1 addition & 1 deletion .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

- [ ] I went through [Adding an app or plugin](https://github.com/cutenode/localnetwork/blob/main/.github/NEW_PROJECT.md) and did every step for this kind of project, and Repo checks passes
- [ ] The steps no check sees are done too: `CODEOWNERS` if someone else reviews it, a changelog line, and READMEs that say what it does
- [ ] Apps: its "… passed" check is in `.github/rulesets/protect-main.json`, and an admin runs `tools/ruleset.sh apply` once this merges
- [ ] Apps: `ci.yml` calls its workflow when its paths change, and "CI passed" needs that job
- [ ] Apps that run as a service: it has a service in `.railway/railway.ts`, applied with `railway config apply`, that deploys from `main` once checks pass

## Checklist
Expand Down
23 changes: 1 addition & 22 deletions .github/rulesets/protect-main.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,28 +51,7 @@
"do_not_enforce_on_create": false,
"required_status_checks": [
{
"context": "Web CI passed"
},
{
"context": "locallibrary CI passed"
},
{
"context": "localtoolbox CI passed"
},
{
"context": "Mix report passed"
},
{
"context": "npm advisories passed"
},
{
"context": "Workflow lint passed"
},
{
"context": "Repo checks passed"
},
{
"context": "atstore CI passed"
"context": "CI passed"
}
]
}
Expand Down
Loading
Loading