Skip to content

Commit 1b461a5

Browse files
Automate project checks and PR conventions (#17)
1 parent 055d891 commit 1b461a5

13 files changed

Lines changed: 616 additions & 32 deletions

.github/CODEOWNERS

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/src/ @SandroMaglione
2+
/scripts/ @SandroMaglione
3+
/.github/ @SandroMaglione
4+
/package.json @SandroMaglione
5+
/pnpm-lock.yaml @SandroMaglione
6+
/tsconfig.build.json @SandroMaglione
7+
/.changeset/config.json @SandroMaglione

.github/ISSUE_TEMPLATE/proposal.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
name: Change proposal
3+
about: Discuss a problem or API change before implementation
4+
title: ""
5+
labels: ""
6+
assignees: ""
7+
---
8+
9+
## Problem or use case
10+
11+
What are you trying to do, and what is difficult or impossible with the current API?
12+
13+
## Proposed direction
14+
15+
Describe the behavior or public API you would like to add or change.
16+
17+
## Example
18+
19+
Show a small example of how the proposed API would be used.
20+
21+
## Additional context
22+
23+
Include alternatives, constraints, or related issues.
24+
25+
Please wait for the proposal to be discussed before opening a pull request.

.github/pull_request_template.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
## Summary
2+
3+
-
4+
5+
## Changeset
6+
7+
- [ ] Added or updated for a library or package-metadata change
8+
- [ ] Not required because this PR does not change `src/` or `package.json`
9+
10+
## Validation
11+
12+
- [ ] `pnpm check`
13+
- [ ] Relevant example checks, when examples changed
14+
- [ ] Reviewed the automated type-performance report, when the public TypeScript API or inference changed
15+
16+
<!--
17+
The type-performance workflow posts and updates the base-versus-PR comparison automatically.
18+
Do not copy a manually measured table into this description.
19+
-->

.github/workflows/ci.yml

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ on:
55
push:
66
branches: [main]
77

8+
concurrency:
9+
group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
10+
cancel-in-progress: true
11+
812
permissions:
913
contents: read
1014

@@ -21,46 +25,65 @@ jobs:
2125
- run: pnpm install --frozen-lockfile
2226
- run: pnpm check
2327

24-
pokemon-example:
28+
changeset-policy:
29+
if: github.event_name == 'pull_request'
2530
runs-on: ubuntu-latest
26-
defaults:
27-
run:
28-
working-directory: examples/pokemon
2931
steps:
3032
- uses: actions/checkout@v7
31-
- uses: pnpm/action-setup@v6
32-
with:
33-
package_json_file: examples/pokemon/package.json
34-
- uses: actions/setup-node@v7
3533
with:
36-
node-version: 24
37-
cache: pnpm
38-
cache-dependency-path: |
39-
pnpm-lock.yaml
40-
examples/pokemon/pnpm-lock.yaml
41-
- run: pnpm --dir ../.. install --frozen-lockfile
42-
- run: pnpm --dir ../.. build
43-
- run: pnpm install --frozen-lockfile
44-
- run: pnpm check
34+
fetch-depth: 0
35+
- run: >-
36+
node scripts/check-changeset.mjs
37+
--base ${{ github.event.pull_request.base.sha }}
38+
--head ${{ github.event.pull_request.head.sha }}
39+
40+
discover-examples:
41+
runs-on: ubuntu-latest
42+
outputs:
43+
matrix: ${{ steps.examples.outputs.matrix }}
44+
steps:
45+
- uses: actions/checkout@v7
46+
- id: examples
47+
run: echo "matrix=$(node scripts/list-examples.mjs)" >> "$GITHUB_OUTPUT"
4548

46-
platformer-example:
49+
example:
50+
needs: discover-examples
51+
strategy:
52+
fail-fast: false
53+
matrix:
54+
include: ${{ fromJSON(needs.discover-examples.outputs.matrix) }}
55+
name: example (${{ matrix.example }})
4756
runs-on: ubuntu-latest
4857
defaults:
4958
run:
50-
working-directory: examples/platformer
59+
working-directory: ${{ matrix.directory }}
5160
steps:
5261
- uses: actions/checkout@v7
5362
- uses: pnpm/action-setup@v6
5463
with:
55-
package_json_file: examples/platformer/package.json
64+
package_json_file: ${{ matrix.directory }}/package.json
5665
- uses: actions/setup-node@v7
5766
with:
5867
node-version: 24
5968
cache: pnpm
6069
cache-dependency-path: |
6170
pnpm-lock.yaml
62-
examples/platformer/pnpm-lock.yaml
71+
${{ matrix.directory }}/pnpm-lock.yaml
6372
- run: pnpm --dir ../.. install --frozen-lockfile
6473
- run: pnpm --dir ../.. build
6574
- run: pnpm install --frozen-lockfile
6675
- run: pnpm check
76+
77+
examples:
78+
if: always()
79+
needs: [discover-examples, example]
80+
name: examples
81+
runs-on: ubuntu-latest
82+
steps:
83+
- name: Require every example check to pass
84+
env:
85+
DISCOVERY_RESULT: ${{ needs.discover-examples.result }}
86+
EXAMPLE_RESULT: ${{ needs.example.result }}
87+
run: |
88+
test "$DISCOVERY_RESULT" = "success"
89+
test "$EXAMPLE_RESULT" = "success"
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
name: Type performance comment
2+
3+
on:
4+
workflow_run:
5+
workflows: [Type performance]
6+
types: [completed]
7+
8+
concurrency:
9+
group: type-performance-comment-${{ github.event.workflow_run.head_repository.id }}-${{ github.event.workflow_run.head_branch }}
10+
cancel-in-progress: true
11+
12+
permissions:
13+
actions: read
14+
contents: read
15+
issues: write
16+
pull-requests: read
17+
18+
jobs:
19+
comment:
20+
if: >-
21+
github.event.workflow_run.event == 'pull_request' &&
22+
github.event.workflow_run.conclusion == 'success'
23+
runs-on: ubuntu-latest
24+
steps:
25+
- name: Check out trusted reporting code
26+
uses: actions/checkout@v7
27+
28+
- name: Download performance report
29+
uses: actions/download-artifact@v8
30+
with:
31+
name: type-performance-report
32+
path: report
33+
github-token: ${{ secrets.GITHUB_TOKEN }}
34+
run-id: ${{ github.event.workflow_run.id }}
35+
36+
- name: Render report from validated benchmark data
37+
run: >-
38+
node scripts/compare-type-performance.mjs
39+
report/before.json
40+
report/after.json
41+
> "$RUNNER_TEMP/report.md"
42+
43+
- name: Create or update pull request comment
44+
uses: actions/github-script@v9
45+
env:
46+
REPORT_PATH: ${{ runner.temp }}/report.md
47+
with:
48+
script: |
49+
const fs = require("node:fs")
50+
const marker = "<!-- effect-machine-type-performance -->"
51+
const report = fs.readFileSync(process.env.REPORT_PATH, "utf8")
52+
53+
if (Buffer.byteLength(report, "utf8") > 60_000) {
54+
core.setFailed("Type-performance report exceeds the safe comment size")
55+
return
56+
}
57+
58+
const run = context.payload.workflow_run
59+
let pull = run.pull_requests?.[0]
60+
61+
if (pull === undefined) {
62+
try {
63+
const associated = await github.rest.repos.listPullRequestsAssociatedWithCommit({
64+
...context.repo,
65+
commit_sha: run.head_sha
66+
})
67+
pull = associated.data.find((candidate) =>
68+
candidate.base.repo.full_name === `${context.repo.owner}/${context.repo.repo}`
69+
)
70+
} catch (error) {
71+
core.warning(`Unable to look up a pull request for ${run.head_sha}: ${error.message}`)
72+
}
73+
}
74+
75+
if (pull === undefined) {
76+
core.notice("No pull request is associated with this workflow run")
77+
return
78+
}
79+
80+
const body = `${marker}\n${report}`
81+
const comments = await github.paginate(github.rest.issues.listComments, {
82+
...context.repo,
83+
issue_number: pull.number,
84+
per_page: 100
85+
})
86+
const previous = comments.find((comment) =>
87+
comment.user?.type === "Bot" && comment.body?.startsWith(marker)
88+
)
89+
90+
if (previous === undefined) {
91+
await github.rest.issues.createComment({
92+
...context.repo,
93+
issue_number: pull.number,
94+
body
95+
})
96+
} else {
97+
await github.rest.issues.updateComment({
98+
...context.repo,
99+
comment_id: previous.id,
100+
body
101+
})
102+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
name: Type performance
2+
3+
on:
4+
pull_request:
5+
6+
concurrency:
7+
group: type-performance-${{ github.event.pull_request.number }}
8+
cancel-in-progress: true
9+
10+
permissions:
11+
contents: read
12+
13+
jobs:
14+
type-performance:
15+
name: type-performance
16+
runs-on: ubuntu-latest
17+
steps:
18+
- name: Check out base
19+
uses: actions/checkout@v7
20+
with:
21+
path: base
22+
ref: ${{ github.event.pull_request.base.sha }}
23+
24+
- name: Check out pull request
25+
uses: actions/checkout@v7
26+
with:
27+
path: head
28+
repository: ${{ github.event.pull_request.head.repo.full_name }}
29+
ref: ${{ github.event.pull_request.head.sha }}
30+
31+
- uses: pnpm/action-setup@v6
32+
with:
33+
package_json_file: head/package.json
34+
35+
- uses: actions/setup-node@v7
36+
with:
37+
node-version: 24
38+
cache: pnpm
39+
cache-dependency-path: |
40+
base/pnpm-lock.yaml
41+
head/pnpm-lock.yaml
42+
43+
- name: Install and build base
44+
run: |
45+
pnpm --dir base install --frozen-lockfile
46+
pnpm --dir base build
47+
48+
- name: Install and build pull request
49+
run: |
50+
pnpm --dir head install --frozen-lockfile
51+
pnpm --dir head build
52+
53+
- name: Measure base and pull request
54+
run: |
55+
node head/scripts/type-performance.mjs --root base --json --allow-missing > "$RUNNER_TEMP/before.json"
56+
node head/scripts/type-performance.mjs --root head --json > "$RUNNER_TEMP/after.json"
57+
node head/scripts/compare-type-performance.mjs "$RUNNER_TEMP/before.json" "$RUNNER_TEMP/after.json" > "$RUNNER_TEMP/report.md"
58+
cat "$RUNNER_TEMP/report.md" >> "$GITHUB_STEP_SUMMARY"
59+
60+
- name: Upload report for the comment workflow
61+
uses: actions/upload-artifact@v7
62+
with:
63+
name: type-performance-report
64+
path: |
65+
${{ runner.temp }}/before.json
66+
${{ runner.temp }}/after.json
67+
if-no-files-found: error
68+
retention-days: 7

AGENTS.md

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,29 @@ The goal is eventually to merge this inside the core of the `effect` library, so
66

77
Make architectural decisions for the long term. Do not accept a stopgap that only works for now and is meant to be replaced later.
88

9-
## Feature verification workflow
9+
## Verification
1010

11-
Before implementing a feature, run:
11+
Run:
1212

1313
```sh
14-
pnpm perf:types
14+
pnpm check
1515
```
1616

17-
Record the type-performance results as the baseline for the feature.
18-
19-
After implementing the feature, run:
17+
For changes that can affect the public TypeScript API or its inference, also run:
2018

2119
```sh
22-
pnpm typecheck
2320
pnpm perf:types
2421
```
2522

26-
Compare the final type-performance results with the baseline. When reporting the completed work, include the before and after results and call out the additional type-instantiation cost of the feature, including regressions or improvements.
23+
When an example changes, run its own check from the example directory:
24+
25+
```sh
26+
pnpm check
27+
```
28+
29+
Every package directly below `examples/` must have a `check` script and a committed lockfile.
30+
31+
## Pull request conventions
32+
33+
- Add or update a changeset for changes under `src/` or changes to `package.json`.
34+
- Fill in the pull request template, including the validation performed and the changeset decision.

CONTRIBUTING.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Contributing
2+
3+
This project generally does not accept unsolicited pull requests. Open an issue first describing the problem or use case and, for API changes, the public API you want to add or change.
4+
5+
Wait for the proposal to be discussed and accepted before starting an implementation or opening a pull request. Pull requests without prior agreement may be closed.

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,9 @@ Individual commands are available for `build`, `test`, `test:types`,
495495
packs the package, imports all public entrypoints, and compiles a strict
496496
TypeScript consumer with `skipLibCheck: false`.
497497

498+
Read [CONTRIBUTING.md](./CONTRIBUTING.md) before proposing a change. Pull
499+
requests receive an automated base-versus-head type-instantiation report.
500+
498501
## Examples
499502

500503
The [platformer statechart example](./examples/platformer) is a playable SVG
@@ -510,6 +513,11 @@ invoked child statecharts, typed emissions, and Atom reactivity. It uses a local
510513
`file:` dependency on this package while retaining an isolated dependency graph,
511514
lockfile, build, and CI job.
512515

516+
The [playground](./examples/playground) collects focused interactive examples
517+
for traffic lights, turnstiles, media players, microwaves, and worker-backed
518+
machines. CI discovers every direct package under `examples/` and runs its
519+
`check` script automatically.
520+
513521
## Releases
514522

515523
Add a changeset with `pnpm changeset`. CI validates frozen installation and the

0 commit comments

Comments
 (0)