From becf1700b2b5e6b5bb1965e1786eca9e7278d24f Mon Sep 17 00:00:00 2001 From: John Lindquist Date: Mon, 23 Sep 2024 14:08:07 -0600 Subject: [PATCH] fix(recent): implement recent limit when adding to groups --- biome.json | 7 +- package.json | 8 +- pnpm-lock.yaml | 280 +-- src/api/kit.ts | 3796 +++++++++++++++++++++------------------- src/api/recent.ts | 6 + src/core/group.test.ts | 600 ++++--- src/core/group.ts | 420 +++-- src/core/utils.ts | 2282 +++++++++++++----------- src/main/index.ts | 510 +++--- 9 files changed, 4249 insertions(+), 3660 deletions(-) create mode 100644 src/api/recent.ts diff --git a/biome.json b/biome.json index 8e08100e..3b1354e8 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/1.7.3/schema.json", + "$schema": "https://biomejs.dev/schemas/1.9.0/schema.json", "vcs": { "enabled": true, "clientKind": "git", @@ -39,6 +39,7 @@ "useTemplate": "off", "useForOf": "off", "useLiteralEnumMembers": "off", + "useConst": "off", "useFilenamingConvention": { "level": "error", "options": { @@ -89,10 +90,10 @@ "formatter": { "enabled": true, "quoteStyle": "single", - "trailingCommas": "all", "semicolons": "always", "bracketSpacing": true, - "arrowParentheses": "always" + "arrowParentheses": "always", + "trailingCommas": "none" } }, "json": { diff --git a/package.json b/package.json index 96b8d653..ba3237f4 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "pnpm": { "overrides": { "typescript": "5.6.2", - "esbuild": "0.23.1" + "esbuild": "0.24.0" } }, "dependencies": { @@ -89,7 +89,7 @@ "chrome-trace-event": "^1.0.4", "color-name": "2.0.0", "dotenv": "^16.4.5", - "esbuild": "0.23.1", + "esbuild": "0.24.0", "express": "^4.21.0", "filesize": "10.1.6", "fs-extra": "^11.2.0", @@ -97,7 +97,7 @@ "highlight.js": "^11.10.0", "isomorphic-git": "1.27.1", "jsonfile": "6.1.0", - "keyv": "^5.0.1", + "keyv": "^5.0.3", "keyv-file": "^5.0.3", "p-retry": "6.2.0", "quick-score": "^0.2.0", @@ -117,7 +117,7 @@ "@types/debug": "4.1.12", "@types/express": "4.17.21", "@types/express-ws": "3.0.5", - "@types/node": "^22.5.5", + "@types/node": "^22.6.0", "@types/node-ipc": "9.2.3", "ava": "^6.1.3", "cross-env": "^7.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 381b0d2e..5d727a33 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,7 +6,7 @@ settings: overrides: typescript: 5.6.2 - esbuild: 0.23.1 + esbuild: 0.24.0 importers: @@ -70,8 +70,8 @@ importers: specifier: ^16.4.5 version: 16.4.5 esbuild: - specifier: 0.23.1 - version: 0.23.1 + specifier: 0.24.0 + version: 0.24.0 express: specifier: ^4.21.0 version: 4.21.0 @@ -94,8 +94,8 @@ importers: specifier: 6.1.0 version: 6.1.0 keyv: - specifier: ^5.0.1 - version: 5.0.1 + specifier: ^5.0.3 + version: 5.0.3 keyv-file: specifier: ^5.0.3 version: 5.0.3 @@ -134,7 +134,7 @@ importers: version: 5.0.0 vite: specifier: 5.4.7 - version: 5.4.7(@types/node@22.5.5) + version: 5.4.7(@types/node@22.6.0) zx: specifier: ^8.1.8 version: 8.1.8 @@ -156,8 +156,8 @@ importers: specifier: 3.0.5 version: 3.0.5 '@types/node': - specifier: ^22.5.5 - version: 22.5.5 + specifier: ^22.6.0 + version: 22.6.0 '@types/node-ipc': specifier: 9.2.3 version: 9.2.3 @@ -169,7 +169,7 @@ importers: version: 7.0.3 cz-conventional-changelog: specifier: ^3.3.0 - version: 3.3.0(@types/node@22.5.5)(typescript@5.6.2) + version: 3.3.0(@types/node@22.6.0)(typescript@5.6.2) debug: specifier: 4.3.7 version: 4.3.7 @@ -227,146 +227,146 @@ packages: resolution: {integrity: sha512-tpyc+7i6bPG9mvaBbtKUeghfyZSDgWquIDfMgqYtTbmZ9Y9VzEm2je9EYcQ0aoz5o7NvGS+rcDec93yO08MHYA==} engines: {node: '>=v18'} - '@esbuild/aix-ppc64@0.23.1': - resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} + '@esbuild/aix-ppc64@0.24.0': + resolution: {integrity: sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.23.1': - resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==} + '@esbuild/android-arm64@0.24.0': + resolution: {integrity: sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.23.1': - resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==} + '@esbuild/android-arm@0.24.0': + resolution: {integrity: sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.23.1': - resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==} + '@esbuild/android-x64@0.24.0': + resolution: {integrity: sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.23.1': - resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==} + '@esbuild/darwin-arm64@0.24.0': + resolution: {integrity: sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.23.1': - resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==} + '@esbuild/darwin-x64@0.24.0': + resolution: {integrity: sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.23.1': - resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==} + '@esbuild/freebsd-arm64@0.24.0': + resolution: {integrity: sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.23.1': - resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==} + '@esbuild/freebsd-x64@0.24.0': + resolution: {integrity: sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.23.1': - resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==} + '@esbuild/linux-arm64@0.24.0': + resolution: {integrity: sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.23.1': - resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==} + '@esbuild/linux-arm@0.24.0': + resolution: {integrity: sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.23.1': - resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==} + '@esbuild/linux-ia32@0.24.0': + resolution: {integrity: sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.23.1': - resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==} + '@esbuild/linux-loong64@0.24.0': + resolution: {integrity: sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.23.1': - resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==} + '@esbuild/linux-mips64el@0.24.0': + resolution: {integrity: sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.23.1': - resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==} + '@esbuild/linux-ppc64@0.24.0': + resolution: {integrity: sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.23.1': - resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==} + '@esbuild/linux-riscv64@0.24.0': + resolution: {integrity: sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.23.1': - resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==} + '@esbuild/linux-s390x@0.24.0': + resolution: {integrity: sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.23.1': - resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==} + '@esbuild/linux-x64@0.24.0': + resolution: {integrity: sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-x64@0.23.1': - resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==} + '@esbuild/netbsd-x64@0.24.0': + resolution: {integrity: sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.23.1': - resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} + '@esbuild/openbsd-arm64@0.24.0': + resolution: {integrity: sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.23.1': - resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==} + '@esbuild/openbsd-x64@0.24.0': + resolution: {integrity: sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/sunos-x64@0.23.1': - resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==} + '@esbuild/sunos-x64@0.24.0': + resolution: {integrity: sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.23.1': - resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==} + '@esbuild/win32-arm64@0.24.0': + resolution: {integrity: sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.23.1': - resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==} + '@esbuild/win32-ia32@0.24.0': + resolution: {integrity: sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.23.1': - resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==} + '@esbuild/win32-x64@0.24.0': + resolution: {integrity: sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -668,8 +668,8 @@ packages: '@types/node@12.20.55': resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} - '@types/node@22.5.5': - resolution: {integrity: sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA==} + '@types/node@22.6.0': + resolution: {integrity: sha512-QyR8d5bmq+eR72TwQDfujwShHMcIrWIYsaQFtXRE58MHPTEKUNxjxvl0yS0qPMds5xbSDWtp7ZpvGFtd7dfMdQ==} '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -1520,8 +1520,8 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} - esbuild@0.23.1: - resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==} + esbuild@0.24.0: + resolution: {integrity: sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==} engines: {node: '>=18'} hasBin: true @@ -2285,8 +2285,8 @@ packages: keyv-file@5.0.3: resolution: {integrity: sha512-VoX/Y+sMGM9O/d0PFNfuqQDt1cJaBDKgHDuauQeLfhd3S8LKZgqSsJRDoYBbMW8qvuv/1/TJhfAq4k/AXpWxYw==} - keyv@5.0.1: - resolution: {integrity: sha512-NH+3ditq1O5uTSQiiHrGOTkwUniRox/lZ8tHARdsu5Skyv0AhZca0OCycWfR1fTECvSRftMQnXqx7cBpxo8G1g==} + keyv@5.0.3: + resolution: {integrity: sha512-WmefGWaWkWiWDkIasfHxpWmM1lych/LPtRmNj8jnIQVGLsAgFw73Vg9utZ7ss97/JwRlERABb/fSejTPY4hlZQ==} labeled-stream-splicer@2.0.2: resolution: {integrity: sha512-Ca4LSXFFZUjPScRaqOcFxneA0VpKZr4MMYCljyQr4LIewTLb3Y0IUTIsnBBsVubIeEfxeSZpSjSsRM8APEQaAw==} @@ -3788,7 +3788,7 @@ snapshots: '@commitlint/execute-rule@19.0.0': optional: true - '@commitlint/load@19.4.0(@types/node@22.5.5)(typescript@5.6.2)': + '@commitlint/load@19.4.0(@types/node@22.6.0)(typescript@5.6.2)': dependencies: '@commitlint/config-validator': 19.0.3 '@commitlint/execute-rule': 19.0.0 @@ -3796,7 +3796,7 @@ snapshots: '@commitlint/types': 19.0.3 chalk: 5.3.0 cosmiconfig: 9.0.0(typescript@5.6.2) - cosmiconfig-typescript-loader: 5.0.0(@types/node@22.5.5)(cosmiconfig@9.0.0(typescript@5.6.2))(typescript@5.6.2) + cosmiconfig-typescript-loader: 5.0.0(@types/node@22.6.0)(cosmiconfig@9.0.0(typescript@5.6.2))(typescript@5.6.2) lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 lodash.uniq: 4.5.0 @@ -3821,76 +3821,76 @@ snapshots: chalk: 5.3.0 optional: true - '@esbuild/aix-ppc64@0.23.1': + '@esbuild/aix-ppc64@0.24.0': optional: true - '@esbuild/android-arm64@0.23.1': + '@esbuild/android-arm64@0.24.0': optional: true - '@esbuild/android-arm@0.23.1': + '@esbuild/android-arm@0.24.0': optional: true - '@esbuild/android-x64@0.23.1': + '@esbuild/android-x64@0.24.0': optional: true - '@esbuild/darwin-arm64@0.23.1': + '@esbuild/darwin-arm64@0.24.0': optional: true - '@esbuild/darwin-x64@0.23.1': + '@esbuild/darwin-x64@0.24.0': optional: true - '@esbuild/freebsd-arm64@0.23.1': + '@esbuild/freebsd-arm64@0.24.0': optional: true - '@esbuild/freebsd-x64@0.23.1': + '@esbuild/freebsd-x64@0.24.0': optional: true - '@esbuild/linux-arm64@0.23.1': + '@esbuild/linux-arm64@0.24.0': optional: true - '@esbuild/linux-arm@0.23.1': + '@esbuild/linux-arm@0.24.0': optional: true - '@esbuild/linux-ia32@0.23.1': + '@esbuild/linux-ia32@0.24.0': optional: true - '@esbuild/linux-loong64@0.23.1': + '@esbuild/linux-loong64@0.24.0': optional: true - '@esbuild/linux-mips64el@0.23.1': + '@esbuild/linux-mips64el@0.24.0': optional: true - '@esbuild/linux-ppc64@0.23.1': + '@esbuild/linux-ppc64@0.24.0': optional: true - '@esbuild/linux-riscv64@0.23.1': + '@esbuild/linux-riscv64@0.24.0': optional: true - '@esbuild/linux-s390x@0.23.1': + '@esbuild/linux-s390x@0.24.0': optional: true - '@esbuild/linux-x64@0.23.1': + '@esbuild/linux-x64@0.24.0': optional: true - '@esbuild/netbsd-x64@0.23.1': + '@esbuild/netbsd-x64@0.24.0': optional: true - '@esbuild/openbsd-arm64@0.23.1': + '@esbuild/openbsd-arm64@0.24.0': optional: true - '@esbuild/openbsd-x64@0.23.1': + '@esbuild/openbsd-x64@0.24.0': optional: true - '@esbuild/sunos-x64@0.23.1': + '@esbuild/sunos-x64@0.24.0': optional: true - '@esbuild/win32-arm64@0.23.1': + '@esbuild/win32-arm64@0.24.0': optional: true - '@esbuild/win32-ia32@0.23.1': + '@esbuild/win32-ia32@0.24.0': optional: true - '@esbuild/win32-x64@0.23.1': + '@esbuild/win32-x64@0.24.0': optional: true '@isaacs/cliui@8.0.2': @@ -4180,15 +4180,15 @@ snapshots: '@types/body-parser@1.19.5': dependencies: '@types/connect': 3.4.38 - '@types/node': 22.5.5 + '@types/node': 22.6.0 '@types/connect@3.4.38': dependencies: - '@types/node': 22.5.5 + '@types/node': 22.6.0 '@types/conventional-commits-parser@5.0.0': dependencies: - '@types/node': 22.5.5 + '@types/node': 22.6.0 optional: true '@types/debug@4.1.12': @@ -4199,7 +4199,7 @@ snapshots: '@types/express-serve-static-core@4.19.5': dependencies: - '@types/node': 22.5.5 + '@types/node': 22.6.0 '@types/qs': 6.9.15 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 @@ -4220,19 +4220,19 @@ snapshots: '@types/fs-extra@11.0.4': dependencies: '@types/jsonfile': 6.1.4 - '@types/node': 22.5.5 + '@types/node': 22.6.0 optional: true '@types/glob@7.2.0': dependencies: '@types/minimatch': 5.1.2 - '@types/node': 22.5.5 + '@types/node': 22.6.0 '@types/http-errors@2.0.4': {} '@types/jsonfile@6.1.4': dependencies: - '@types/node': 22.5.5 + '@types/node': 22.6.0 optional: true '@types/mime@1.3.5': {} @@ -4243,12 +4243,12 @@ snapshots: '@types/node-ipc@9.2.3': dependencies: - '@types/node': 22.5.5 + '@types/node': 22.6.0 '@types/node@12.20.55': optional: true - '@types/node@22.5.5': + '@types/node@22.6.0': dependencies: undici-types: 6.19.8 @@ -4265,22 +4265,22 @@ snapshots: '@types/send@0.17.4': dependencies: '@types/mime': 1.3.5 - '@types/node': 22.5.5 + '@types/node': 22.6.0 '@types/serve-static@1.15.7': dependencies: '@types/http-errors': 2.0.4 - '@types/node': 22.5.5 + '@types/node': 22.6.0 '@types/send': 0.17.4 '@types/shelljs@0.8.15': dependencies: '@types/glob': 7.2.0 - '@types/node': 22.5.5 + '@types/node': 22.6.0 '@types/ws@8.5.12': dependencies: - '@types/node': 22.5.5 + '@types/node': 22.6.0 '@vercel/nft@0.26.5': dependencies: @@ -4875,10 +4875,10 @@ snapshots: dependencies: delayed-stream: 1.0.0 - commitizen@4.3.0(@types/node@22.5.5)(typescript@5.6.2): + commitizen@4.3.0(@types/node@22.6.0)(typescript@5.6.2): dependencies: cachedir: 2.3.0 - cz-conventional-changelog: 3.3.0(@types/node@22.5.5)(typescript@5.6.2) + cz-conventional-changelog: 3.3.0(@types/node@22.6.0)(typescript@5.6.2) dedent: 0.7.0 detect-indent: 6.1.0 find-node-modules: 2.1.3 @@ -4979,9 +4979,9 @@ snapshots: core-util-is@1.0.3: {} - cosmiconfig-typescript-loader@5.0.0(@types/node@22.5.5)(cosmiconfig@9.0.0(typescript@5.6.2))(typescript@5.6.2): + cosmiconfig-typescript-loader@5.0.0(@types/node@22.6.0)(cosmiconfig@9.0.0(typescript@5.6.2))(typescript@5.6.2): dependencies: - '@types/node': 22.5.5 + '@types/node': 22.6.0 cosmiconfig: 9.0.0(typescript@5.6.2) jiti: 1.21.6 typescript: 5.6.2 @@ -5052,16 +5052,16 @@ snapshots: dependencies: array-find-index: 1.0.2 - cz-conventional-changelog@3.3.0(@types/node@22.5.5)(typescript@5.6.2): + cz-conventional-changelog@3.3.0(@types/node@22.6.0)(typescript@5.6.2): dependencies: chalk: 2.4.2 - commitizen: 4.3.0(@types/node@22.5.5)(typescript@5.6.2) + commitizen: 4.3.0(@types/node@22.6.0)(typescript@5.6.2) conventional-commit-types: 3.0.0 lodash.map: 4.6.0 longest: 2.0.1 word-wrap: 1.2.5 optionalDependencies: - '@commitlint/load': 19.4.0(@types/node@22.5.5)(typescript@5.6.2) + '@commitlint/load': 19.4.0(@types/node@22.6.0)(typescript@5.6.2) transitivePeerDependencies: - '@types/node' - typescript @@ -5247,32 +5247,32 @@ snapshots: es-errors@1.3.0: {} - esbuild@0.23.1: + esbuild@0.24.0: optionalDependencies: - '@esbuild/aix-ppc64': 0.23.1 - '@esbuild/android-arm': 0.23.1 - '@esbuild/android-arm64': 0.23.1 - '@esbuild/android-x64': 0.23.1 - '@esbuild/darwin-arm64': 0.23.1 - '@esbuild/darwin-x64': 0.23.1 - '@esbuild/freebsd-arm64': 0.23.1 - '@esbuild/freebsd-x64': 0.23.1 - '@esbuild/linux-arm': 0.23.1 - '@esbuild/linux-arm64': 0.23.1 - '@esbuild/linux-ia32': 0.23.1 - '@esbuild/linux-loong64': 0.23.1 - '@esbuild/linux-mips64el': 0.23.1 - '@esbuild/linux-ppc64': 0.23.1 - '@esbuild/linux-riscv64': 0.23.1 - '@esbuild/linux-s390x': 0.23.1 - '@esbuild/linux-x64': 0.23.1 - '@esbuild/netbsd-x64': 0.23.1 - '@esbuild/openbsd-arm64': 0.23.1 - '@esbuild/openbsd-x64': 0.23.1 - '@esbuild/sunos-x64': 0.23.1 - '@esbuild/win32-arm64': 0.23.1 - '@esbuild/win32-ia32': 0.23.1 - '@esbuild/win32-x64': 0.23.1 + '@esbuild/aix-ppc64': 0.24.0 + '@esbuild/android-arm': 0.24.0 + '@esbuild/android-arm64': 0.24.0 + '@esbuild/android-x64': 0.24.0 + '@esbuild/darwin-arm64': 0.24.0 + '@esbuild/darwin-x64': 0.24.0 + '@esbuild/freebsd-arm64': 0.24.0 + '@esbuild/freebsd-x64': 0.24.0 + '@esbuild/linux-arm': 0.24.0 + '@esbuild/linux-arm64': 0.24.0 + '@esbuild/linux-ia32': 0.24.0 + '@esbuild/linux-loong64': 0.24.0 + '@esbuild/linux-mips64el': 0.24.0 + '@esbuild/linux-ppc64': 0.24.0 + '@esbuild/linux-riscv64': 0.24.0 + '@esbuild/linux-s390x': 0.24.0 + '@esbuild/linux-x64': 0.24.0 + '@esbuild/netbsd-x64': 0.24.0 + '@esbuild/openbsd-arm64': 0.24.0 + '@esbuild/openbsd-x64': 0.24.0 + '@esbuild/sunos-x64': 0.24.0 + '@esbuild/win32-arm64': 0.24.0 + '@esbuild/win32-ia32': 0.24.0 + '@esbuild/win32-x64': 0.24.0 escalade@3.2.0: {} @@ -6112,7 +6112,7 @@ snapshots: fs-extra: 4.0.3 tslib: 1.14.1 - keyv@5.0.1: + keyv@5.0.3: dependencies: '@keyv/serialize': 1.0.1 @@ -7312,7 +7312,7 @@ snapshots: tsx@4.19.1: dependencies: - esbuild: 0.23.1 + esbuild: 0.24.0 get-tsconfig: 4.8.0 optionalDependencies: fsevents: 2.3.3 @@ -7423,13 +7423,13 @@ snapshots: core-util-is: 1.0.2 extsprintf: 1.3.0 - vite@5.4.7(@types/node@22.5.5): + vite@5.4.7(@types/node@22.6.0): dependencies: - esbuild: 0.23.1 + esbuild: 0.24.0 postcss: 8.4.45 rollup: 4.21.2 optionalDependencies: - '@types/node': 22.5.5 + '@types/node': 22.6.0 fsevents: 2.3.3 vm-browserify@0.0.4: @@ -7523,4 +7523,4 @@ snapshots: zx@8.1.8: optionalDependencies: '@types/fs-extra': 11.0.4 - '@types/node': 22.5.5 + '@types/node': 22.6.0 diff --git a/src/api/kit.ts b/src/api/kit.ts index 92a8c3bb..143cdcd3 100644 --- a/src/api/kit.ts +++ b/src/api/kit.ts @@ -1,53 +1,56 @@ import path from "node:path" import { existsSync, lstatSync } from "node:fs" -import { readFile, readJson } from "@johnlindquist/kit-internal/fs-extra" +import { + readFile, + readJson, +} from "@johnlindquist/kit-internal/fs-extra" import * as os from "node:os" import { pathToFileURL } from "node:url" import * as JSONSafe from "safe-stable-stringify" import { - QuickScore, - quickScore, - createConfig, - type Options, - type ConfigOptions + QuickScore, + quickScore, + createConfig, + type Options, + type ConfigOptions, } from "quick-score" import { formatDistanceToNow } from "@johnlindquist/kit-internal/date-fns" import type { - Action, - Choice, - FlagsObject, - FlagsWithKeys, - PromptConfig, - ScoredChoice, - Script, - Scriptlet, - Shortcut + Action, + Choice, + FlagsObject, + FlagsWithKeys, + PromptConfig, + ScoredChoice, + Script, + Scriptlet, + Shortcut, } from "../types/core" import { Channel, PROMPT } from "../core/enum.js" import { - kitPath, - kenvPath, - resolveScriptToCommand, - run, - home, - isFile, - getKenvs, - groupChoices, - formatChoices, - parseScript, - processInBatches, - highlight, - md as mdUtil, - tagger + kitPath, + kenvPath, + resolveScriptToCommand, + run, + home, + isFile, + getKenvs, + groupChoices, + formatChoices, + parseScript, + processInBatches, + highlight, + md as mdUtil, + tagger, } from "../core/utils.js" import { - getScripts, - getScriptFromString, - getUserJson, - getTimestamps, - type Stamp, - setUserJson + getScripts, + getScriptFromString, + getUserJson, + getTimestamps, + type Stamp, + setUserJson, } from "../core/db.js" import { stripAnsi } from "@johnlindquist/kit-internal/strip-ansi" @@ -56,80 +59,88 @@ import type { Kenv } from "../types/kit" import type { Fields as TraceFields } from "chrome-trace-event" import dotenv from "dotenv" import type { kenvEnv } from "../types/env" +import { getRecentLimit } from "./recent.js" -global.__kitActionsMap = new Map() +global.__kitActionsMap = new Map< + string, + Action | Shortcut +>() export async function initTrace() { - if ( - process.env.KIT_TRACE || - (process.env.KIT_TRACE_DATA && !global?.trace?.enabled) - ) { - let timestamp = Date.now() - let { default: Trace } = await import("chrome-trace-event") - let tracer = new Trace.Tracer({ - noStream: true - }) - - await ensureDir(kitPath("trace")) - - let writeStream = createWriteStream( - kitPath("trace", `trace-${timestamp}.json`) - ) - - tracer.pipe(writeStream) - - const tidCache = new Map() - - function updateFields(channel) { - let tid - if (channel) { - let cachedTid = tidCache.get(channel) - if (cachedTid === undefined) { - cachedTid = Object.entries(Channel).findIndex( - ([, value]) => value === channel - ) - tidCache.set(channel, cachedTid) - } - tid = cachedTid - } - return tid - } - - function createTraceFunction(eventType: "B" | "E" | "I") { - return function (fields: TraceFields) { - fields.tid = updateFields(fields?.channel) || 1 - if (!process.env.KIT_TRACE_DATA) { - fields.args = undefined - } - return tracer.mkEventFunc(eventType)(fields) - } - } - - global.trace = { - begin: createTraceFunction("B"), - end: createTraceFunction("E"), - instant: createTraceFunction("I"), - flush: () => { - tracer.flush() - }, - enabled: true - } - - global.trace.instant({ - name: "Init Trace", - args: { - timestamp - } - }) - } + if ( + process.env.KIT_TRACE || + (process.env.KIT_TRACE_DATA && !global?.trace?.enabled) + ) { + let timestamp = Date.now() + let { default: Trace } = await import( + "chrome-trace-event" + ) + let tracer = new Trace.Tracer({ + noStream: true, + }) + + await ensureDir(kitPath("trace")) + + let writeStream = createWriteStream( + kitPath("trace", `trace-${timestamp}.json`) + ) + + tracer.pipe(writeStream) + + const tidCache = new Map() + + function updateFields(channel) { + let tid + if (channel) { + let cachedTid = tidCache.get(channel) + if (cachedTid === undefined) { + cachedTid = Object.entries(Channel).findIndex( + ([, value]) => value === channel + ) + tidCache.set(channel, cachedTid) + } + tid = cachedTid + } + return tid + } + + function createTraceFunction( + eventType: "B" | "E" | "I" + ) { + return function (fields: TraceFields) { + fields.tid = updateFields(fields?.channel) || 1 + if (!process.env.KIT_TRACE_DATA) { + fields.args = undefined + } + return tracer.mkEventFunc(eventType)(fields) + } + } + + global.trace = { + begin: createTraceFunction("B"), + end: createTraceFunction("E"), + instant: createTraceFunction("I"), + flush: () => { + tracer.flush() + }, + enabled: true, + } + + global.trace.instant({ + name: "Init Trace", + args: { + timestamp, + }, + }) + } } global.trace ||= { - begin: () => {}, - end: () => {}, - instant: () => {}, - flush: () => {}, - enabled: false + begin: () => {}, + end: () => {}, + instant: () => {}, + flush: () => {}, + enabled: false, } global.isWin = os.platform().startsWith("win") @@ -139,105 +150,142 @@ global.cmd = global.isMac ? "cmd" : "ctrl" let isErrored = false export let errorPrompt = async (error: Error) => { - if (isErrored) { - return - } - isErrored = true - if (global.__kitAbandoned) { - let { name } = path.parse(global.kitScript) - let errorLog = path.resolve( - path.dirname(path.dirname(global.kitScript)), - "logs", - `${name}.log` - ) - - await appendFile(errorLog, `\nAbandonned. Exiting...`) - exit() - } - if (process.env.KIT_CONTEXT === "app") { - global.warn(`☠️ ERROR PROMPT SHOULD SHOW ☠️`) - let stackWithoutId = - error?.stack?.replace(/\?[^:]*/g, "") || "No Error Stack" - global.warn(stackWithoutId) - - let errorFile = global.kitScript - let line = "1" - let col = "1" - - let secondLine = stackWithoutId.split("\n")?.[1] || "" - - // TODO: This is broken on Windows... - if (secondLine?.match("at file://")) { - if (isWin) { - errorFile = path.normalize( - secondLine.replace("at file:///", "").replace(/:\d+/g, "").trim() - ) - ;[, , line, col] = secondLine.replace("at file:///", "").split(":") - } else { - errorFile = secondLine - .replace("at file://", "") - .replace(/:.*/, "") - .trim() - ;[, line, col] = secondLine.replace("at file://", "").split(":") - } - } - - // END TODO - - let script = global.kitScript.replace(/.*\//, "") - let errorToCopy = `${error.message}\n${error.stack}` - let dashedDate = () => - new Date() - .toISOString() - .replace("T", "-") - .replace(/:/g, "-") - .split(".")[0] - let errorJsonPath = global.tmp(`error-${dashedDate()}.txt`) - await global.writeFile(errorJsonPath, errorToCopy) - - try { - if (global?.args.length > 0) { - log({ args }) - args = [] - } - global.warn(`Running error action because of`, {script, error}) - await run( - kitPath("cli", "error-action.js"), - script, - errorJsonPath, - errorFile, - line, - col - ) - } catch (error) { - global.warn(error) - } - } else { - global.console.log(error) - } -} - -export let outputTmpFile = async (fileName: string, contents: string) => { - let outputPath = path.resolve(os.tmpdir(), "kit", fileName) - await outputFile(outputPath, contents) - return outputPath -} - -export let copyTmpFile = async (fromFile: string, fileName: string) => - await outputTmpFile(fileName, await global.readFile(fromFile, "utf-8")) - -export let buildWidget = async (scriptPath, outPath = "") => { - let outfile = outPath || scriptPath - - let templateContent = await readFile( - kenvPath("templates", "widget.html"), - "utf8" - ) - - let REACT_PATH = kitPath("node_modules", "react", "index.js") - let REACT_DOM_PATH = kitPath("node_modules", "react-dom", "index.js") - - let REACT_CONTENT = ` + if (isErrored) { + return + } + isErrored = true + if (global.__kitAbandoned) { + let { name } = path.parse(global.kitScript) + let errorLog = path.resolve( + path.dirname(path.dirname(global.kitScript)), + "logs", + `${name}.log` + ) + + await appendFile(errorLog, `\nAbandonned. Exiting...`) + exit() + } + if (process.env.KIT_CONTEXT === "app") { + global.warn(`☠️ ERROR PROMPT SHOULD SHOW ☠️`) + let stackWithoutId = + error?.stack?.replace(/\?[^:]*/g, "") || + "No Error Stack" + global.warn(stackWithoutId) + + let errorFile = global.kitScript + let line = "1" + let col = "1" + + let secondLine = stackWithoutId.split("\n")?.[1] || "" + + // TODO: This is broken on Windows... + if (secondLine?.match("at file://")) { + if (isWin) { + errorFile = path.normalize( + secondLine + .replace("at file:///", "") + .replace(/:\d+/g, "") + .trim() + ) + ;[, , line, col] = secondLine + .replace("at file:///", "") + .split(":") + } else { + errorFile = secondLine + .replace("at file://", "") + .replace(/:.*/, "") + .trim() + ;[, line, col] = secondLine + .replace("at file://", "") + .split(":") + } + } + + // END TODO + + let script = global.kitScript.replace(/.*\//, "") + let errorToCopy = `${error.message}\n${error.stack}` + let dashedDate = () => + new Date() + .toISOString() + .replace("T", "-") + .replace(/:/g, "-") + .split(".")[0] + let errorJsonPath = global.tmp( + `error-${dashedDate()}.txt` + ) + await global.writeFile(errorJsonPath, errorToCopy) + + try { + if (global?.args.length > 0) { + log({ args }) + args = [] + } + global.warn(`Running error action because of`, { + script, + error, + }) + await run( + kitPath("cli", "error-action.js"), + script, + errorJsonPath, + errorFile, + line, + col + ) + } catch (error) { + global.warn(error) + } + } else { + global.console.log(error) + } +} + +export let outputTmpFile = async ( + fileName: string, + contents: string +) => { + let outputPath = path.resolve( + os.tmpdir(), + "kit", + fileName + ) + await outputFile(outputPath, contents) + return outputPath +} + +export let copyTmpFile = async ( + fromFile: string, + fileName: string +) => + await outputTmpFile( + fileName, + await global.readFile(fromFile, "utf-8") + ) + +export let buildWidget = async ( + scriptPath, + outPath = "" +) => { + let outfile = outPath || scriptPath + + let templateContent = await readFile( + kenvPath("templates", "widget.html"), + "utf8" + ) + + let REACT_PATH = kitPath( + "node_modules", + "react", + "index.js" + ) + let REACT_DOM_PATH = kitPath( + "node_modules", + "react-dom", + "index.js" + ) + + let REACT_CONTENT = ` let { default: React } = await import( kitPath("node_modules", "react", "umd", "react.development.js") ) @@ -248,230 +296,263 @@ export let buildWidget = async (scriptPath, outPath = "") => { let __renderToString = (x, y, z)=> Server.renderToString(React.createElement(x, y, z)) ` - let templateCompiler = compile(templateContent) - let result = templateCompiler({ - REACT_PATH, - REACT_DOM_PATH, - REACT_CONTENT - }) + let templateCompiler = compile(templateContent) + let result = templateCompiler({ + REACT_PATH, + REACT_DOM_PATH, + REACT_CONTENT, + }) - let contents = await readFile(outfile, "utf8") + let contents = await readFile(outfile, "utf8") - await writeFile(outfile, result) + await writeFile(outfile, result) } let getMissingPackages = (e: string): string[] => { - let missingPackage = [] - if (e.includes("Cannot find package")) { - missingPackage = e.match(/(?<=Cannot find package ['"]).*(?=['"])/g) - } else if (e.includes("Could not resolve")) { - missingPackage = e.match(/(?<=Could not resolve ['"]).*(?=['"])/g) - } else if (e.includes("Cannot find module")) { - missingPackage = e.match(/(?<=Cannot find module ['"]).*(?=['"])/g) - } - - return (missingPackage || []).map((s) => s.trim()).filter(Boolean) + let missingPackage = [] + if (e.includes("Cannot find package")) { + missingPackage = e.match( + /(?<=Cannot find package ['"]).*(?=['"])/g + ) + } else if (e.includes("Could not resolve")) { + missingPackage = e.match( + /(?<=Could not resolve ['"]).*(?=['"])/g + ) + } else if (e.includes("Cannot find module")) { + missingPackage = e.match( + /(?<=Cannot find module ['"]).*(?=['"])/g + ) + } + + return (missingPackage || []) + .map(s => s.trim()) + .filter(Boolean) } global.attemptImport = async (scriptPath, ..._args) => { - let cachedArgs = args.slice(0) - let importResult = undefined - try { - global.updateArgs(_args) + let cachedArgs = args.slice(0) + let importResult = undefined + try { + global.updateArgs(_args) - let href = pathToFileURL(scriptPath).href - let kitImport = `${href}?now=${Date.now()}.kit` - importResult = await import(kitImport) - } catch (error) { - let e = error.toString() - global.warn(e) - if (process.env.KIT_CONTEXT === "app") { - await errorPrompt(error) - } - } + let href = pathToFileURL(scriptPath).href + let kitImport = `${href}?now=${Date.now()}.kit` + importResult = await import(kitImport) + } catch (error) { + let e = error.toString() + global.warn(e) + if (process.env.KIT_CONTEXT === "app") { + await errorPrompt(error) + } + } - return importResult + return importResult } global.__kitAbandoned = false global.send = (channel: Channel, value?: any) => { - if (global.__kitAbandoned) return null - if (process?.send) { - try { - let payload = { - pid: process.pid, - promptId: global.__kitPromptId, - kitScript: global.kitScript, - channel, - value - } - - global.trace.instant({ - name: `Send ${channel}`, - channel, - args: payload - }) - - process.send(payload) - } catch (e) { - global.warn(e) - } - } else { - // console.log(from, ...args) - } + if (global.__kitAbandoned) return null + if (process?.send) { + try { + let payload = { + pid: process.pid, + promptId: global.__kitPromptId, + kitScript: global.kitScript, + channel, + value, + } + + global.trace.instant({ + name: `Send ${channel}`, + channel, + args: payload, + }) + + process.send(payload) + } catch (e) { + global.warn(e) + } + } else { + // console.log(from, ...args) + } } let _consoleLog = global.console.log.bind(global.console) let _consoleWarn = global.console.warn.bind(global.console) -let _consoleClear = global.console.clear.bind(global.console) +let _consoleClear = global.console.clear.bind( + global.console +) global.log = (...args) => { - if (process?.send) { - global.send( - Channel.KIT_LOG, - args - .map((a) => (typeof a != "string" ? JSONSafe.stringify(a) : a)) - .join(" ") - ) - } else { - _consoleLog(...args) - } + if (process?.send) { + global.send( + Channel.KIT_LOG, + args + .map(a => + typeof a != "string" ? JSONSafe.stringify(a) : a + ) + .join(" ") + ) + } else { + _consoleLog(...args) + } } global.warn = (...args) => { - if (process?.send) { - global.send( - Channel.KIT_WARN, - args - .map((a) => (typeof a != "string" ? JSONSafe.stringify(a) : a)) - .join(" ") - ) - } else { - _consoleWarn(...args) - } + if (process?.send) { + global.send( + Channel.KIT_WARN, + args + .map(a => + typeof a != "string" ? JSONSafe.stringify(a) : a + ) + .join(" ") + ) + } else { + _consoleWarn(...args) + } } global.clear = () => { - if (process?.send) { - global.send(Channel.KIT_CLEAR) - } else { - _consoleClear() - } + if (process?.send) { + global.send(Channel.KIT_CLEAR) + } else { + _consoleClear() + } } if (process?.send) { - global.console.log = (...args) => { - let log = args - .map((a) => (typeof a != "string" ? JSONSafe.stringify(a) : a)) - .join(" ") - - global.send(Channel.CONSOLE_LOG, log) - } - - global.console.warn = (...args) => { - let warn = args - .map((a) => (typeof a != "string" ? JSONSafe.stringify(a) : a)) - .join(" ") - - global.send(Channel.CONSOLE_WARN, warn) - } - - global.console.clear = () => { - global.send(Channel.CONSOLE_CLEAR) - } - - global.console.error = (...args) => { - let error = args - .map((a) => (typeof a != "string" ? JSONSafe.stringify(a) : a)) - .join(" ") - - global.send(Channel.CONSOLE_ERROR, error) - } - - global.console.info = (...args) => { - let info = args - .map((a) => (typeof a != "string" ? JSONSafe.stringify(a) : a)) - .join(" ") - - global.send(Channel.CONSOLE_INFO, info) - } -} - -global.dev = async (data) => { - await global.sendWait(Channel.DEV_TOOLS, data) + global.console.log = (...args) => { + let log = args + .map(a => + typeof a != "string" ? JSONSafe.stringify(a) : a + ) + .join(" ") + + global.send(Channel.CONSOLE_LOG, log) + } + + global.console.warn = (...args) => { + let warn = args + .map(a => + typeof a != "string" ? JSONSafe.stringify(a) : a + ) + .join(" ") + + global.send(Channel.CONSOLE_WARN, warn) + } + + global.console.clear = () => { + global.send(Channel.CONSOLE_CLEAR) + } + + global.console.error = (...args) => { + let error = args + .map(a => + typeof a != "string" ? JSONSafe.stringify(a) : a + ) + .join(" ") + + global.send(Channel.CONSOLE_ERROR, error) + } + + global.console.info = (...args) => { + let info = args + .map(a => + typeof a != "string" ? JSONSafe.stringify(a) : a + ) + .join(" ") + + global.send(Channel.CONSOLE_INFO, info) + } +} + +global.dev = async data => { + await global.sendWait(Channel.DEV_TOOLS, data) } global.devTools = global.dev global.showImage = async (html, options) => { - await global.widget( - md(`## \`showImage\` is Deprecated + await global.widget( + md(`## \`showImage\` is Deprecated Please use the new \`widget\` function instead. [https://github.com/johnlindquist/kit/discussions/745](https://github.com/johnlindquist/kit/discussions/745) `) - ) - // global.send(Channel.SHOW, { options, html }) + ) + // global.send(Channel.SHOW, { options, html }) } -global.setPlaceholder = (text) => { - global.send(Channel.SET_PLACEHOLDER, stripAnsi(text)) +global.setPlaceholder = text => { + global.send(Channel.SET_PLACEHOLDER, stripAnsi(text)) } -global.setEnter = async (text) => { - global.send(Channel.SET_ENTER, text) +global.setEnter = async text => { + global.send(Channel.SET_ENTER, text) } global.main = async (scriptPath: string, ..._args) => { - let kitScriptPath = kitPath("main", scriptPath) + ".js" - return await global.attemptImport(kitScriptPath, ..._args) + let kitScriptPath = kitPath("main", scriptPath) + ".js" + return await global.attemptImport(kitScriptPath, ..._args) } global.lib = async (lib: string, ..._args) => { - let libScriptPath = - path.resolve(global.kitScript, "..", "..", "lib", lib) + ".js" - return await global.attemptImport(libScriptPath, ..._args) + let libScriptPath = + path.resolve(global.kitScript, "..", "..", "lib", lib) + + ".js" + return await global.attemptImport(libScriptPath, ..._args) } global.cli = async (cliPath, ..._args) => { - let cliScriptPath = kitPath("cli", cliPath) + ".js" + let cliScriptPath = kitPath("cli", cliPath) + ".js" - return await global.attemptImport(cliScriptPath, ..._args) + return await global.attemptImport(cliScriptPath, ..._args) } global.setup = async (setupPath, ..._args) => { - global.setPlaceholder(`>_ setup: ${setupPath}...`) - let setupScriptPath = kitPath("setup", setupPath) + ".js" - return await global.attemptImport(setupScriptPath, ..._args) + global.setPlaceholder(`>_ setup: ${setupPath}...`) + let setupScriptPath = kitPath("setup", setupPath) + ".js" + return await global.attemptImport( + setupScriptPath, + ..._args + ) } global.kenvTmpPath = (...parts) => { - let command = resolveScriptToCommand(global.kitScript) - let scriptTmpDir = kenvPath("tmp", command, ...parts) + let command = resolveScriptToCommand(global.kitScript) + let scriptTmpDir = kenvPath("tmp", command, ...parts) - mkdir("-p", path.dirname(scriptTmpDir)) - return scriptTmpDir + mkdir("-p", path.dirname(scriptTmpDir)) + return scriptTmpDir } export let tmpPath = (...parts: string[]) => { - let command = global?.kitScript - ? resolveScriptToCommand(global.kitScript) - : "" - - let tmpCommandDir = path.resolve(os.tmpdir(), "kit", command) + let command = global?.kitScript + ? resolveScriptToCommand(global.kitScript) + : "" + + let tmpCommandDir = path.resolve( + os.tmpdir(), + "kit", + command + ) - let scriptTmpDir = path.resolve(tmpCommandDir, ...parts) + let scriptTmpDir = path.resolve(tmpCommandDir, ...parts) - let kenvTmpCommandPath = kenvPath("tmp", command) + let kenvTmpCommandPath = kenvPath("tmp", command) - global.ensureDirSync(tmpCommandDir) - // symlink to kenvPath("command") - // Check if tmpCommandDir exists and is not a symlink before creating the symlink - if ( - !existsSync(kenvTmpCommandPath) || - lstatSync(kenvTmpCommandPath).isSymbolicLink() - ) { - global.ensureSymlinkSync(tmpCommandDir, kenvTmpCommandPath) - } + global.ensureDirSync(tmpCommandDir) + // symlink to kenvPath("command") + // Check if tmpCommandDir exists and is not a symlink before creating the symlink + if ( + !existsSync(kenvTmpCommandPath) || + lstatSync(kenvTmpCommandPath).isSymbolicLink() + ) { + global.ensureSymlinkSync( + tmpCommandDir, + kenvTmpCommandPath + ) + } - return scriptTmpDir + return scriptTmpDir } global.tmpPath = tmpPath @@ -480,58 +561,62 @@ global.tmpPath = tmpPath */ global.tmp = global.tmpPath global.inspect = async (data, fileName) => { - let dashedDate = () => - new Date().toISOString().replace("T", "-").replace(/:/g, "-").split(".")[0] + let dashedDate = () => + new Date() + .toISOString() + .replace("T", "-") + .replace(/:/g, "-") + .split(".")[0] - let formattedData = data - let tmpFullPath = "" + let formattedData = data + let tmpFullPath = "" - if (typeof data !== "string") { - formattedData = JSONSafe.stringify(data, null, "\t") - } + if (typeof data !== "string") { + formattedData = JSONSafe.stringify(data, null, "\t") + } - if (fileName) { - tmpFullPath = tmpPath(fileName) - } else if (typeof data === "object") { - tmpFullPath = tmpPath(`${dashedDate()}.json`) - } else { - tmpFullPath = tmpPath(`${dashedDate()}.txt`) - } + if (fileName) { + tmpFullPath = tmpPath(fileName) + } else if (typeof data === "object") { + tmpFullPath = tmpPath(`${dashedDate()}.json`) + } else { + tmpFullPath = tmpPath(`${dashedDate()}.txt`) + } - await global.writeFile(tmpFullPath, formattedData) + await global.writeFile(tmpFullPath, formattedData) - await global.edit(tmpFullPath) + await global.edit(tmpFullPath) } global.compileTemplate = async (template, vars) => { - let templateContent = await global.readFile( - kenvPath("templates", template), - "utf8" - ) - let templateCompiler = global.compile(templateContent) - return templateCompiler(vars) + let templateContent = await global.readFile( + kenvPath("templates", template), + "utf8" + ) + let templateCompiler = global.compile(templateContent) + return templateCompiler(vars) } global.currentOnTab = null global.onTabs = [] global.onTabIndex = 0 global.onTab = (name, tabFunction) => { - let fn = async (...args) => { - await tabFunction(...args) - } - global.onTabs.push({ name, fn }) - if (global.flag?.tab) { - if (global.flag?.tab === name) { - let tabIndex = global.onTabs.length - 1 - global.onTabIndex = tabIndex - global.send(Channel.SET_TAB_INDEX, tabIndex) - global.currentOnTab = fn() - } - } else if (global.onTabs.length === 1) { - global.onTabIndex = 0 - global.send(Channel.SET_TAB_INDEX, 0) - global.currentOnTab = fn() - } + let fn = async (...args) => { + await tabFunction(...args) + } + global.onTabs.push({ name, fn }) + if (global.flag?.tab) { + if (global.flag?.tab === name) { + let tabIndex = global.onTabs.length - 1 + global.onTabIndex = tabIndex + global.send(Channel.SET_TAB_INDEX, tabIndex) + global.currentOnTab = fn() + } + } else if (global.onTabs.length === 1) { + global.onTabIndex = 0 + global.send(Channel.SET_TAB_INDEX, 0) + global.currentOnTab = fn() + } } global.kitPrevChoices = [] @@ -540,324 +625,368 @@ global.groupChoices = groupChoices global.formatChoices = formatChoices global.addChoice = async (choice: string | Choice) => { - if (typeof choice !== "object") { - choice = { - name: String(choice), - value: String(choice) - } - } + if (typeof choice !== "object") { + choice = { + name: String(choice), + value: String(choice), + } + } - choice.id ||= global.uuid() - return await global.sendWait(Channel.ADD_CHOICE, choice) + choice.id ||= global.uuid() + return await global.sendWait(Channel.ADD_CHOICE, choice) } -global.appendChoices = async (choices: string[] | Choice[]) => { - return await global.sendWait(Channel.APPEND_CHOICES, choices) +global.appendChoices = async ( + choices: string[] | Choice[] +) => { + return await global.sendWait( + Channel.APPEND_CHOICES, + choices + ) } // TODO: Add an option to avoid sorting global.createChoiceSearch = async ( - choices: Choice[], - config: Partial< - Omit & ConfigOptions & { keys: string[] } - > = { - minimumScore: 0.3, - maxIterations: 3, - keys: ["name"] - } + choices: Choice[], + config: Partial< + Omit & + ConfigOptions & { keys: string[] } + > = { + minimumScore: 0.3, + maxIterations: 3, + keys: ["name"], + } ) => { - if (!config?.minimumScore) config.minimumScore = 0.3 - if (!config?.maxIterations) config.maxIterations = 3 - if (config?.keys && Array.isArray(config.keys)) { - config.keys = config.keys.map((key) => { - if (key === "name") return "slicedName" - if (key === "description") return "slicedDescription" - return key - }) - } - - let formattedChoices = await global.___kitFormatChoices(choices) - function scorer(string: string, query: string, matches: number[][]) { - return quickScore( - string, - query, - matches as any, - undefined, - undefined, - createConfig(config) - ) - } - - const keys = (config?.keys || ["slicedName"]).map((name) => ({ - name, - scorer - })) - - let qs = new QuickScore(formattedChoices, { - keys, - ...config - }) - - return (query: string) => { - let result = qs.search(query) as ScoredChoice[] - if (result.find((c) => c?.item?.group)) { - let createScoredChoice = (item: Choice): ScoredChoice => { - return { - item, - score: 0, - matches: {}, - _: "" - } - } - const groups: Set = new Set() - const keepGroups: Set = new Set() - const filteredBySearch: ScoredChoice[] = [] - - // Build a map for constant time access - const resultMap = new Map(result.map((r) => [r.item.id, r])) - - for (const choice of formattedChoices) { - if (choice?.skip) { - const scoredSkip = createScoredChoice(choice) - filteredBySearch.push(scoredSkip) - if (choice?.group) groups.add(choice.group) - } else { - const scored = resultMap.get(choice?.id) - if (scored) { - filteredBySearch.push(scored) - if (choice?.group && groups.has(choice.group)) { - keepGroups.add(choice.group) - } - } - } - } - - result = filteredBySearch.filter((sc) => { - if (sc?.item?.skip) { - if (!keepGroups.has(sc?.item?.group)) return false - } - - return true - }) - } - - return result - } -} - -global.setScoredChoices = async (choices: ScoredChoice[]) => { - return await global.sendWait(Channel.SET_SCORED_CHOICES, choices) -} - -global.___kitFormatChoices = async (choices, className = "") => { - if (!Array.isArray(choices)) return choices - let formattedChoices = formatChoices(choices, className) - let { __currentPromptConfig } = global as any - let { shortcuts: globalShortcuts } = __currentPromptConfig || {} - - if (globalShortcuts && choices?.[0]) { - let shortcuts = globalShortcuts.filter((shortcut) => { - if (shortcut?.condition) { - return shortcut.condition(choices?.[0]) - } - return true - }) - - global.send(Channel.SET_SHORTCUTS, shortcuts) - } - global.kitPrevChoices = formattedChoices - - global.setLoading(false) - return formattedChoices + if (!config?.minimumScore) config.minimumScore = 0.3 + if (!config?.maxIterations) config.maxIterations = 3 + if (config?.keys && Array.isArray(config.keys)) { + config.keys = config.keys.map(key => { + if (key === "name") return "slicedName" + if (key === "description") return "slicedDescription" + return key + }) + } + + let formattedChoices = await global.___kitFormatChoices( + choices + ) + function scorer( + string: string, + query: string, + matches: number[][] + ) { + return quickScore( + string, + query, + matches as any, + undefined, + undefined, + createConfig(config) + ) + } + + const keys = (config?.keys || ["slicedName"]).map( + name => ({ + name, + scorer, + }) + ) + + let qs = new QuickScore(formattedChoices, { + keys, + ...config, + }) + + return (query: string) => { + let result = qs.search(query) as ScoredChoice[] + if (result.find(c => c?.item?.group)) { + let createScoredChoice = ( + item: Choice + ): ScoredChoice => { + return { + item, + score: 0, + matches: {}, + _: "", + } + } + const groups: Set = new Set() + const keepGroups: Set = new Set() + const filteredBySearch: ScoredChoice[] = [] + + // Build a map for constant time access + const resultMap = new Map( + result.map(r => [r.item.id, r]) + ) + + for (const choice of formattedChoices) { + if (choice?.skip) { + const scoredSkip = createScoredChoice(choice) + filteredBySearch.push(scoredSkip) + if (choice?.group) groups.add(choice.group) + } else { + const scored = resultMap.get(choice?.id) + if (scored) { + filteredBySearch.push(scored) + if (choice?.group && groups.has(choice.group)) { + keepGroups.add(choice.group) + } + } + } + } + + result = filteredBySearch.filter(sc => { + if (sc?.item?.skip) { + if (!keepGroups.has(sc?.item?.group)) return false + } + + return true + }) + } + + return result + } +} + +global.setScoredChoices = async ( + choices: ScoredChoice[] +) => { + return await global.sendWait( + Channel.SET_SCORED_CHOICES, + choices + ) +} + +global.___kitFormatChoices = async ( + choices, + className = "" +) => { + if (!Array.isArray(choices)) return choices + let formattedChoices = formatChoices(choices, className) + let { __currentPromptConfig } = global as any + let { shortcuts: globalShortcuts } = + __currentPromptConfig || {} + + if (globalShortcuts && choices?.[0]) { + let shortcuts = globalShortcuts.filter(shortcut => { + if (shortcut?.condition) { + return shortcut.condition(choices?.[0]) + } + return true + }) + + global.send(Channel.SET_SHORTCUTS, shortcuts) + } + global.kitPrevChoices = formattedChoices + + global.setLoading(false) + return formattedChoices } global.setChoices = async (choices, config) => { - let formattedChoices = await global.___kitFormatChoices( - choices, - config?.className || "" - ) - global.send(Channel.SET_CHOICES, { - choices: formattedChoices, - skipInitialSearch: config?.skipInitialSearch || false, - inputRegex: config?.inputRegex || "", - generated: Boolean(config?.generated) - }) + let formattedChoices = await global.___kitFormatChoices( + choices, + config?.className || "" + ) + global.send(Channel.SET_CHOICES, { + choices: formattedChoices, + skipInitialSearch: config?.skipInitialSearch || false, + inputRegex: config?.inputRegex || "", + generated: Boolean(config?.generated), + }) - performance.measure("SET_CHOICES", "run") + performance.measure("SET_CHOICES", "run") } global.flag ||= {} -global.prepFlags = (flagsOptions: FlagsObject): FlagsObject => { - for (let key of Object.keys(global?.flag)) { - delete global?.flag?.[key] - } - - if (!flagsOptions || Object.entries(flagsOptions)?.length === 0) { - return false - } - - let validFlags = { - sortChoicesKey: (flagsOptions as FlagsWithKeys)?.sortChoicesKey || [], - order: (flagsOptions as FlagsWithKeys)?.order || [] - } - let currentFlags = Object.entries(flagsOptions) - for (let [key, value] of currentFlags) { - if (key === "order") continue - if (key === "sortChoicesKey") continue - - let validFlag = { - ...value, - name: value?.name || key, - shortcut: value?.shortcut || "", - description: value?.description || "", - value: key, - bar: value?.bar || "", - preview: value?.preview || "", - hasAction: Boolean(value?.onAction) - } - validFlags[key] = validFlag - - if (value?.group) { - validFlags[key].group = value.group - } - } - - for (const [key, value] of currentFlags) { - if (key === "order") continue - if (key === "sortChoicesKey") continue - const choice = { - id: key, - name: value?.name || key, - value: key, - description: value?.description || "", - preview: value?.preview || "
", - shortcut: value?.shortcut || "", - onAction: value?.onAction || null - } as Choice - - if (value?.group) { - choice.group = value.group - } - - global.__kitActionsMap.set(value?.name || key, choice) - } - - return validFlags -} - -global.setFlags = async (flags: FlagsObject, options = {}) => { - let flagsMessage = { - flags: global.prepFlags(flags), - options: { - name: options?.name || "", - placeholder: options?.placeholder || "", - active: options?.active || "Actions" - } - } - // TODO: Move props from FlagsObject like "order", "sortChoicesKey" to the options - await global.sendWait(Channel.SET_FLAGS, flagsMessage) +global.prepFlags = ( + flagsOptions: FlagsObject +): FlagsObject => { + for (let key of Object.keys(global?.flag)) { + delete global?.flag?.[key] + } + + if ( + !flagsOptions || + Object.entries(flagsOptions)?.length === 0 + ) { + return false + } + + let validFlags = { + sortChoicesKey: + (flagsOptions as FlagsWithKeys)?.sortChoicesKey || [], + order: (flagsOptions as FlagsWithKeys)?.order || [], + } + let currentFlags = Object.entries(flagsOptions) + for (let [key, value] of currentFlags) { + if (key === "order") continue + if (key === "sortChoicesKey") continue + + let validFlag = { + ...value, + name: value?.name || key, + shortcut: value?.shortcut || "", + description: value?.description || "", + value: key, + bar: value?.bar || "", + preview: value?.preview || "", + hasAction: Boolean(value?.onAction), + } + validFlags[key] = validFlag + + if (value?.group) { + validFlags[key].group = value.group + } + } + + for (const [key, value] of currentFlags) { + if (key === "order") continue + if (key === "sortChoicesKey") continue + const choice = { + id: key, + name: value?.name || key, + value: key, + description: value?.description || "", + preview: value?.preview || "
", + shortcut: value?.shortcut || "", + onAction: value?.onAction || null, + } as Choice + + if (value?.group) { + choice.group = value.group + } + + global.__kitActionsMap.set(value?.name || key, choice) + } + + return validFlags +} + +global.setFlags = async ( + flags: FlagsObject, + options = {} +) => { + let flagsMessage = { + flags: global.prepFlags(flags), + options: { + name: options?.name || "", + placeholder: options?.placeholder || "", + active: options?.active || "Actions", + }, + } + // TODO: Move props from FlagsObject like "order", "sortChoicesKey" to the options + await global.sendWait(Channel.SET_FLAGS, flagsMessage) } function sortArrayByIndex(arr) { - const sortedArr = [] - const indexedItems = [] - - // Separate indexed items from non-indexed items - arr.forEach((item, i) => { - if (item.hasOwnProperty("index")) { - indexedItems.push({ item, index: item.index }) - } else { - sortedArr.push(item) - } - }) - - // Sort indexed items based on their index - indexedItems.sort((a, b) => a.index - b.index) - - // Insert indexed items into the sorted array at their respective positions - for (const { item, index } of indexedItems) { - sortedArr.splice(index, 0, item) - } - - return sortedArr -} - -export let getFlagsFromActions = (actions: PromptConfig["actions"]) => { - let flags: FlagsObject = {} - let indices = new Set() - for (let a of actions as Action[]) { - if (a?.index) { - indices.add(a.index) - } - } - let groups = new Set() - if (Array.isArray(actions)) { - const sortedActions = sortArrayByIndex(actions) - for (let i = 0; i < sortedActions.length; i++) { - let action = sortedActions[i] - if (typeof action === "string") { - action = { - name: action, - flag: action - } - } - if (action?.group) { - groups.add(action.group) - } - - let flagAction = { - flag: action.flag || action.name, - index: i, - close: true, - ...action, - hasAction: !!action?.onAction, - bar: action?.visible ? "right" : "" - } as Action - flags[action.flag || action.name] = flagAction - } - } - - flags.sortChoicesKey = Array.from(groups).map((g) => "index") - - return flags + const sortedArr = [] + const indexedItems = [] + + // Separate indexed items from non-indexed items + arr.forEach((item, i) => { + if (item.hasOwnProperty("index")) { + indexedItems.push({ item, index: item.index }) + } else { + sortedArr.push(item) + } + }) + + // Sort indexed items based on their index + indexedItems.sort((a, b) => a.index - b.index) + + // Insert indexed items into the sorted array at their respective positions + for (const { item, index } of indexedItems) { + sortedArr.splice(index, 0, item) + } + + return sortedArr +} + +export let getFlagsFromActions = ( + actions: PromptConfig["actions"] +) => { + let flags: FlagsObject = {} + let indices = new Set() + for (let a of actions as Action[]) { + if (a?.index) { + indices.add(a.index) + } + } + let groups = new Set() + if (Array.isArray(actions)) { + const sortedActions = sortArrayByIndex(actions) + for (let i = 0; i < sortedActions.length; i++) { + let action = sortedActions[i] + if (typeof action === "string") { + action = { + name: action, + flag: action, + } + } + if (action?.group) { + groups.add(action.group) + } + + let flagAction = { + flag: action.flag || action.name, + index: i, + close: true, + ...action, + hasAction: !!action?.onAction, + bar: action?.visible ? "right" : "", + } as Action + flags[action.flag || action.name] = flagAction + } + } + + flags.sortChoicesKey = Array.from(groups).map( + g => "index" + ) + + return flags } global.setActions = (actions: Action[], options = {}) => { - let flags = getFlagsFromActions(actions) - setFlags(flags, options) + let flags = getFlagsFromActions(actions) + setFlags(flags, options) } global.openActions = async () => { - await sendWait(Channel.OPEN_ACTIONS) + await sendWait(Channel.OPEN_ACTIONS) } global.closeActions = async () => { - await sendWait(Channel.CLOSE_ACTIONS) + await sendWait(Channel.CLOSE_ACTIONS) } global.setFlagValue = (value: any) => { - return global.sendWait(Channel.SET_FLAG_VALUE, value) + return global.sendWait(Channel.SET_FLAG_VALUE, value) } global.hide = async (hideOptions = {}) => { - await global.sendWait(Channel.HIDE_APP, hideOptions) - if (process.env.KIT_HIDE_DELAY) { - await wait(-process.env.KIT_HIDE_DELAY) - } + await global.sendWait(Channel.HIDE_APP, hideOptions) + if (process.env.KIT_HIDE_DELAY) { + await wait(-process.env.KIT_HIDE_DELAY) + } } global.show = async () => { - await global.sendWait(Channel.SHOW_APP) + await global.sendWait(Channel.SHOW_APP) } global.blur = async () => { - await global.sendWait(Channel.BLUR_APP, {}) + await global.sendWait(Channel.BLUR_APP, {}) } global.run = run -let wrapCode = (html: string, containerClass: string, codeStyles = "") => { - return `
+let wrapCode = (
+  html: string,
+  containerClass: string,
+  codeStyles = ""
+) => {
+  return `
   
 `
 export let proPane = () =>
-	`
+  `
   ${checkmarkStyles}
 
 
@@ -1014,268 +1160,308 @@ export let proPane = () =>
 `
 
 export const getShellSeparator = () => {
-	let separator = "&&"
-	if (process.platform === "win32") {
-		separator = "&"
-	}
-	// if powershell
-	if (
-		process.env.KIT_SHELL?.includes("pwsh") ||
-		process.env.KIT_SHELL?.includes("powershell") ||
-		process.env.SHELL?.includes("pwsh") ||
-		process.env.SHELL?.includes("powershell") ||
-		process.env.ComSpec?.includes("powershell") ||
-		process.env.ComSpec?.includes("pwsh")
-	) {
-		separator = ";"
-	}
-
-	if (
-		process.env.KIT_SHELL?.includes("fish") ||
-		process.env.SHELL?.includes("fish")
-	) {
-		separator = ";"
-	}
-
-	return separator
+  let separator = "&&"
+  if (process.platform === "win32") {
+    separator = "&"
+  }
+  // if powershell
+  if (
+    process.env.KIT_SHELL?.includes("pwsh") ||
+    process.env.KIT_SHELL?.includes("powershell") ||
+    process.env.SHELL?.includes("pwsh") ||
+    process.env.SHELL?.includes("powershell") ||
+    process.env.ComSpec?.includes("powershell") ||
+    process.env.ComSpec?.includes("pwsh")
+  ) {
+    separator = ";"
+  }
+
+  if (
+    process.env.KIT_SHELL?.includes("fish") ||
+    process.env.SHELL?.includes("fish")
+  ) {
+    separator = ";"
+  }
+
+  return separator
 }
 
 export let getTrustedKenvsKey = () => {
-	let username =
-		process.env?.USER || process.env?.USERNAME || "NO_USER_ENV_FOUND"
+  let username =
+    process.env?.USER ||
+    process.env?.USERNAME ||
+    "NO_USER_ENV_FOUND"
 
-	let formattedUsername = username.replace(/[^a-zA-Z0-9]/g, "_").toUpperCase()
+  let formattedUsername = username
+    .replace(/[^a-zA-Z0-9]/g, "_")
+    .toUpperCase()
 
-	let trustedKenvKey = `KIT_${formattedUsername}_DANGEROUSLY_TRUST_KENVS`
+  let trustedKenvKey = `KIT_${formattedUsername}_DANGEROUSLY_TRUST_KENVS`
 
-	return trustedKenvKey
+  return trustedKenvKey
 }
 
 export const uniq = (array: any[]): any[] => {
-	if (!Array.isArray(array)) {
-		throw new Error("Input should be an array")
-	}
-	return [...new Set(array)]
+  if (!Array.isArray(array)) {
+    throw new Error("Input should be an array")
+  }
+  return [...new Set(array)]
 }
 
 interface DebounceSettings {
-	leading?: boolean
-	trailing?: boolean
+  leading?: boolean
+  trailing?: boolean
 }
 
 type Procedure = (...args: any[]) => void
 
-type DebouncedFunc = (...args: Parameters) => void
+type DebouncedFunc = (
+  ...args: Parameters
+) => void
 
 export const debounce = (
-	func: T,
-	waitMilliseconds = 0,
-	options: DebounceSettings = {}
+  func: T,
+  waitMilliseconds = 0,
+  options: DebounceSettings = {}
 ): DebouncedFunc => {
-	let timeoutId: ReturnType | undefined
-
-	return (...args: Parameters) => {
-		const doLater = () => {
-			timeoutId = undefined
-			// If trailing is enabled, we invoke the function only if the function was invoked during the wait period
-			if (options.trailing !== false) {
-				func(...args)
-			}
-		}
-
-		const shouldCallNow = options.leading && timeoutId === undefined
-
-		// Always clear the timeout
-		if (timeoutId !== undefined) {
-			clearTimeout(timeoutId)
-		}
-
-		timeoutId = setTimeout(doLater, waitMilliseconds)
-
-		// If leading is enabled and no function call has been scheduled, we call the function immediately
-		if (shouldCallNow) {
-			func(...args)
-		}
-	}
+  let timeoutId: ReturnType | undefined
+
+  return (...args: Parameters) => {
+    const doLater = () => {
+      timeoutId = undefined
+      // If trailing is enabled, we invoke the function only if the function was invoked during the wait period
+      if (options.trailing !== false) {
+        func(...args)
+      }
+    }
+
+    const shouldCallNow =
+      options.leading && timeoutId === undefined
+
+    // Always clear the timeout
+    if (timeoutId !== undefined) {
+      clearTimeout(timeoutId)
+    }
+
+    timeoutId = setTimeout(doLater, waitMilliseconds)
+
+    // If leading is enabled and no function call has been scheduled, we call the function immediately
+    if (shouldCallNow) {
+      func(...args)
+    }
+  }
 }
 
-export const range = (start: number, end: number, step = 1): number[] => {
-	return Array.from(
-		{ length: Math.ceil((end - start) / step) },
-		(_, i) => start + i * step
-	)
+export const range = (
+  start: number,
+  end: number,
+  step = 1
+): number[] => {
+  return Array.from(
+    { length: Math.ceil((end - start) / step) },
+    (_, i) => start + i * step
+  )
 }
 
 type Iteratee = ((item: T) => any) | keyof T
 
-export let sortBy = (collection: T[], iteratees: Iteratee[]): T[] => {
-	const iterateeFuncs = iteratees.map((iteratee) =>
-		typeof iteratee === "function"
-			? iteratee
-			: (item: T) => item[iteratee as keyof T]
-	)
-
-	return [...collection].sort((a, b) => {
-		for (const iteratee of iterateeFuncs) {
-			const valueA = iteratee(a)
-			const valueB = iteratee(b)
-
-			if (valueA < valueB) {
-				return -1
-			} else if (valueA > valueB) {
-				return 1
-			}
-		}
-
-		return 0
-	})
+export let sortBy = (
+  collection: T[],
+  iteratees: Iteratee[]
+): T[] => {
+  const iterateeFuncs = iteratees.map(iteratee =>
+    typeof iteratee === "function"
+      ? iteratee
+      : (item: T) => item[iteratee as keyof T]
+  )
+
+  return [...collection].sort((a, b) => {
+    for (const iteratee of iterateeFuncs) {
+      const valueA = iteratee(a)
+      const valueB = iteratee(b)
+
+      if (valueA < valueB) {
+        return -1
+      } else if (valueA > valueB) {
+        return 1
+      }
+    }
+
+    return 0
+  })
 }
 
-export let isUndefined = (value: any): value is undefined => {
-	return value === undefined
+export let isUndefined = (
+  value: any
+): value is undefined => {
+  return value === undefined
 }
 
 export let isString = (value: any): value is string => {
-	return typeof value === "string"
+  return typeof value === "string"
 }
 
-export let getCachePath = (filePath: string, type: string) => {
-	// Normalize file path
-	const normalizedPath = path.normalize(filePath)
-
-	// Replace all non-alphanumeric characters and path separators with dashes
-	let dashedName = normalizedPath.replace(/[^a-zA-Z0-9]/g, "-")
-
-	// Remove leading dashes
-	while (dashedName.charAt(0) === "-") {
-		dashedName = dashedName.substr(1)
-	}
+export let getCachePath = (
+  filePath: string,
+  type: string
+) => {
+  // Normalize file path
+  const normalizedPath = path.normalize(filePath)
+
+  // Replace all non-alphanumeric characters and path separators with dashes
+  let dashedName = normalizedPath.replace(
+    /[^a-zA-Z0-9]/g,
+    "-"
+  )
+
+  // Remove leading dashes
+  while (dashedName.charAt(0) === "-") {
+    dashedName = dashedName.substr(1)
+  }
 
-	// Replace multiple consecutive dashes with a single dash
-	dashedName = dashedName.replace(/-+/g, "-")
+  // Replace multiple consecutive dashes with a single dash
+  dashedName = dashedName.replace(/-+/g, "-")
 
-	// Append .json extension
-	return kitPath("cache", type, `${dashedName}.json`)
+  // Append .json extension
+  return kitPath("cache", type, `${dashedName}.json`)
 }
 
 export let adjustPackageName = (packageName: string) => {
-	let adjustedPackageName = ""
-	if (packageName.startsWith("@")) {
-		let parts = packageName.split("/")
-		adjustedPackageName = `${parts[0]}/${parts[1]}`
-	} else {
-		adjustedPackageName = packageName.split("/")[0]
-	}
-
-	return adjustedPackageName
+  let adjustedPackageName = ""
+  if (packageName.startsWith("@")) {
+    let parts = packageName.split("/")
+    adjustedPackageName = `${parts[0]}/${parts[1]}`
+  } else {
+    adjustedPackageName = packageName.split("/")[0]
+  }
+
+  return adjustedPackageName
 }
 
 export let keywordInputTransformer = (keyword: string) => {
-	if (!keyword) return (input: string) => input
+  if (!keyword) return (input: string) => input
 
-	let keywordRegex = new RegExp(`(?<=${global.arg.keyword}\\s)(.*)`, "gi")
+  let keywordRegex = new RegExp(
+    `(?<=${global.arg.keyword}\\s)(.*)`,
+    "gi"
+  )
 
-	return (input: string) => {
-		return input.match(keywordRegex)?.[0] || ""
-	}
+  return (input: string) => {
+    return input.match(keywordRegex)?.[0] || ""
+  }
 }
 
 export let escapeHTML = (text: string) => {
-	// Handle null or undefined input
-	if (!text || typeof text !== "string") return ""
-
-	const map = {
-		"&": "&",
-		"<": "<",
-		">": ">",
-		'"': """,
-		"'": "'"
-	}
-
-	// Perform HTML escape on the updated text
-	text = text.replace(/[&<>"']/g, function (m) {
-		return map[m]
-	})
-
-	// Convert tabs to spaces
-	text = text.replace(/\t/g, "    ")
-
-	// Convert newline characters to 
- return text.replace(/\n/g, "
") + // Handle null or undefined input + if (!text || typeof text !== "string") return "" + + const map = { + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'", + } + + // Perform HTML escape on the updated text + text = text.replace(/[&<>"']/g, function (m) { + return map[m] + }) + + // Convert tabs to spaces + text = text.replace(/\t/g, " ") + + // Convert newline characters to
+ return text.replace(/\n/g, "
") } export let processInBatches = async ( - items: Promise[], - batchSize: number, - maxRetries: number = 3 + items: Promise[], + batchSize: number, + maxRetries: number = 3 ): Promise => { - let result: T[] = [] - for (let i = 0; i < items.length; i += batchSize) { - const batch = items.slice(i, i + batchSize) - const batchResults = await Promise.all( - batch.map(async (item) => { - return pRetry(async () => { - try { - return await item - } catch (error) { - console.error(`Error processing item: ${error.message}`) - throw error // Rethrow to trigger retry - } - }, { retries: maxRetries }) - }) - ) - result = result.concat(batchResults.filter((item): item is Awaited => item !== undefined)) - } - return result + let result: T[] = [] + for (let i = 0; i < items.length; i += batchSize) { + const batch = items.slice(i, i + batchSize) + const batchResults = await Promise.all( + batch.map(async item => { + return pRetry( + async () => { + try { + return await item + } catch (error) { + console.error( + `Error processing item: ${error.message}` + ) + throw error // Rethrow to trigger retry + } + }, + { retries: maxRetries } + ) + }) + ) + result = result.concat( + batchResults.filter( + (item): item is Awaited => item !== undefined + ) + ) + } + return result } -export let md = (content = "", containerClasses = "p-5 prose prose-sm") => { - return globalMd(content + "\n", containerClasses) +export let md = ( + content = "", + containerClasses = "p-5 prose prose-sm" +) => { + return globalMd(content + "\n", containerClasses) } export let highlight = async ( - markdown: string, - containerClass = "p-5 leading-loose", - injectStyles = "" + markdown: string, + containerClass = "p-5 leading-loose", + injectStyles = "" ) => { - let { default: highlight } = - global.__kitHighlight || (await import("highlight.js")) - if (!global.__kitHighlight) global.__kitHighlight = { default: highlight } - - let renderer = new marked.Renderer() - renderer.paragraph = (p) => { - // Convert a tag with href .mov, .mp4, or .ogg video links to video tags - if (p.match(/.*<\/a>/)) { - let url = p.match(/href="(.*)"/)[1] - return `` - } - - return `

${p}

` - } - - renderer.text = (text) => { - return `${text}` - } - marked.setOptions({ - renderer, - // biome-ignore lint/complexity/useArrowFunction: - highlight: function (code, lang) { - const language = highlight.getLanguage(lang) ? lang : "plaintext" - return highlight.highlight(code, { language }).value - }, - langPrefix: "hljs language-", // highlight.js css expects a top-level 'hljs' class. - pedantic: false, - gfm: true, - breaks: false, - sanitize: false, - smartLists: true, - smartypants: false, - xhtml: false - }) - - let highlightedMarkdown = marked(markdown) - - let result = `
+ let { default: highlight } = + global.__kitHighlight || (await import("highlight.js")) + if (!global.__kitHighlight) + global.__kitHighlight = { default: highlight } + + let renderer = new marked.Renderer() + renderer.paragraph = p => { + // Convert a tag with href .mov, .mp4, or .ogg video links to video tags + if (p.match(/.*<\/a>/)) { + let url = p.match(/href="(.*)"/)[1] + return `` + } + + return `

${p}

` + } + + renderer.text = text => { + return `${text}` + } + marked.setOptions({ + renderer, + // biome-ignore lint/complexity/useArrowFunction: + highlight: function (code, lang) { + const language = highlight.getLanguage(lang) + ? lang + : "plaintext" + return highlight.highlight(code, { language }).value + }, + langPrefix: "hljs language-", // highlight.js css expects a top-level 'hljs' class. + pedantic: false, + gfm: true, + breaks: false, + sanitize: false, + smartLists: true, + smartypants: false, + xhtml: false, + }) + + let highlightedMarkdown = marked(markdown) + + let result = `