Skip to content

Commit 27829a4

Browse files
authored
πŸš€ release: v0.1.1
## Release v0.1.1 ### 🌈 Improvements - reduce cognitive complexity in health check handler (#9) - detect all Biome finding severities as failures (#8) - revamp badge row (#21) - add Renovate commit format and squash strategy (#10) - consolidate test utilities (#6) ### 🧺 Chores - update undici to v7 - update install-action digest --- **After merging**, the tag-release job will automatically: - Tag this commit as `v0.1.1` - Create a GitHub Release with the changelog
2 parents 43f3f72 + 5802a4d commit 27829a4

23 files changed

Lines changed: 307 additions & 221 deletions

β€Ž.github/workflows/ci-bun.ymlβ€Ž

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525
matrix:
2626
include:
2727
- name: Lint
28-
command: bun biome ci --reporter=github
28+
command: bun scripts/biome-lint.ts ci --reporter=github
2929
- name: Type Check
3030
command: echo "::add-matcher::.github/matchers/tsc.json" && tsc --noEmit --skipLibCheck --pretty false
3131
- name: Test

β€Ž.github/workflows/ci-release.ymlβ€Ž

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ jobs:
4848
token: ${{ steps.app-token.outputs.token }}
4949

5050
- name: Install git-cliff
51-
uses: taiki-e/install-action@f23382d582832e41d5eb4fff2bddb06bc5adf8d3 # v2
51+
uses: taiki-e/install-action@b18b9d93a43496aeda12369e7563d9251abc2fe1 # v2
5252
with:
5353
tool: git-cliff
5454

@@ -175,7 +175,7 @@ jobs:
175175
fetch-depth: 0
176176

177177
- name: Install git-cliff
178-
uses: taiki-e/install-action@f23382d582832e41d5eb4fff2bddb06bc5adf8d3 # v2
178+
uses: taiki-e/install-action@b18b9d93a43496aeda12369e7563d9251abc2fe1 # v2
179179
with:
180180
tool: git-cliff
181181

β€Ž.vscode/settings.jsonβ€Ž

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"sonarlint.connectedMode.project": {
33
"connectionId": "uniquepixels",
4-
"projectKey": "UniquePixels_Unicorn"
4+
"projectKey": "UniquePixels_unicorn"
55
}
66
}

β€ŽCHANGELOG.mdβ€Ž

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,23 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.1.1] - 2026-03-09
9+
10+
### 🌈 Improved
11+
12+
- **testing:** consolidate test utilities (#6) ([#6](https://github.com/UniquePixels/unicorn/pull/6)) (@UniquePixels)
13+
- reduce cognitive complexity in health check handler (#9) ([#9](https://github.com/UniquePixels/unicorn/pull/9)) (@UniquePixels)
14+
- **readme:** revamp badge row (#21) ([#21](https://github.com/UniquePixels/unicorn/pull/21)) (@UniquePixels)
15+
- **config:** add Renovate commit format and squash strategy (#10) ([#10](https://github.com/UniquePixels/unicorn/pull/10)) (@UniquePixels)
16+
17+
### 🦠 Fixed
18+
19+
- **qa:** detect all Biome finding severities as failures (#8) ([#8](https://github.com/UniquePixels/unicorn/pull/8)) (@UniquePixels)
820
## [0.1.0] - 2026-03-08
921

1022
### πŸ¦„ New
1123

12-
- initial framework
24+
- initial framework (@UniquePixels)
25+
[0.1.1]: https://github.com/UniquePixels/unicorn/compare/v0.1.0...v0.1.1
1326
[0.1.0]: https://github.com/UniquePixels/unicorn/releases/tag/v0.1.0
1427

β€ŽREADME.mdβ€Ž

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@
77
</p>
88

99
<p align="center">
10-
<a href="https://github.com/uniquepixels/unicorn/actions"><img src="https://img.shields.io/github/actions/workflow/status/uniquepixels/unicorn/ci-bun.yml?style=for-the-badge&label=CI" alt="CI" /></a>
1110
<a href="https://github.com/uniquepixels/unicorn/actions/workflows/ci-codeql.yml"><img src="https://img.shields.io/github/actions/workflow/status/uniquepixels/unicorn/ci-codeql.yml?style=for-the-badge&label=CodeQL" alt="CodeQL" /></a>
12-
<a href="https://securityscorecards.dev/viewer/?uri=github.com/uniquepixels/unicorn"><img src="https://img.shields.io/ossf-scorecard/github.com/uniquepixels/unicorn?style=for-the-badge&label=OpenSSF" alt="OpenSSF Scorecard" /></a>
13-
<a href="LICENSE"><img src="https://img.shields.io/github/license/uniquepixels/unicorn?style=for-the-badge" alt="License" /></a>
11+
<a href="https://sonarcloud.io/summary/overall?id=UniquePixels_unicorn"><img src="https://img.shields.io/sonar/quality_gate/UniquePixels_unicorn?server=https%3A%2F%2Fsonarcloud.io&style=for-the-badge&label=SonarCloud" alt="SonarCloud" /></a>
12+
<a href="https://securityscorecards.dev/viewer/?uri=github.com/uniquepixels/unicorn"><img src="https://img.shields.io/ossf-scorecard/github.com/UniquePixels/unicorn?style=for-the-badge&label=OpenSSF%20Scorecard" alt="OpenSSF Scorecard" /></a>
13+
<a href="https://www.bestpractices.dev/projects/12120"><img src="https://img.shields.io/cii/summary/12120?style=for-the-badge&label=OpenSSF%20Best%20Practices" alt="OpenSSF Best Practices" /></a>
1414
<a href="https://discord.gg/Dk8P8h3e9u"><img src="https://img.shields.io/badge/Discord-Join-5865F2?style=for-the-badge&logo=discord&logoColor=white" alt="Discord" /></a>
15+
<a href="https://github.com/uniquepixels/unicorn"><img src="https://img.shields.io/badge/built_with-Unicorn-6366f1?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjM1MCAyODAgMjgwIDM1MCI+PHBhdGggZmlsbD0id2hpdGUiIGQ9Ik01MjQuMzEsNjA4LjQ1Yy0uODkuMDQtMS43OS4wNC0yLjY4LjA0LTkuNDIsMC0xOC42NC0uOC0yNy42MS0yLjM4LTQyLjA1LTE0LjE1LTcyLjM0LTUzLjktNzIuMzQtMTAwLjc0LDAtMi4xNC4wNy00LjI3LjItNi4zOC45NS0xNi4xMiw1LjQ5LTMxLjI2LDEyLjg1LTQ0LjY2LTIuNjMsNS40My00LjA5LDExLjUxLTQuMDksMTcuOTQsMCwxOS42NiwxMy43MywzNi4xNCwzMi4xMiw0MC4zNGwuMDIuMDIsMTUuNzEsNS4yNGMtMi4xMyw2Ljg0LTMuMjksMTQuMTQtMy4yOSwyMS43LDAsMzEuOTIsMjAuNTUsNTkuMDUsNDkuMTMsNjguOVoiLz48cGF0aCBmaWxsPSJ3aGl0ZSIgZD0iTTU2OS42OSw0ODcuODVjMy4zNCw1LjgxLDQuOTQsMTIuNzcsNC4xNCwyMC4xMi0xLjg1LDE3LjA3LTE2Ljk3LDI5Ljk1LTM0LjEzLDI5LjMzLTEuNC0uMDUtMi43OC0uMTktNC4xMy0uNDFoLS4wMmMtMy40OC0uNTktNi43Ny0xLjctOS44MS0zLjI3di0uMDJsLTQ3LjI3LTE1Ljc1LTE1LjcxLTUuMjQtLjAyLS4wMmMtMTguMzktNC4yLTMyLjEyLTIwLjY4LTMyLjEyLTQwLjM0LDAtNi40MywxLjQ3LTEyLjUxLDQuMDktMTcuOTQtNy4zNiwxMy40LTExLjksMjguNTQtMTIuODUsNDQuNjYtLjEyLDIuMTEtLjIsNC4yNC0uMiw2LjM4LDAsNDYuODQsMzAuMjksODYuNTksNzIuMzQsMTAwLjc0LTY2LjItMTEuNTEtMTE4LjQ3LTYzLjY4LTEzMC4xMi0xMjkuODQsMTEuMDUtNTMuNCw1OC4zMy05My41NCwxMTUtOTMuNTQsMS40MSwwLDIuODIuMDIsNC4yNC4wOSw2LjEzLjIsMTIuMTUuODksMTguMDEsMi4wMmguMDJsODUuMzYtNDAuNzMtNDguNjYsNTQuNDctLjc5Ljg5LTIwLjkxLDIzLjQxLDM2LjIzLDQzLjk2LDEwLjE5LDEyLjM1di4wMmMyLjgyLDIuNDMsNS4yNCw1LjM2LDcuMTEsOC42MXYuMDJaIi8+PC9zdmc+" alt="Built with Unicorn" /></a>
1516
<a href="https://github.com/UniquePixels/OpenCommunities"><img src=".github/assets/badge.svg" alt="open communities: aligned" /></a>
1617
</p>
1718

@@ -205,9 +206,3 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, code style, and pu
205206
[MIT](LICENSE)
206207

207208
---
208-
209-
<p align="center">
210-
<a href="https://github.com/uniquepixels/unicorn">
211-
<img src="https://img.shields.io/badge/built_with-Unicorn-6366f1?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjM1MCAyODAgMjgwIDM1MCI+PHBhdGggZmlsbD0id2hpdGUiIGQ9Ik01MjQuMzEsNjA4LjQ1Yy0uODkuMDQtMS43OS4wNC0yLjY4LjA0LTkuNDIsMC0xOC42NC0uOC0yNy42MS0yLjM4LTQyLjA1LTE0LjE1LTcyLjM0LTUzLjktNzIuMzQtMTAwLjc0LDAtMi4xNC4wNy00LjI3LjItNi4zOC45NS0xNi4xMiw1LjQ5LTMxLjI2LDEyLjg1LTQ0LjY2LTIuNjMsNS40My00LjA5LDExLjUxLTQuMDksMTcuOTQsMCwxOS42NiwxMy43MywzNi4xNCwzMi4xMiw0MC4zNGwuMDIuMDIsMTUuNzEsNS4yNGMtMi4xMyw2Ljg0LTMuMjksMTQuMTQtMy4yOSwyMS43LDAsMzEuOTIsMjAuNTUsNTkuMDUsNDkuMTMsNjguOVoiLz48cGF0aCBmaWxsPSJ3aGl0ZSIgZD0iTTU2OS42OSw0ODcuODVjMy4zNCw1LjgxLDQuOTQsMTIuNzcsNC4xNCwyMC4xMi0xLjg1LDE3LjA3LTE2Ljk3LDI5Ljk1LTM0LjEzLDI5LjMzLTEuNC0uMDUtMi43OC0uMTktNC4xMy0uNDFoLS4wMmMtMy40OC0uNTktNi43Ny0xLjctOS44MS0zLjI3di0uMDJsLTQ3LjI3LTE1Ljc1LTE1LjcxLTUuMjQtLjAyLS4wMmMtMTguMzktNC4yLTMyLjEyLTIwLjY4LTMyLjEyLTQwLjM0LDAtNi40MywxLjQ3LTEyLjUxLDQuMDktMTcuOTQtNy4zNiwxMy40LTExLjksMjguNTQtMTIuODUsNDQuNjYtLjEyLDIuMTEtLjIsNC4yNC0uMiw2LjM4LDAsNDYuODQsMzAuMjksODYuNTksNzIuMzQsMTAwLjc0LTY2LjItMTEuNTEtMTE4LjQ3LTYzLjY4LTEzMC4xMi0xMjkuODQsMTEuMDUtNTMuNCw1OC4zMy05My41NCwxMTUtOTMuNTQsMS40MSwwLDIuODIuMDIsNC4yNC4wOSw2LjEzLjIsMTIuMTUuODksMTguMDEsMi4wMmguMDJsODUuMzYtNDAuNzMtNDguNjYsNTQuNDctLjc5Ljg5LTIwLjkxLDIzLjQxLDM2LjIzLDQzLjk2LDEwLjE5LDEyLjM1di4wMmMyLjgyLDIuNDMsNS4yNCw1LjM2LDcuMTEsOC42MXYuMDJaIi8+PC9zdmc+" alt="Built with Unicorn" />
212-
</a>
213-
</p>

β€Žbun.lockβ€Ž

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

β€Žpackage.jsonβ€Ž

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "unicorn",
3-
"version": "0.1.0",
3+
"version": "0.1.1",
44
"description": "A type-safe Discord bot framework with modular Sparks, composable Guards, and Zod-validated configuration β€” built on Discord.js and Bun.",
55
"author": "Brian L. <[email protected]> (http://uniquepixels.xyz)",
66
"contributors": [],
@@ -14,7 +14,7 @@
1414
"start": "bun ./src/index.ts",
1515
"qa": "bun scripts/qa.ts",
1616
"qa:format": "biome format --write",
17-
"qa:lint": "biome check",
17+
"qa:lint": "bun scripts/biome-lint.ts check",
1818
"qa:tsc": "tsc --noEmit --skipLibCheck",
1919
"qa:test": "bun test && bun scripts/check-coverage.ts"
2020
},
@@ -32,7 +32,7 @@
3232
"bun": ">=1.3.0"
3333
},
3434
"overrides": {
35-
"undici": ">=6.23.0"
35+
"undici": "^7.22.0"
3636
},
3737
"dependencies": {
3838
"@sentry/bun": "^10.42.0",

β€Žrenovate.jsonβ€Ž

Lines changed: 14 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -5,80 +5,55 @@
55
"group:allNonMajor",
66
":separateMajorReleases"
77
],
8-
"baseBranchPatterns": [
9-
"develop"
10-
],
11-
"labels": [
12-
"dependencies"
13-
],
8+
"commitMessagePrefix": "🧺 chore(deps):",
9+
"commitMessageAction": "update",
10+
"automergeStrategy": "squash",
11+
"baseBranchPatterns": ["develop"],
12+
"labels": ["dependencies"],
1413
"timezone": "America/Chicago",
1514
"rangeStrategy": "bump",
1615
"minimumReleaseAge": "3 days",
1716
"lockFileMaintenance": {
1817
"enabled": true,
19-
"schedule": [
20-
"before 6am on monday"
21-
],
18+
"schedule": ["before 6am on monday"],
2219
"automerge": true
2320
},
2421
"packageRules": [
2522
{
2623
"description": "Automerge non-major updates during work hours",
27-
"matchUpdateTypes": [
28-
"minor",
29-
"patch"
30-
],
24+
"matchUpdateTypes": ["minor", "patch"],
3125
"automerge": true,
3226
"platformAutomerge": false,
33-
"schedule": [
34-
"after 9am and before 5pm every weekday"
35-
],
36-
"automergeSchedule": [
37-
"after 9am and before 5pm every weekday"
38-
]
27+
"schedule": ["after 9am and before 5pm every weekday"],
28+
"automergeSchedule": ["after 9am and before 5pm every weekday"]
3929
},
4030
{
4131
"description": "Group Bun runtime, types, and Docker image β€” require review",
4232
"groupName": "bun",
43-
"matchDepNames": [
44-
"bun",
45-
"@types/bun",
46-
"oven/bun"
47-
],
33+
"matchDepNames": ["bun", "@types/bun", "oven/bun"],
4834
"automerge": false
4935
},
5036
{
5137
"description": "Group Biome linter and tooling β€” require review",
5238
"groupName": "biome",
53-
"matchDepNames": [
54-
"biome",
55-
"@biomejs/biome"
56-
],
39+
"matchDepNames": ["biome", "@biomejs/biome"],
5740
"automerge": false
5841
},
5942
{
6043
"description": "Group Prisma packages β€” require review",
6144
"groupName": "prisma",
62-
"matchDepNames": [
63-
"/^prisma$/",
64-
"/^@prisma\\//"
65-
],
45+
"matchDepNames": ["/^prisma$/", "/^@prisma\\//"],
6646
"automerge": false
6747
},
6848
{
6949
"description": "Group Discord.js ecosystem",
7050
"groupName": "discord.js",
71-
"matchDepNames": [
72-
"/^discord\\.js$/",
73-
"/^@discordjs\\//"
74-
]
51+
"matchDepNames": ["/^discord\\.js$/", "/^@discordjs\\//"]
7552
},
7653
{
7754
"description": "Group Sentry packages",
7855
"groupName": "sentry",
79-
"matchDepNames": [
80-
"/^@sentry\\//"
81-
]
56+
"matchDepNames": ["/^@sentry\\//"]
8257
}
8358
]
8459
}

β€Žscripts/biome-lint.tsβ€Ž

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
* Biome lint wrapper that treats all finding severities (info, warn, error) as failures.
3+
*
4+
* Biome's exit code only reflects `error` findings by default, and `--error-on-warnings`
5+
* covers `warn` but there is no equivalent for `info`. This wrapper runs biome with all
6+
* provided arguments, streams output through to the caller (preserving GitHub reporter
7+
* annotations in CI), and exits non-zero if any findings are detected in the output.
8+
*
9+
* Usage:
10+
* bun scripts/biome-lint.ts check # local QA
11+
* bun scripts/biome-lint.ts ci --reporter=github # CI
12+
*/
13+
14+
import process from 'node:process';
15+
16+
/** Matches Biome's "Found N error(s)" / "Found N info" / "Found N warn" output. */
17+
const RE_FOUND_FINDINGS = /Found \d+ (error|warn|info)/;
18+
19+
const args = process.argv.slice(2);
20+
21+
if (args.length === 0) {
22+
process.stderr.write(
23+
'Usage: bun scripts/biome-lint.ts <check|ci> [...biome flags]\n',
24+
);
25+
process.exit(1);
26+
}
27+
28+
const proc = Bun.spawn(['bun', 'biome', ...args], {
29+
stdout: 'pipe',
30+
stderr: 'pipe',
31+
});
32+
33+
const [stdout, stderr] = await Promise.all([
34+
new Response(proc.stdout).text(),
35+
new Response(proc.stderr).text(),
36+
]);
37+
38+
const code = await proc.exited;
39+
40+
// Write buffered output so callers (QA script, CI) see it as-is
41+
if (stdout) {
42+
process.stdout.write(stdout);
43+
}
44+
if (stderr) {
45+
process.stderr.write(stderr);
46+
}
47+
48+
const combined = `${stdout}${stderr}`;
49+
const hasFindings = RE_FOUND_FINDINGS.test(combined);
50+
51+
// Exit non-zero if biome failed OR any findings were detected
52+
process.exit(code !== 0 || hasFindings ? 1 : 0);

β€Žscripts/qa.tsβ€Ž

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ const RE_FORMATTED_FILES = /Formatted (\d+) file/;
4747
const RE_CHECKED_FILES = /Checked (\d+) file/;
4848
/** Matches Biome's "Found N error(s)" output. */
4949
const RE_FOUND_ERRORS = /Found (\d+) error/;
50+
/** Matches all Biome "Found N info" or "Found N warn" occurrences in output. */
51+
const RE_FOUND_INFO_WARN_G = /Found (\d+) (info|warn)/g;
5052
/** Matches Bun test runner's "N pass" output. */
5153
const RE_TEST_PASS = /(\d+) pass/;
5254
/** Matches Bun test runner's "N fail" output. */
@@ -97,8 +99,15 @@ const steps: StepDef[] = [
9799
const checked = RE_CHECKED_FILES.exec(out);
98100
return checked ? `${checked[1]} files` : '';
99101
}
102+
const parts: string[] = [];
100103
const errors = RE_FOUND_ERRORS.exec(out);
101-
return errors ? `${errors[1]} error(s)` : 'failed';
104+
if (errors) {
105+
parts.push(`${errors[1]} error(s)`);
106+
}
107+
for (const match of out.matchAll(RE_FOUND_INFO_WARN_G)) {
108+
parts.push(`${match[1]} ${match[2]} finding(s)`);
109+
}
110+
return parts.length > 0 ? parts.join(', ') : 'failed';
102111
},
103112
},
104113
{
@@ -163,7 +172,6 @@ async function runStep(def: StepDef): Promise<StepResult> {
163172
]);
164173
const code = await proc.exited;
165174
const output = `${stdout}${stderr}`.trim();
166-
167175
return {
168176
name: def.name,
169177
passed: code === 0,
@@ -293,13 +301,45 @@ function printAndExit(results: StepResult[], passed: boolean): never {
293301
process.exit(passed ? 0 : 1);
294302
}
295303

304+
/** CSI u prefix used by the Kitty keyboard protocol (`ESC [`). */
305+
const CSI_PREFIX = '\x1b[';
306+
307+
/**
308+
* Normalizes Kitty keyboard protocol sequences to standard key values.
309+
*
310+
* Kitty protocol encodes keys as `ESC [ <code> ; <modifiers> u`. This converts
311+
* them to their standard equivalents so key handlers work across terminals.
312+
*/
313+
function normalizeKey(raw: string): string {
314+
if (!(raw.startsWith(CSI_PREFIX) && raw.endsWith('u'))) {
315+
return raw;
316+
}
317+
318+
const inner = raw.slice(CSI_PREFIX.length, -1);
319+
const [codeStr, modStr] = inner.split(';');
320+
const code = Number(codeStr);
321+
const mods = modStr ? Number(modStr) - 1 : 0;
322+
323+
// biome-ignore lint/suspicious/noBitwiseOperators: bitwise mask extracts Ctrl modifier flag per Kitty protocol spec
324+
const isCtrl = (mods & 4) !== 0;
325+
326+
// Ctrl+letter β†’ control character (e.g. Ctrl+C β†’ 0x03)
327+
if (isCtrl && code >= 97 && code <= 122) {
328+
return String.fromCodePoint(code - 96);
329+
}
330+
331+
return String.fromCodePoint(code);
332+
}
333+
296334
/** Handles a single keypress in interactive mode. */
297335
function handleKey(
298-
key: string,
336+
raw: string,
299337
state: { cursor: number },
300338
results: StepResult[],
301339
passed: boolean,
302340
): void {
341+
const key = normalizeKey(raw);
342+
303343
if (key === 'q' || key === '\x03') {
304344
process.stdout.write(SHOW_CURSOR);
305345
process.stdin.setRawMode(false);
@@ -316,7 +356,7 @@ function handleKey(
316356
render(results, state.cursor, passed);
317357
}
318358

319-
if (key === '\r' || key === ' ') {
359+
if (key === '\r' || key === '\n' || key === ' ') {
320360
const selected = results[state.cursor];
321361
if (selected) {
322362
selected.expanded = !selected.expanded;

0 commit comments

Comments
Β (0)