@knpkv/jira-clockify 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +121 -0
- package/dist/package.json +47 -0
- package/dist/src/bin.js +28 -0
- package/dist/src/bin.js.map +1 -0
- package/dist/src/cli/auth.js +169 -0
- package/dist/src/cli/auth.js.map +1 -0
- package/dist/src/cli/config.js +107 -0
- package/dist/src/cli/config.js.map +1 -0
- package/dist/src/cli/fuzzySelect.js +149 -0
- package/dist/src/cli/fuzzySelect.js.map +1 -0
- package/dist/src/cli/layers.js +59 -0
- package/dist/src/cli/layers.js.map +1 -0
- package/dist/src/cli/list.js +27 -0
- package/dist/src/cli/list.js.map +1 -0
- package/dist/src/cli/setup.js +134 -0
- package/dist/src/cli/setup.js.map +1 -0
- package/dist/src/cli/timer/discard.js +33 -0
- package/dist/src/cli/timer/discard.js.map +1 -0
- package/dist/src/cli/timer/edit.js +139 -0
- package/dist/src/cli/timer/edit.js.map +1 -0
- package/dist/src/cli/timer/index.js +12 -0
- package/dist/src/cli/timer/index.js.map +1 -0
- package/dist/src/cli/timer/log.js +96 -0
- package/dist/src/cli/timer/log.js.map +1 -0
- package/dist/src/cli/timer/start.js +156 -0
- package/dist/src/cli/timer/start.js.map +1 -0
- package/dist/src/cli/timer/status.js +80 -0
- package/dist/src/cli/timer/status.js.map +1 -0
- package/dist/src/cli/timer/stop.js +96 -0
- package/dist/src/cli/timer/stop.js.map +1 -0
- package/dist/src/cli/timer.js +7 -0
- package/dist/src/cli/timer.js.map +1 -0
- package/dist/src/main.js +24 -0
- package/dist/src/main.js.map +1 -0
- package/dist/src/services/ClockifyAuth.js +77 -0
- package/dist/src/services/ClockifyAuth.js.map +1 -0
- package/dist/src/services/ConfigService.js +66 -0
- package/dist/src/services/ConfigService.js.map +1 -0
- package/dist/src/services/StateWriter.js +69 -0
- package/dist/src/services/StateWriter.js.map +1 -0
- package/dist/src/services/TicketService.js +106 -0
- package/dist/src/services/TicketService.js.map +1 -0
- package/dist/src/services/TimerService.js +271 -0
- package/dist/src/services/TimerService.js.map +1 -0
- package/dist/src/tui/App.js +204 -0
- package/dist/src/tui/App.js.map +1 -0
- package/dist/src/tui/atoms/runtime.js +9 -0
- package/dist/src/tui/atoms/runtime.js.map +1 -0
- package/dist/src/tui/atoms/tickets.js +17 -0
- package/dist/src/tui/atoms/tickets.js.map +1 -0
- package/dist/src/tui/atoms/timer.js +31 -0
- package/dist/src/tui/atoms/timer.js.map +1 -0
- package/dist/src/tui/atoms/ui.js +10 -0
- package/dist/src/tui/atoms/ui.js.map +1 -0
- package/dist/src/tui/components/BigTimer.js +60 -0
- package/dist/src/tui/components/BigTimer.js.map +1 -0
- package/dist/src/tui/components/Footer.js +16 -0
- package/dist/src/tui/components/Footer.js.map +1 -0
- package/dist/src/tui/components/Header.js +16 -0
- package/dist/src/tui/components/Header.js.map +1 -0
- package/dist/src/tui/components/PopupInput.js +30 -0
- package/dist/src/tui/components/PopupInput.js.map +1 -0
- package/dist/src/tui/components/PopupMessage.js +47 -0
- package/dist/src/tui/components/PopupMessage.js.map +1 -0
- package/dist/src/tui/components/TicketList.js +40 -0
- package/dist/src/tui/components/TicketList.js.map +1 -0
- package/dist/src/tui/components/TicketRow.js +48 -0
- package/dist/src/tui/components/TicketRow.js.map +1 -0
- package/dist/src/tui/components/TimerDisplay.js +30 -0
- package/dist/src/tui/components/TimerDisplay.js.map +1 -0
- package/dist/src/tui/components/index.js +12 -0
- package/dist/src/tui/components/index.js.map +1 -0
- package/dist/src/tui/context/theme.js +15 -0
- package/dist/src/tui/context/theme.js.map +1 -0
- package/dist/src/tui/hooks/useElapsedTimer.js +35 -0
- package/dist/src/tui/hooks/useElapsedTimer.js.map +1 -0
- package/dist/src/tui/hooks/useTerminalSize.js +32 -0
- package/dist/src/tui/hooks/useTerminalSize.js.map +1 -0
- package/dist/src/utils/time.js +23 -0
- package/dist/src/utils/time.js.map +1 -0
- package/dist/test/TimerService.test.js +355 -0
- package/dist/test/TimerService.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/nvim/lua/jcf/branch.lua +26 -0
- package/nvim/lua/jcf/float.lua +87 -0
- package/nvim/lua/jcf/init.lua +70 -0
- package/nvim/lua/jcf/state.lua +56 -0
- package/nvim/lua/jcf/statusline.lua +31 -0
- package/nvim/lua/jcf/telescope.lua +50 -0
- package/nvim/plugin/jcf.vim +2 -0
- package/package.json +47 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
local M = {}
|
|
2
|
+
|
|
3
|
+
local patterns = {
|
|
4
|
+
"^feature/(%u+%-%d+)",
|
|
5
|
+
"^bugfix/(%u+%-%d+)",
|
|
6
|
+
"^hotfix/(%u+%-%d+)",
|
|
7
|
+
"^(%u+%-%d+)/",
|
|
8
|
+
"^(%u+%-%d+)%-",
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function M.detect()
|
|
12
|
+
local branch = vim.fn.system("git branch --show-current 2>/dev/null"):gsub("%s+", "")
|
|
13
|
+
if branch == "" then
|
|
14
|
+
return nil
|
|
15
|
+
end
|
|
16
|
+
branch = branch:upper()
|
|
17
|
+
for _, pat in ipairs(patterns) do
|
|
18
|
+
local key = branch:match(pat)
|
|
19
|
+
if key then
|
|
20
|
+
return key
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
return nil
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
return M
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
local M = {}
|
|
2
|
+
local win_id = nil
|
|
3
|
+
local buf_id = nil
|
|
4
|
+
|
|
5
|
+
function M.toggle(config)
|
|
6
|
+
if win_id and vim.api.nvim_win_is_valid(win_id) then
|
|
7
|
+
vim.api.nvim_win_close(win_id, true)
|
|
8
|
+
win_id = nil
|
|
9
|
+
buf_id = nil
|
|
10
|
+
return
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
local width = math.floor(vim.o.columns * (config.float.width or 0.8))
|
|
14
|
+
local height = math.floor(vim.o.lines * (config.float.height or 0.8))
|
|
15
|
+
local col = math.floor((vim.o.columns - width) / 2)
|
|
16
|
+
local row = math.floor((vim.o.lines - height) / 2)
|
|
17
|
+
|
|
18
|
+
buf_id = vim.api.nvim_create_buf(false, true)
|
|
19
|
+
win_id = vim.api.nvim_open_win(buf_id, true, {
|
|
20
|
+
relative = "editor",
|
|
21
|
+
width = width,
|
|
22
|
+
height = height,
|
|
23
|
+
col = col,
|
|
24
|
+
row = row,
|
|
25
|
+
style = "minimal",
|
|
26
|
+
border = "rounded",
|
|
27
|
+
title = " jcf ",
|
|
28
|
+
title_pos = "center",
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
vim.fn.termopen(config.binary or "jcf")
|
|
32
|
+
vim.cmd("startinsert")
|
|
33
|
+
|
|
34
|
+
-- Close on window leave
|
|
35
|
+
vim.api.nvim_create_autocmd("WinLeave", {
|
|
36
|
+
buffer = buf_id,
|
|
37
|
+
once = true,
|
|
38
|
+
callback = function()
|
|
39
|
+
if win_id and vim.api.nvim_win_is_valid(win_id) then
|
|
40
|
+
vim.api.nvim_win_close(win_id, true)
|
|
41
|
+
win_id = nil
|
|
42
|
+
buf_id = nil
|
|
43
|
+
end
|
|
44
|
+
end,
|
|
45
|
+
})
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
function M.run_command(config, ...)
|
|
49
|
+
local args = { ... }
|
|
50
|
+
local binary = config.binary or "jcf"
|
|
51
|
+
|
|
52
|
+
local width = math.floor(vim.o.columns * (config.float.width or 0.8))
|
|
53
|
+
local height = math.floor(vim.o.lines * (config.float.height or 0.8))
|
|
54
|
+
local col = math.floor((vim.o.columns - width) / 2)
|
|
55
|
+
local row = math.floor((vim.o.lines - height) / 2)
|
|
56
|
+
|
|
57
|
+
local b = vim.api.nvim_create_buf(false, true)
|
|
58
|
+
local w = vim.api.nvim_open_win(b, true, {
|
|
59
|
+
relative = "editor",
|
|
60
|
+
width = width,
|
|
61
|
+
height = height,
|
|
62
|
+
col = col,
|
|
63
|
+
row = row,
|
|
64
|
+
style = "minimal",
|
|
65
|
+
border = "rounded",
|
|
66
|
+
title = " jcf " .. table.concat(args, " ") .. " ",
|
|
67
|
+
title_pos = "center",
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
local cmd_table = { binary, unpack(args) }
|
|
71
|
+
vim.fn.termopen(cmd_table, {
|
|
72
|
+
on_exit = function()
|
|
73
|
+
vim.defer_fn(function()
|
|
74
|
+
if w and vim.api.nvim_win_is_valid(w) then
|
|
75
|
+
vim.api.nvim_win_close(w, true)
|
|
76
|
+
end
|
|
77
|
+
end, 500)
|
|
78
|
+
end,
|
|
79
|
+
})
|
|
80
|
+
vim.cmd("startinsert")
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
function M.is_open()
|
|
84
|
+
return win_id ~= nil and vim.api.nvim_win_is_valid(win_id)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
return M
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
local M = {}
|
|
2
|
+
|
|
3
|
+
local defaults = {
|
|
4
|
+
binary = "jcf",
|
|
5
|
+
state_path = vim.fn.expand("~/.jcf/state.json"),
|
|
6
|
+
float = { width = 0.8, height = 0.8 },
|
|
7
|
+
auto_detect_branch = true,
|
|
8
|
+
poll_interval = 30000, -- ms, check Clockify for external changes
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
M.config = defaults
|
|
12
|
+
|
|
13
|
+
function M.setup(opts)
|
|
14
|
+
M.config = vim.tbl_deep_extend("force", defaults, opts or {})
|
|
15
|
+
|
|
16
|
+
vim.api.nvim_create_user_command("JcfToggle", function()
|
|
17
|
+
require("jcf.float").toggle(M.config)
|
|
18
|
+
end, { desc = "Toggle jcf floating window" })
|
|
19
|
+
|
|
20
|
+
vim.api.nvim_create_user_command("JcfStart", function(args)
|
|
21
|
+
local key = args.args ~= "" and args.args or nil
|
|
22
|
+
if not key and M.config.auto_detect_branch then
|
|
23
|
+
key = require("jcf.branch").detect()
|
|
24
|
+
end
|
|
25
|
+
if key then
|
|
26
|
+
vim.fn.jobstart({ M.config.binary, "start", key }, { detach = true })
|
|
27
|
+
vim.notify("jcf: starting timer on " .. key, vim.log.levels.INFO)
|
|
28
|
+
else
|
|
29
|
+
-- No key and no branch match → open jcf start in float (has built-in fuzzy selector)
|
|
30
|
+
require("jcf.float").run_command(M.config, "start")
|
|
31
|
+
end
|
|
32
|
+
end, { nargs = "?", desc = "Start jcf timer" })
|
|
33
|
+
|
|
34
|
+
vim.api.nvim_create_user_command("JcfStop", function()
|
|
35
|
+
-- Open in float so the comment prompt is interactive
|
|
36
|
+
require("jcf.float").run_command(M.config, "stop")
|
|
37
|
+
end, { desc = "Stop jcf timer" })
|
|
38
|
+
|
|
39
|
+
vim.api.nvim_create_user_command("JcfDiscard", function()
|
|
40
|
+
require("jcf.float").run_command(M.config, "discard")
|
|
41
|
+
end, { desc = "Discard jcf timer (delete Clockify entry)" })
|
|
42
|
+
|
|
43
|
+
vim.api.nvim_create_user_command("JcfLog", function(args)
|
|
44
|
+
local cmd_args = { "log" }
|
|
45
|
+
if args.args ~= "" then
|
|
46
|
+
for word in args.args:gmatch("%S+") do
|
|
47
|
+
table.insert(cmd_args, word)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
require("jcf.float").run_command(M.config, unpack(cmd_args))
|
|
51
|
+
end, { nargs = "*", desc = "Log past work manually" })
|
|
52
|
+
|
|
53
|
+
vim.api.nvim_create_user_command("JcfEdit", function()
|
|
54
|
+
require("jcf.float").run_command(M.config, "edit")
|
|
55
|
+
end, { desc = "Edit running timer" })
|
|
56
|
+
|
|
57
|
+
vim.api.nvim_create_user_command("JcfStatus", function()
|
|
58
|
+
require("jcf.float").run_command(M.config, "status")
|
|
59
|
+
end, { desc = "Show timer status" })
|
|
60
|
+
|
|
61
|
+
-- Start polling for external timer changes
|
|
62
|
+
require("jcf.state").start_poll(M.config, M.config.poll_interval)
|
|
63
|
+
|
|
64
|
+
-- Clean up poll timer on exit
|
|
65
|
+
vim.api.nvim_create_autocmd("VimLeave", {
|
|
66
|
+
callback = function() require("jcf.state").stop_poll() end
|
|
67
|
+
})
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
return M
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
local M = {}
|
|
2
|
+
local uv = vim.loop or vim.uv
|
|
3
|
+
local cached = { active = false }
|
|
4
|
+
local last_mtime = 0
|
|
5
|
+
local poll_timer = nil
|
|
6
|
+
|
|
7
|
+
function M.read(state_path)
|
|
8
|
+
local path = state_path or vim.fn.expand("~/.jcf/state.json")
|
|
9
|
+
local stat = uv.fs_stat(path)
|
|
10
|
+
if not stat then
|
|
11
|
+
return cached
|
|
12
|
+
end
|
|
13
|
+
if stat.mtime.sec == last_mtime then
|
|
14
|
+
return cached
|
|
15
|
+
end
|
|
16
|
+
last_mtime = stat.mtime.sec
|
|
17
|
+
local f = io.open(path, "r")
|
|
18
|
+
if f then
|
|
19
|
+
local content = f:read("*a")
|
|
20
|
+
f:close()
|
|
21
|
+
local ok, data = pcall(vim.json.decode, content)
|
|
22
|
+
if ok and data then
|
|
23
|
+
cached = data
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
return cached
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
-- Periodically run `jcf status` to sync state file with Clockify
|
|
30
|
+
-- This detects externally stopped timers
|
|
31
|
+
function M.start_poll(config, interval_ms)
|
|
32
|
+
if poll_timer then
|
|
33
|
+
return
|
|
34
|
+
end
|
|
35
|
+
interval_ms = interval_ms or 30000 -- 30s default
|
|
36
|
+
|
|
37
|
+
poll_timer = uv.new_timer()
|
|
38
|
+
poll_timer:start(interval_ms, interval_ms, vim.schedule_wrap(function()
|
|
39
|
+
-- jcf status updates ~/.jcf/state.json and detects external stops
|
|
40
|
+
vim.fn.jobstart({ config.binary or "jcf", "status" }, {
|
|
41
|
+
on_stdout = function() end,
|
|
42
|
+
on_stderr = function() end,
|
|
43
|
+
detach = true,
|
|
44
|
+
})
|
|
45
|
+
end))
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
function M.stop_poll()
|
|
49
|
+
if poll_timer then
|
|
50
|
+
poll_timer:stop()
|
|
51
|
+
poll_timer:close()
|
|
52
|
+
poll_timer = nil
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
return M
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
local state = require("jcf.state")
|
|
2
|
+
|
|
3
|
+
local M = {}
|
|
4
|
+
|
|
5
|
+
function M.status()
|
|
6
|
+
local config = require("jcf").config
|
|
7
|
+
local s = state.read(config.state_path)
|
|
8
|
+
if not s.active then
|
|
9
|
+
return ""
|
|
10
|
+
end
|
|
11
|
+
local started = s.startedAt_unix or 0
|
|
12
|
+
if started == 0 then
|
|
13
|
+
return s.ticketKey or "??"
|
|
14
|
+
end
|
|
15
|
+
local elapsed = s.elapsed or (os.time() - started)
|
|
16
|
+
local h = math.floor(elapsed / 3600)
|
|
17
|
+
local m = math.floor((elapsed % 3600) / 60)
|
|
18
|
+
local sec = elapsed % 60
|
|
19
|
+
local summary = s.summary or ""
|
|
20
|
+
if #summary > 30 then
|
|
21
|
+
summary = summary:sub(1, 30) .. "…"
|
|
22
|
+
end
|
|
23
|
+
return string.format("● %s %s %02d:%02d:%02d", s.ticketKey or "??", summary, h, m, sec)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
function M.is_active()
|
|
27
|
+
local config = require("jcf").config
|
|
28
|
+
return state.read(config.state_path).active == true
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
return M
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
local M = {}
|
|
2
|
+
|
|
3
|
+
function M.pick(config)
|
|
4
|
+
local ok, pickers = pcall(require, "telescope.pickers")
|
|
5
|
+
if not ok then
|
|
6
|
+
vim.notify("jcf: telescope.nvim not installed", vim.log.levels.WARN)
|
|
7
|
+
return
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
local finders = require("telescope.finders")
|
|
11
|
+
local conf = require("telescope.config").values
|
|
12
|
+
local actions = require("telescope.actions")
|
|
13
|
+
local action_state = require("telescope.actions.state")
|
|
14
|
+
|
|
15
|
+
-- Get tickets via jcf list --json
|
|
16
|
+
local result = vim.fn.system({ config.binary, "list", "--json" })
|
|
17
|
+
local tickets_ok, tickets = pcall(vim.json.decode, result)
|
|
18
|
+
if not tickets_ok or not tickets then
|
|
19
|
+
vim.notify("jcf: failed to get tickets", vim.log.levels.ERROR)
|
|
20
|
+
return
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
pickers.new({}, {
|
|
24
|
+
prompt_title = "jcf - Select Ticket",
|
|
25
|
+
finder = finders.new_table({
|
|
26
|
+
results = tickets,
|
|
27
|
+
entry_maker = function(entry)
|
|
28
|
+
return {
|
|
29
|
+
value = entry,
|
|
30
|
+
display = string.format("%s %s [%s]", entry.key or "?", entry.summary or "", entry.status or ""),
|
|
31
|
+
ordinal = (entry.key or "") .. " " .. (entry.summary or ""),
|
|
32
|
+
}
|
|
33
|
+
end,
|
|
34
|
+
}),
|
|
35
|
+
sorter = conf.generic_sorter({}),
|
|
36
|
+
attach_mappings = function(prompt_bufnr)
|
|
37
|
+
actions.select_default:replace(function()
|
|
38
|
+
actions.close(prompt_bufnr)
|
|
39
|
+
local selection = action_state.get_selected_entry()
|
|
40
|
+
if selection and selection.value and selection.value.key then
|
|
41
|
+
vim.fn.jobstart({ config.binary, "start", selection.value.key }, { detach = true })
|
|
42
|
+
vim.notify("jcf: starting timer on " .. selection.value.key, vim.log.levels.INFO)
|
|
43
|
+
end
|
|
44
|
+
end)
|
|
45
|
+
return true
|
|
46
|
+
end,
|
|
47
|
+
}):find()
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
return M
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@knpkv/jira-clockify",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "TUI for Jira-Clockify time tracking, attachable to neovim",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "knpkv",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"bin": {
|
|
9
|
+
"jcf": "dist/src/bin.js"
|
|
10
|
+
},
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"nvim"
|
|
17
|
+
],
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@effect-atom/atom": "latest",
|
|
20
|
+
"@effect-atom/atom-react": "latest",
|
|
21
|
+
"@effect/cli": "latest",
|
|
22
|
+
"@effect/platform": "latest",
|
|
23
|
+
"@effect/platform-node": "latest",
|
|
24
|
+
"@opentui/core": "https://pkg.pr.new/anomalyco/opentui/@opentui/core@367a94087821b3b5feedd35bbb57df43b10a286e",
|
|
25
|
+
"@opentui/react": "https://pkg.pr.new/anomalyco/opentui/@opentui/react@367a94087821b3b5feedd35bbb57df43b10a286e",
|
|
26
|
+
"effect": "latest",
|
|
27
|
+
"react": "^19.1.0",
|
|
28
|
+
"@knpkv/atlassian-common": "^0.2.0",
|
|
29
|
+
"@knpkv/clockify-api-client": "^0.2.0",
|
|
30
|
+
"@knpkv/jira-api-client": "^0.2.0",
|
|
31
|
+
"@knpkv/jira-cli": "^0.1.1"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@effect/vitest": "latest",
|
|
35
|
+
"@types/node": "latest",
|
|
36
|
+
"@types/react": "^19.1.8",
|
|
37
|
+
"tsx": "^4.19.4",
|
|
38
|
+
"vitest": "latest"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"start": "tsx src/bin.ts",
|
|
42
|
+
"build": "tsc -b && chmod +x dist/src/bin.js",
|
|
43
|
+
"check": "tsc -b tsconfig.json",
|
|
44
|
+
"test": "vitest",
|
|
45
|
+
"lint": "eslint \"{src,test}/**/*.{ts,tsx}\""
|
|
46
|
+
}
|
|
47
|
+
}
|