Skip to content

Commit c7db5f6

Browse files
committed
Add linter & ci
1 parent 460cccd commit c7db5f6

10 files changed

Lines changed: 248 additions & 149 deletions

File tree

.github/workflows/lint.yml

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
name: Lint
2+
3+
on:
4+
push:
5+
branches: [main, dev]
6+
pull_request:
7+
branches: [main, dev]
8+
9+
jobs:
10+
lint:
11+
name: Run TypeScript Linting
12+
runs-on: ubuntu-latest
13+
timeout-minutes: 10
14+
15+
steps:
16+
- name: Checkout code
17+
uses: actions/checkout@v4
18+
19+
- name: Setup Node.js
20+
uses: actions/setup-node@v4
21+
with:
22+
node-version: 20
23+
cache: "npm"
24+
25+
- name: Install dependencies
26+
run: npm ci
27+
28+
- name: Run TypeScript checks
29+
run: npm run typecheck:web
30+
31+
- name: Run linting
32+
run: npm run lint
33+
lint-lua:
34+
name: Run Lua (Love2D) Linting
35+
runs-on: ubuntu-latest
36+
timeout-minutes: 10
37+
if: |
38+
contains(github.event_name, 'pull_request') ||
39+
contains(github.event.head_commit.message, 'src-lua/') ||
40+
github.event_name == 'push'
41+
steps:
42+
- name: Checkout code
43+
uses: actions/checkout@v4
44+
45+
- name: Install Lua & luarocks
46+
run: |
47+
sudo apt-get update
48+
sudo apt-get install -y lua5.4 luarocks
49+
50+
- name: Install luacheck
51+
run: luarocks install luacheck
52+
53+
- name: Run luacheck with Love2D globals
54+
run: luacheck src-lua

.luacheckrc

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
files['.luacheckrc'].global = false
2+
std = 'max+busted'
3+
max_line_length = false
4+
5+
globals = {
6+
'love',
7+
'getVersion',
8+
'getTitle'
9+
}
10+
11+
exclude_files = {
12+
'./lua_install/*',
13+
'./src-lua/feather/lib/*'
14+
}
15+
16+
ignore = {
17+
'/self',
18+
'121'
19+
}

README.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Feather 🪶 — Debug & Inspect Tool for LÖVE (love2d)
22

3-
Feather is a lightweight, extensible debug tool for [LÖVE](https://love2d.org) projects, inspired by [LoveBird](https://github.com/rxi/lovebird).
3+
Feather is an extensible debug tool for [LÖVE](https://love2d.org) projects, inspired by [LoveBird](https://github.com/rxi/lovebird).
44
It lets you **inspect logs, variables, performance metrics, and errors in real-time** over a network connection — perfect for debugging on desktop or mobile without stopping the game.
55

66
---
@@ -107,8 +107,17 @@ http://127.0.0.1:4004
107107

108108
---
109109

110+
## 📦 Dependencies
111+
112+
- [Hump Class](https://github.com/vrld/hump/blob/master/class.lua)
113+
- [Inspect](https://github.com/kikito/inspect.lua)
114+
- [json.lua](https://github.com/rxi/json.lua)
115+
116+
---
117+
110118
## 📜 License
111119

120+
112121
Feel free to use and remix this project for personal, educational, or non-commercial fun.
113122

114123
Just don’t sell it, don’t make forks that let others sell it, and don’t use it for AI training — unless I say it’s okay.

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
"lua": "sh ./scripts/watch-lua.sh",
1010
"build": "tsc && vite build",
1111
"preview": "vite preview",
12+
"typecheck:web": "tsc --noEmit -p tsconfig.json --composite false",
13+
"typecheck:lua": "luacheck src-lua",
14+
"typecheck": "npm run typecheck:web && npm run typecheck:lua",
15+
"lint": "eslint src --ext .ts,.tsx",
1216
"tauri": "tauri"
1317
},
1418
"dependencies": {

src-lua/feather/error_handler.lua

Lines changed: 123 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -3,139 +3,139 @@
33
local utf8 = require("utf8")
44

55
local function error_printer(msg, layer)
6-
print((debug.traceback("Error: " .. tostring(msg), 1 + (layer or 1)):gsub("\n[^\n]+$", "")))
6+
print((debug.traceback("Error: " .. tostring(msg), 1 + (layer or 1)):gsub("\n[^\n]+$", "")))
77
end
88

9-
function errorhandler(msg)
10-
msg = tostring(msg)
11-
12-
error_printer(msg, 2)
13-
14-
if not love.window or not love.graphics or not love.event then
15-
return
16-
end
17-
18-
if not love.graphics.isCreated() or not love.window.isOpen() then
19-
local success, status = pcall(love.window.setMode, 800, 600)
20-
if not success or not status then
21-
return
22-
end
23-
end
24-
25-
-- Reset state.
26-
if love.mouse then
27-
love.mouse.setVisible(true)
28-
love.mouse.setGrabbed(false)
29-
love.mouse.setRelativeMode(false)
30-
if love.mouse.isCursorSupported() then
31-
love.mouse.setCursor()
32-
end
33-
end
34-
if love.joystick then
35-
-- Stop all joystick vibrations.
36-
for i, v in ipairs(love.joystick.getJoysticks()) do
37-
v:setVibration()
38-
end
39-
end
40-
if love.audio then
41-
love.audio.stop()
42-
end
43-
44-
love.graphics.reset()
45-
local font = love.graphics.setNewFont(14)
46-
47-
love.graphics.setColor(1, 1, 1)
48-
49-
local trace = debug.traceback()
50-
51-
love.graphics.origin()
52-
53-
local sanitizedmsg = {}
54-
for char in msg:gmatch(utf8.charpattern) do
9+
local function errorhandler(msg)
10+
msg = tostring(msg)
11+
12+
error_printer(msg, 2)
13+
14+
if not love.window or not love.graphics or not love.event then
15+
return
16+
end
17+
18+
if not love.window.isOpen() then
19+
local success, status = pcall(love.window.setMode, 800, 600)
20+
if not success or not status then
21+
return
22+
end
23+
end
24+
25+
-- Reset state.
26+
if love.mouse then
27+
love.mouse.setVisible(true)
28+
love.mouse.setGrabbed(false)
29+
love.mouse.setRelativeMode(false)
30+
if love.mouse.isCursorSupported() then
31+
love.mouse.setCursor()
32+
end
33+
end
34+
if love.joystick then
35+
-- Stop all joystick vibrations.
36+
for _, v in ipairs(love.joystick.getJoysticks()) do
37+
v:setVibration()
38+
end
39+
end
40+
if love.audio then
41+
love.audio.stop()
42+
end
43+
44+
love.graphics.reset()
45+
love.graphics.setNewFont(14)
46+
47+
love.graphics.setColor(1, 1, 1)
48+
49+
local trace = debug.traceback()
50+
51+
love.graphics.origin()
52+
53+
local sanitizedmsg = {}
54+
for char in msg:gmatch(utf8.charpattern) do
5555
table.insert(sanitizedmsg.msg, char)
56-
end
56+
end
5757
local sanitized = table.concat(sanitizedmsg)
5858

5959
local err = {}
6060

6161
table.insert(err, "Error\n")
6262
table.insert(err, sanitized)
6363

64-
if #sanitized ~= #msg then
65-
table.insert(err, "Invalid UTF-8 string in error message.")
66-
end
67-
68-
table.insert(err, "\n")
69-
70-
for l in trace:gmatch("(.-)\n") do
71-
if not l:match("boot.lua") then
72-
l = l:gsub("stack traceback:", "Traceback\n")
73-
table.insert(err, l)
74-
end
75-
end
76-
77-
local p = table.concat(err, "\n")
78-
79-
p = p:gsub("\t", "")
80-
p = p:gsub('%[string "(.-)"%]', "%1")
81-
82-
local function draw()
83-
if not love.graphics.isActive() then
84-
return
85-
end
86-
local pos = 70
87-
love.graphics.clear(89 / 255, 157 / 255, 220 / 255)
88-
love.graphics.printf(p, pos, pos, love.graphics.getWidth() - pos)
89-
love.graphics.present()
90-
end
91-
92-
local fullErrorText = p
93-
local function copyToClipboard()
94-
if not love.system then
95-
return
96-
end
97-
love.system.setClipboardText(fullErrorText)
98-
p = p .. "\nCopied to clipboard!"
99-
end
100-
101-
if love.system then
102-
p = p .. "\n\nPress Ctrl+C or tap to copy this error"
103-
end
104-
105-
return function()
106-
love.event.pump()
107-
108-
for e, a, b, c in love.event.poll() do
109-
if e == "quit" then
110-
return 1
111-
elseif e == "keypressed" and a == "escape" then
112-
return 1
113-
elseif e == "keypressed" and a == "c" and love.keyboard.isDown("lctrl", "rctrl") then
114-
copyToClipboard()
115-
elseif e == "touchpressed" then
116-
local name = love.window.getTitle()
117-
if #name == 0 or name == "Untitled" then
118-
name = "Game"
119-
end
120-
local buttons = { "OK", "Cancel" }
121-
if love.system then
122-
buttons[3] = "Copy to clipboard"
123-
end
124-
local pressed = love.window.showMessageBox("Quit " .. name .. "?", "", buttons)
125-
if pressed == 1 then
126-
return 1
127-
elseif pressed == 3 then
128-
copyToClipboard()
129-
end
130-
end
131-
end
132-
133-
draw()
134-
135-
if love.timer then
136-
love.timer.sleep(0.1)
137-
end
138-
end
64+
if #sanitized ~= #msg then
65+
table.insert(err, "Invalid UTF-8 string in error message.")
66+
end
67+
68+
table.insert(err, "\n")
69+
70+
for l in trace:gmatch("(.-)\n") do
71+
if not l:match("boot.lua") then
72+
l = l:gsub("stack traceback:", "Traceback\n")
73+
table.insert(err, l)
74+
end
75+
end
76+
77+
local p = table.concat(err, "\n")
78+
79+
p = p:gsub("\t", "")
80+
p = p:gsub('%[string "(.-)"%]', "%1")
81+
82+
local function draw()
83+
if not love.graphics.isActive() then
84+
return
85+
end
86+
local pos = 70
87+
love.graphics.clear(89 / 255, 157 / 255, 220 / 255)
88+
love.graphics.printf(p, pos, pos, love.graphics.getWidth() - pos)
89+
love.graphics.present()
90+
end
91+
92+
local fullErrorText = p
93+
local function copyToClipboard()
94+
if not love.system then
95+
return
96+
end
97+
love.system.setClipboardText(fullErrorText)
98+
p = p .. "\nCopied to clipboard!"
99+
end
100+
101+
if love.system then
102+
p = p .. "\n\nPress Ctrl+C or tap to copy this error"
103+
end
104+
105+
return function()
106+
love.event.pump()
107+
108+
for e, a, _, _ in love.event.poll() do
109+
if e == "quit" then
110+
return 1
111+
elseif e == "keypressed" and a == "escape" then
112+
return 1
113+
elseif e == "keypressed" and a == "c" and love.keyboard.isDown("lctrl", "rctrl") then
114+
copyToClipboard()
115+
elseif e == "touchpressed" then
116+
local name = love.window.getTitle()
117+
if #name == 0 or name == "Untitled" then
118+
name = "Game"
119+
end
120+
local buttons = { "OK", "Cancel" }
121+
if love.system then
122+
buttons[3] = "Copy to clipboard"
123+
end
124+
local pressed = love.window.showMessageBox("Quit " .. name .. "?", "", buttons)
125+
if pressed == 1 then
126+
return 1
127+
elseif pressed == 3 then
128+
copyToClipboard()
129+
end
130+
end
131+
end
132+
133+
draw()
134+
135+
if love.timer then
136+
love.timer.sleep(0.1)
137+
end
138+
end
139139
end
140140

141141
return errorhandler

0 commit comments

Comments
 (0)