keyv-github 1.2.0 → 1.4.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.
@@ -0,0 +1,14 @@
1
+ {
2
+ "plugins": [
3
+ "@semantic-release/commit-analyzer",
4
+ "@semantic-release/release-notes-generator",
5
+ "@semantic-release/npm",
6
+ [
7
+ "@semantic-release/git",
8
+ {
9
+ "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
10
+ }
11
+ ],
12
+ "@semantic-release/github"
13
+ ]
14
+ }
package/README.md CHANGED
@@ -99,6 +99,8 @@ const store = new KeyvGithub("owner/repo", {
99
99
 
100
100
  ## Key rules
101
101
 
102
+ ⚠️ **Keys are validated but NOT sanitized.** You must sanitize keys yourself before passing them to this adapter. Invalid keys will throw an error.
103
+
102
104
  Keys must be valid relative file paths:
103
105
 
104
106
  - Non-empty
@@ -109,6 +111,18 @@ Keys must be valid relative file paths:
109
111
 
110
112
  Invalid keys throw synchronously before any API request.
111
113
 
114
+ ```ts
115
+ // ✗ These will throw errors
116
+ await store.set("/absolute/path", "value"); // leading slash
117
+ await store.set("path/", "value"); // trailing slash
118
+ await store.set("path/../escape", "value"); // directory traversal
119
+ await store.set("path//double", "value"); // double slashes
120
+
121
+ // ✓ Valid keys
122
+ await store.set("data/file.txt", "value");
123
+ await store.set("nested/path/key.json", "value");
124
+ ```
125
+
112
126
  ## See Also
113
127
 
114
128
  Other Keyv storage adapters by the same author:
package/bun.lock CHANGED
@@ -9,6 +9,7 @@
9
9
  "octokit": "^5.0.5",
10
10
  },
11
11
  "devDependencies": {
12
+ "@semantic-release/git": "^10.0.1",
12
13
  "@types/bun": "latest",
13
14
  "semantic-release": "^25.0.3",
14
15
  "tsdown": "^0.20.3",
@@ -149,7 +150,9 @@
149
150
 
150
151
  "@semantic-release/commit-analyzer": ["@semantic-release/commit-analyzer@13.0.1", "", { "dependencies": { "conventional-changelog-angular": "^8.0.0", "conventional-changelog-writer": "^8.0.0", "conventional-commits-filter": "^5.0.0", "conventional-commits-parser": "^6.0.0", "debug": "^4.0.0", "import-from-esm": "^2.0.0", "lodash-es": "^4.17.21", "micromatch": "^4.0.2" }, "peerDependencies": { "semantic-release": ">=20.1.0" } }, "sha512-wdnBPHKkr9HhNhXOhZD5a2LNl91+hs8CC2vsAVYxtZH3y0dV3wKn+uZSN61rdJQZ8EGxzWB3inWocBHV9+u/CQ=="],
151
152
 
152
- "@semantic-release/error": ["@semantic-release/error@4.0.0", "", {}, "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ=="],
153
+ "@semantic-release/error": ["@semantic-release/error@3.0.0", "", {}, "sha512-5hiM4Un+tpl4cKw3lV4UgzJj+SmfNIDCLLw0TepzQxz9ZGV5ixnqkzIVF+3tp0ZHgcMKE+VNGHJjEeyFG2dcSw=="],
154
+
155
+ "@semantic-release/git": ["@semantic-release/git@10.0.1", "", { "dependencies": { "@semantic-release/error": "^3.0.0", "aggregate-error": "^3.0.0", "debug": "^4.0.0", "dir-glob": "^3.0.0", "execa": "^5.0.0", "lodash": "^4.17.4", "micromatch": "^4.0.0", "p-reduce": "^2.0.0" }, "peerDependencies": { "semantic-release": ">=18.0.0" } }, "sha512-eWrx5KguUcU2wUPaO6sfvZI0wPafUKAMNC18aXY4EnNcrZL86dEmpNVnC9uMpGZkmZJ9EfCVJBQx4pV4EMGT1w=="],
153
156
 
154
157
  "@semantic-release/github": ["@semantic-release/github@12.0.6", "", { "dependencies": { "@octokit/core": "^7.0.0", "@octokit/plugin-paginate-rest": "^14.0.0", "@octokit/plugin-retry": "^8.0.0", "@octokit/plugin-throttling": "^11.0.0", "@semantic-release/error": "^4.0.0", "aggregate-error": "^5.0.0", "debug": "^4.3.4", "dir-glob": "^3.0.1", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "issue-parser": "^7.0.0", "lodash-es": "^4.17.21", "mime": "^4.0.0", "p-filter": "^4.0.0", "tinyglobby": "^0.2.14", "undici": "^7.0.0", "url-join": "^5.0.0" }, "peerDependencies": { "semantic-release": ">=24.1.0" } }, "sha512-aYYFkwHW3c6YtHwQF0t0+lAjlU+87NFOZuH2CvWFD0Ylivc7MwhZMiHOJ0FMpIgPpCVib/VUAcOwvrW0KnxQtA=="],
155
158
 
@@ -193,7 +196,7 @@
193
196
 
194
197
  "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
195
198
 
196
- "aggregate-error": ["aggregate-error@5.0.0", "", { "dependencies": { "clean-stack": "^5.2.0", "indent-string": "^5.0.0" } }, "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw=="],
199
+ "aggregate-error": ["aggregate-error@3.1.0", "", { "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" } }, "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA=="],
197
200
 
198
201
  "ansi-escapes": ["ansi-escapes@7.3.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg=="],
199
202
 
@@ -231,7 +234,7 @@
231
234
 
232
235
  "char-regex": ["char-regex@1.0.2", "", {}, "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw=="],
233
236
 
234
- "clean-stack": ["clean-stack@5.3.0", "", { "dependencies": { "escape-string-regexp": "5.0.0" } }, "sha512-9ngPTOhYGQqNVSfeJkYXHmF7AGWp4/nN5D/QqNQs3Dvxd1Kk/WpjHfNujKHYUQ/5CoGyOyFNoWSPk5afzP0QVg=="],
237
+ "clean-stack": ["clean-stack@2.2.0", "", {}, "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A=="],
235
238
 
236
239
  "cli-highlight": ["cli-highlight@2.1.11", "", { "dependencies": { "chalk": "^4.0.0", "highlight.js": "^10.7.1", "mz": "^2.4.0", "parse5": "^5.1.1", "parse5-htmlparser2-tree-adapter": "^6.0.0", "yargs": "^16.0.0" }, "bin": { "highlight": "bin/highlight" } }, "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg=="],
237
240
 
@@ -299,7 +302,7 @@
299
302
 
300
303
  "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
301
304
 
302
- "execa": ["execa@9.6.1", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.6", "figures": "^6.1.0", "get-stream": "^9.0.0", "human-signals": "^8.0.1", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^6.0.0", "pretty-ms": "^9.2.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", "yoctocolors": "^2.1.1" } }, "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA=="],
305
+ "execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="],
303
306
 
304
307
  "fast-content-type-parse": ["fast-content-type-parse@3.0.0", "", {}, "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg=="],
305
308
 
@@ -349,7 +352,7 @@
349
352
 
350
353
  "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
351
354
 
352
- "human-signals": ["human-signals@8.0.1", "", {}, "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ=="],
355
+ "human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="],
353
356
 
354
357
  "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
355
358
 
@@ -359,7 +362,7 @@
359
362
 
360
363
  "import-without-cache": ["import-without-cache@0.2.5", "", {}, "sha512-B6Lc2s6yApwnD2/pMzFh/d5AVjdsDXjgkeJ766FmFuJELIGHNycKRj+l3A39yZPM4CchqNCB4RITEAYB1KUM6A=="],
361
364
 
362
- "indent-string": ["indent-string@5.0.0", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="],
365
+ "indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="],
363
366
 
364
367
  "index-to-position": ["index-to-position@1.2.0", "", {}, "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw=="],
365
368
 
@@ -379,7 +382,7 @@
379
382
 
380
383
  "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="],
381
384
 
382
- "is-stream": ["is-stream@4.0.1", "", {}, "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A=="],
385
+ "is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
383
386
 
384
387
  "is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="],
385
388
 
@@ -411,6 +414,8 @@
411
414
 
412
415
  "locate-path": ["locate-path@2.0.0", "", { "dependencies": { "p-locate": "^2.0.0", "path-exists": "^3.0.0" } }, "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA=="],
413
416
 
417
+ "lodash": ["lodash@4.17.23", "", {}, "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="],
418
+
414
419
  "lodash-es": ["lodash-es@4.17.23", "", {}, "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg=="],
415
420
 
416
421
  "lodash.capitalize": ["lodash.capitalize@4.2.1", "", {}, "sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw=="],
@@ -439,7 +444,7 @@
439
444
 
440
445
  "mime": ["mime@4.1.0", "", { "bin": { "mime": "bin/cli.js" } }, "sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw=="],
441
446
 
442
- "mimic-fn": ["mimic-fn@4.0.0", "", {}, "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw=="],
447
+ "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
443
448
 
444
449
  "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
445
450
 
@@ -459,7 +464,7 @@
459
464
 
460
465
  "npm": ["npm@11.10.0", "", { "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", "@npmcli/arborist": "^9.3.0", "@npmcli/config": "^10.7.0", "@npmcli/fs": "^5.0.0", "@npmcli/map-workspaces": "^5.0.3", "@npmcli/metavuln-calculator": "^9.0.3", "@npmcli/package-json": "^7.0.4", "@npmcli/promise-spawn": "^9.0.1", "@npmcli/redact": "^4.0.0", "@npmcli/run-script": "^10.0.3", "@sigstore/tuf": "^4.0.1", "abbrev": "^4.0.0", "archy": "~1.0.0", "cacache": "^20.0.3", "chalk": "^5.6.2", "ci-info": "^4.4.0", "cli-columns": "^4.0.0", "fastest-levenshtein": "^1.0.16", "fs-minipass": "^3.0.3", "glob": "^13.0.2", "graceful-fs": "^4.2.11", "hosted-git-info": "^9.0.2", "ini": "^6.0.0", "init-package-json": "^8.2.4", "is-cidr": "^6.0.3", "json-parse-even-better-errors": "^5.0.0", "libnpmaccess": "^10.0.3", "libnpmdiff": "^8.1.1", "libnpmexec": "^10.2.1", "libnpmfund": "^7.0.15", "libnpmorg": "^8.0.1", "libnpmpack": "^9.1.1", "libnpmpublish": "^11.1.3", "libnpmsearch": "^9.0.1", "libnpmteam": "^8.0.2", "libnpmversion": "^8.0.3", "make-fetch-happen": "^15.0.3", "minimatch": "^10.1.1", "minipass": "^7.1.1", "minipass-pipeline": "^1.2.4", "ms": "^2.1.2", "node-gyp": "^12.2.0", "nopt": "^9.0.0", "npm-audit-report": "^7.0.0", "npm-install-checks": "^8.0.0", "npm-package-arg": "^13.0.2", "npm-pick-manifest": "^11.0.3", "npm-profile": "^12.0.1", "npm-registry-fetch": "^19.1.1", "npm-user-validate": "^4.0.0", "p-map": "^7.0.4", "pacote": "^21.3.1", "parse-conflict-json": "^5.0.1", "proc-log": "^6.1.0", "qrcode-terminal": "^0.12.0", "read": "^5.0.1", "semver": "^7.7.4", "spdx-expression-parse": "^4.0.0", "ssri": "^13.0.1", "supports-color": "^10.2.2", "tar": "^7.5.7", "text-table": "~0.2.0", "tiny-relative-date": "^2.0.2", "treeverse": "^3.0.0", "validate-npm-package-name": "^7.0.2", "which": "^6.0.1" }, "bin": { "npm": "bin/npm-cli.js", "npx": "bin/npx-cli.js" } }, "sha512-i8hE43iSIAMFuYVi8TxsEISdELM4fIza600aLjJ0ankGPLqd0oTPKMJqAcO/QWm307MbSlWGzJcNZ0lGMQgHPA=="],
461
466
 
462
- "npm-run-path": ["npm-run-path@6.0.0", "", { "dependencies": { "path-key": "^4.0.0", "unicorn-magic": "^0.3.0" } }, "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA=="],
467
+ "npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="],
463
468
 
464
469
  "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
465
470
 
@@ -467,7 +472,7 @@
467
472
 
468
473
  "octokit": ["octokit@5.0.5", "", { "dependencies": { "@octokit/app": "^16.1.2", "@octokit/core": "^7.0.6", "@octokit/oauth-app": "^8.0.3", "@octokit/plugin-paginate-graphql": "^6.0.0", "@octokit/plugin-paginate-rest": "^14.0.0", "@octokit/plugin-rest-endpoint-methods": "^17.0.0", "@octokit/plugin-retry": "^8.0.3", "@octokit/plugin-throttling": "^11.0.3", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "@octokit/webhooks": "^14.0.0" } }, "sha512-4+/OFSqOjoyULo7eN7EA97DE0Xydj/PW5aIckxqQIoFjFwqXKuFCvXUJObyJfBF9Khu4RL/jlDRI9FPaMGfPnw=="],
469
474
 
470
- "onetime": ["onetime@6.0.0", "", { "dependencies": { "mimic-fn": "^4.0.0" } }, "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ=="],
475
+ "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
471
476
 
472
477
  "p-each-series": ["p-each-series@3.0.0", "", {}, "sha512-lastgtAdoH9YaLyDa5i5z64q+kzOcQHsQ5SsZJD3q0VEyI8mq872S3geuNbRUQLVAE9siMfgKrpj7MloKFHruw=="],
473
478
 
@@ -483,7 +488,7 @@
483
488
 
484
489
  "p-map": ["p-map@7.0.4", "", {}, "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ=="],
485
490
 
486
- "p-reduce": ["p-reduce@3.0.0", "", {}, "sha512-xsrIUgI0Kn6iyDYm9StOpOeK29XM1aboGji26+QEortiFST1hGZaUQOLhtEbqHErPpGW/aSz6allwK2qcptp0Q=="],
491
+ "p-reduce": ["p-reduce@2.1.0", "", {}, "sha512-2USApvnsutq8uoxZBGbbWM0JIYLiEMJ9RlaN7fAzVNb9OZN0SHjjTTfIcb667XynS5Y1VhwDJVDa72TnPzAYWw=="],
487
492
 
488
493
  "p-timeout": ["p-timeout@6.1.4", "", {}, "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg=="],
489
494
 
@@ -555,7 +560,7 @@
555
560
 
556
561
  "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
557
562
 
558
- "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
563
+ "signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
559
564
 
560
565
  "signale": ["signale@1.4.0", "", { "dependencies": { "chalk": "^2.3.2", "figures": "^2.0.0", "pkg-conf": "^2.1.0" } }, "sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w=="],
561
566
 
@@ -585,7 +590,7 @@
585
590
 
586
591
  "strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="],
587
592
 
588
- "strip-final-newline": ["strip-final-newline@4.0.0", "", {}, "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw=="],
593
+ "strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="],
589
594
 
590
595
  "strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="],
591
596
 
@@ -683,6 +688,16 @@
683
688
 
684
689
  "@pnpm/network.ca-file/graceful-fs": ["graceful-fs@4.2.10", "", {}, "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA=="],
685
690
 
691
+ "@semantic-release/github/@semantic-release/error": ["@semantic-release/error@4.0.0", "", {}, "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ=="],
692
+
693
+ "@semantic-release/github/aggregate-error": ["aggregate-error@5.0.0", "", { "dependencies": { "clean-stack": "^5.2.0", "indent-string": "^5.0.0" } }, "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw=="],
694
+
695
+ "@semantic-release/npm/@semantic-release/error": ["@semantic-release/error@4.0.0", "", {}, "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ=="],
696
+
697
+ "@semantic-release/npm/aggregate-error": ["aggregate-error@5.0.0", "", { "dependencies": { "clean-stack": "^5.2.0", "indent-string": "^5.0.0" } }, "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw=="],
698
+
699
+ "@semantic-release/npm/execa": ["execa@9.6.1", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.6", "figures": "^6.1.0", "get-stream": "^9.0.0", "human-signals": "^8.0.1", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^6.0.0", "pretty-ms": "^9.2.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", "yoctocolors": "^2.1.1" } }, "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA=="],
700
+
686
701
  "@semantic-release/release-notes-generator/get-stream": ["get-stream@7.0.1", "", {}, "sha512-3M8C1EOFN6r8AMUhwUAACIoXZJEOufDU5+0gFFN5uNs6XYOralD2Pqkl7m046va6x77FwposWXbAhPPIOus7mQ=="],
687
702
 
688
703
  "@semantic-release/release-notes-generator/read-package-up": ["read-package-up@11.0.0", "", { "dependencies": { "find-up-simple": "^1.0.0", "read-pkg": "^9.0.0", "type-fest": "^4.6.0" } }, "sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ=="],
@@ -697,8 +712,6 @@
697
712
 
698
713
  "env-ci/execa": ["execa@8.0.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^8.0.1", "human-signals": "^5.0.0", "is-stream": "^3.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^5.1.0", "onetime": "^6.0.0", "signal-exit": "^4.1.0", "strip-final-newline": "^3.0.0" } }, "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg=="],
699
714
 
700
- "execa/get-stream": ["get-stream@9.0.1", "", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="],
701
-
702
715
  "import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
703
716
 
704
717
  "load-json-file/parse-json": ["parse-json@4.0.0", "", { "dependencies": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" } }, "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw=="],
@@ -1015,14 +1028,18 @@
1015
1028
 
1016
1029
  "npm/yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="],
1017
1030
 
1018
- "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
1019
-
1020
- "npm-run-path/unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="],
1021
-
1022
1031
  "parse5-htmlparser2-tree-adapter/parse5": ["parse5@6.0.1", "", {}, "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="],
1023
1032
 
1024
1033
  "read-pkg/parse-json": ["parse-json@8.3.0", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "index-to-position": "^1.1.0", "type-fest": "^4.39.1" } }, "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ=="],
1025
1034
 
1035
+ "semantic-release/@semantic-release/error": ["@semantic-release/error@4.0.0", "", {}, "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ=="],
1036
+
1037
+ "semantic-release/aggregate-error": ["aggregate-error@5.0.0", "", { "dependencies": { "clean-stack": "^5.2.0", "indent-string": "^5.0.0" } }, "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw=="],
1038
+
1039
+ "semantic-release/execa": ["execa@9.6.1", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.6", "figures": "^6.1.0", "get-stream": "^9.0.0", "human-signals": "^8.0.1", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^6.0.0", "pretty-ms": "^9.2.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", "yoctocolors": "^2.1.1" } }, "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA=="],
1040
+
1041
+ "semantic-release/p-reduce": ["p-reduce@3.0.0", "", {}, "sha512-xsrIUgI0Kn6iyDYm9StOpOeK29XM1aboGji26+QEortiFST1hGZaUQOLhtEbqHErPpGW/aSz6allwK2qcptp0Q=="],
1042
+
1026
1043
  "signale/chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="],
1027
1044
 
1028
1045
  "signale/figures": ["figures@2.0.0", "", { "dependencies": { "escape-string-regexp": "^1.0.5" } }, "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA=="],
@@ -1033,6 +1050,26 @@
1033
1050
 
1034
1051
  "wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
1035
1052
 
1053
+ "@semantic-release/github/aggregate-error/clean-stack": ["clean-stack@5.3.0", "", { "dependencies": { "escape-string-regexp": "5.0.0" } }, "sha512-9ngPTOhYGQqNVSfeJkYXHmF7AGWp4/nN5D/QqNQs3Dvxd1Kk/WpjHfNujKHYUQ/5CoGyOyFNoWSPk5afzP0QVg=="],
1054
+
1055
+ "@semantic-release/github/aggregate-error/indent-string": ["indent-string@5.0.0", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="],
1056
+
1057
+ "@semantic-release/npm/aggregate-error/clean-stack": ["clean-stack@5.3.0", "", { "dependencies": { "escape-string-regexp": "5.0.0" } }, "sha512-9ngPTOhYGQqNVSfeJkYXHmF7AGWp4/nN5D/QqNQs3Dvxd1Kk/WpjHfNujKHYUQ/5CoGyOyFNoWSPk5afzP0QVg=="],
1058
+
1059
+ "@semantic-release/npm/aggregate-error/indent-string": ["indent-string@5.0.0", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="],
1060
+
1061
+ "@semantic-release/npm/execa/get-stream": ["get-stream@9.0.1", "", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="],
1062
+
1063
+ "@semantic-release/npm/execa/human-signals": ["human-signals@8.0.1", "", {}, "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ=="],
1064
+
1065
+ "@semantic-release/npm/execa/is-stream": ["is-stream@4.0.1", "", {}, "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A=="],
1066
+
1067
+ "@semantic-release/npm/execa/npm-run-path": ["npm-run-path@6.0.0", "", { "dependencies": { "path-key": "^4.0.0", "unicorn-magic": "^0.3.0" } }, "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA=="],
1068
+
1069
+ "@semantic-release/npm/execa/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
1070
+
1071
+ "@semantic-release/npm/execa/strip-final-newline": ["strip-final-newline@4.0.0", "", {}, "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw=="],
1072
+
1036
1073
  "@semantic-release/release-notes-generator/read-package-up/read-pkg": ["read-pkg@9.0.1", "", { "dependencies": { "@types/normalize-package-data": "^2.4.3", "normalize-package-data": "^6.0.0", "parse-json": "^8.0.0", "type-fest": "^4.6.0", "unicorn-magic": "^0.1.0" } }, "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA=="],
1037
1074
 
1038
1075
  "@semantic-release/release-notes-generator/read-package-up/type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
@@ -1057,6 +1094,10 @@
1057
1094
 
1058
1095
  "env-ci/execa/npm-run-path": ["npm-run-path@5.3.0", "", { "dependencies": { "path-key": "^4.0.0" } }, "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ=="],
1059
1096
 
1097
+ "env-ci/execa/onetime": ["onetime@6.0.0", "", { "dependencies": { "mimic-fn": "^4.0.0" } }, "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ=="],
1098
+
1099
+ "env-ci/execa/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
1100
+
1060
1101
  "env-ci/execa/strip-final-newline": ["strip-final-newline@3.0.0", "", {}, "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw=="],
1061
1102
 
1062
1103
  "npm/minipass-flush/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
@@ -1069,12 +1110,32 @@
1069
1110
 
1070
1111
  "read-pkg/parse-json/type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
1071
1112
 
1113
+ "semantic-release/aggregate-error/clean-stack": ["clean-stack@5.3.0", "", { "dependencies": { "escape-string-regexp": "5.0.0" } }, "sha512-9ngPTOhYGQqNVSfeJkYXHmF7AGWp4/nN5D/QqNQs3Dvxd1Kk/WpjHfNujKHYUQ/5CoGyOyFNoWSPk5afzP0QVg=="],
1114
+
1115
+ "semantic-release/aggregate-error/indent-string": ["indent-string@5.0.0", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="],
1116
+
1117
+ "semantic-release/execa/get-stream": ["get-stream@9.0.1", "", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="],
1118
+
1119
+ "semantic-release/execa/human-signals": ["human-signals@8.0.1", "", {}, "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ=="],
1120
+
1121
+ "semantic-release/execa/is-stream": ["is-stream@4.0.1", "", {}, "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A=="],
1122
+
1123
+ "semantic-release/execa/npm-run-path": ["npm-run-path@6.0.0", "", { "dependencies": { "path-key": "^4.0.0", "unicorn-magic": "^0.3.0" } }, "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA=="],
1124
+
1125
+ "semantic-release/execa/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
1126
+
1127
+ "semantic-release/execa/strip-final-newline": ["strip-final-newline@4.0.0", "", {}, "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw=="],
1128
+
1072
1129
  "signale/chalk/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="],
1073
1130
 
1074
1131
  "signale/chalk/supports-color": ["supports-color@5.5.0", "", { "dependencies": { "has-flag": "^3.0.0" } }, "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="],
1075
1132
 
1076
1133
  "signale/figures/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="],
1077
1134
 
1135
+ "@semantic-release/npm/execa/npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
1136
+
1137
+ "@semantic-release/npm/execa/npm-run-path/unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="],
1138
+
1078
1139
  "@semantic-release/release-notes-generator/read-package-up/read-pkg/normalize-package-data": ["normalize-package-data@6.0.2", "", { "dependencies": { "hosted-git-info": "^7.0.0", "semver": "^7.3.5", "validate-npm-package-license": "^3.0.4" } }, "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g=="],
1079
1140
 
1080
1141
  "@semantic-release/release-notes-generator/read-package-up/read-pkg/parse-json": ["parse-json@8.3.0", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "index-to-position": "^1.1.0", "type-fest": "^4.39.1" } }, "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ=="],
@@ -1095,10 +1156,16 @@
1095
1156
 
1096
1157
  "env-ci/execa/npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
1097
1158
 
1159
+ "env-ci/execa/onetime/mimic-fn": ["mimic-fn@4.0.0", "", {}, "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw=="],
1160
+
1098
1161
  "npm/minipass-flush/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
1099
1162
 
1100
1163
  "npm/minipass-pipeline/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
1101
1164
 
1165
+ "semantic-release/execa/npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
1166
+
1167
+ "semantic-release/execa/npm-run-path/unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="],
1168
+
1102
1169
  "signale/chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="],
1103
1170
 
1104
1171
  "@semantic-release/release-notes-generator/read-package-up/read-pkg/normalize-package-data/hosted-git-info": ["hosted-git-info@7.0.2", "", { "dependencies": { "lru-cache": "^10.0.1" } }, "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w=="],
package/dist/index.d.mts CHANGED
@@ -3,24 +3,47 @@ import { Octokit } from "octokit";
3
3
  import { KeyvStoreAdapter, StoredData } from "keyv";
4
4
 
5
5
  //#region src/index.d.ts
6
+ /** Minimal Map-like interface for SHA caching. */
7
+ interface ShaMap {
8
+ get(key: string): string | null | undefined;
9
+ set(key: string, value: string | null): void;
10
+ }
6
11
  interface KeyvGithubOptions {
7
12
  url: string;
8
13
  branch?: string;
9
14
  client?: Octokit | Octokit["rest"];
10
- /** Customize the commit message. value is null for deletes. */
15
+ /**
16
+ * Customize the commit message for single-key operations. value is null for deletes.
17
+ * @warning Consider adding `[skip ci]` to your commit messages to prevent
18
+ * triggering CI workflows on each key-value update.
19
+ */
11
20
  msg?: (key: string, value: string | null) => string;
21
+ /**
22
+ * Customize the commit message for batch operations (setMany, deleteMany, clear).
23
+ * @param operation - 'set' | 'delete' | 'clear'
24
+ * @param paths - array of file paths being modified
25
+ * @warning Consider adding `[skip ci]` to your commit messages to prevent
26
+ * triggering CI workflows on each key-value update.
27
+ */
28
+ batchMsg?: (operation: "set" | "delete" | "clear", paths: string[]) => string;
12
29
  /** clear() deletes every file in the repo and is disabled by default. Set to true to allow it. */
13
30
  enableClear?: boolean;
14
31
  /** Path prefix prepended to every key (e.g. 'data/'). Defaults to ''. */
15
32
  prefix?: string;
16
33
  /** Path suffix appended to every key (e.g. '.json'). Defaults to ''. */
17
34
  suffix?: string;
35
+ /** SHA cache map. Defaults to new Map(). Pass any keyv-like object with get/set. */
36
+ shaMap?: ShaMap;
18
37
  }
19
38
  /**
20
39
  * Keyv storage adapter backed by a GitHub repository.
21
40
  *
22
41
  * Each key is a file path in the repo; the file content is the value.
23
42
  * Example: new KeyvGithub("https://github.com/owner/repo/tree/main", { client })
43
+ *
44
+ * @warning **Keys are validated but NOT sanitized.** You must ensure keys are valid
45
+ * GitHub file paths before calling any method. Invalid keys throw an error.
46
+ * Requirements: non-empty, no leading/trailing `/`, no `//`, no `.`/`..` segments, no null bytes.
24
47
  */
25
48
  declare class KeyvGithub extends EventEmitter implements KeyvStoreAdapter {
26
49
  opts: KeyvGithubOptions;
@@ -32,9 +55,12 @@ declare class KeyvGithub extends EventEmitter implements KeyvStoreAdapter {
32
55
  get branch(): string;
33
56
  rest: Octokit["rest"];
34
57
  private msg;
58
+ private batchMsg;
35
59
  readonly enableClear: boolean;
36
60
  readonly prefix: string;
37
61
  readonly suffix: string;
62
+ /** SHA cache: key → sha (string), null (file doesn't exist), undefined (unknown). */
63
+ readonly shaMap: ShaMap;
38
64
  constructor(url: string, options?: Omit<KeyvGithubOptions, "url">);
39
65
  /** Converts a user key to the GitHub file path. */
40
66
  private toPath;
@@ -69,4 +95,4 @@ declare class KeyvGithub extends EventEmitter implements KeyvStoreAdapter {
69
95
  iterator<Value>(prefix?: string): AsyncGenerator<[string, Value | undefined]>;
70
96
  }
71
97
  //#endregion
72
- export { KeyvGithubOptions, KeyvGithub as default };
98
+ export { KeyvGithubOptions, ShaMap, KeyvGithub as default };
package/dist/index.mjs CHANGED
@@ -7,6 +7,10 @@ import { Octokit } from "octokit";
7
7
  *
8
8
  * Each key is a file path in the repo; the file content is the value.
9
9
  * Example: new KeyvGithub("https://github.com/owner/repo/tree/main", { client })
10
+ *
11
+ * @warning **Keys are validated but NOT sanitized.** You must ensure keys are valid
12
+ * GitHub file paths before calling any method. Invalid keys throw an error.
13
+ * Requirements: non-empty, no leading/trailing `/`, no `//`, no `.`/`..` segments, no null bytes.
10
14
  */
11
15
  var KeyvGithub = class KeyvGithub extends EventEmitter {
12
16
  opts;
@@ -20,9 +24,12 @@ var KeyvGithub = class KeyvGithub extends EventEmitter {
20
24
  }
21
25
  rest;
22
26
  msg;
27
+ batchMsg;
23
28
  enableClear;
24
29
  prefix;
25
30
  suffix;
31
+ /** SHA cache: key → sha (string), null (file doesn't exist), undefined (unknown). */
32
+ shaMap;
26
33
  constructor(url, options = {}) {
27
34
  super();
28
35
  const match = url.match(/(?:.*github\.com[/:])?([^/:]+)\/([^/]+?)(?:\.git)?(?:\/tree\/([^?#]+))?(?:[?#].*)?$/);
@@ -35,10 +42,16 @@ var KeyvGithub = class KeyvGithub extends EventEmitter {
35
42
  ...options
36
43
  };
37
44
  this.rest = options.client instanceof Octokit ? options.client.rest : options.client ?? new Octokit().rest;
38
- this.msg = options.msg ?? ((key, value) => value === null ? `delete ${key}` : `update ${key}`);
45
+ this.msg = options.msg ?? ((key, value) => value === null ? `delete ${key} [skip ci]` : `update ${key} [skip ci]`);
46
+ this.batchMsg = options.batchMsg ?? ((op, paths) => {
47
+ const n = paths.length;
48
+ if (op === "clear") return `clear: remove ${n} files [skip ci]`;
49
+ return `batch ${op} ${n} files [skip ci]`;
50
+ });
39
51
  this.enableClear = options.enableClear ?? false;
40
52
  this.prefix = options.prefix ?? "";
41
53
  this.suffix = options.suffix ?? "";
54
+ this.shaMap = options.shaMap ?? /* @__PURE__ */ new Map();
42
55
  }
43
56
  /** Converts a user key to the GitHub file path. */
44
57
  toPath(key) {
@@ -65,39 +78,41 @@ var KeyvGithub = class KeyvGithub extends EventEmitter {
65
78
  if (path.split("/").some((seg) => seg === ".." || seg === ".")) throw new Error(`Path must not contain '.' or '..' segments: ${path}`);
66
79
  }
67
80
  async get(key) {
68
- this.validatePath(this.toPath(key));
81
+ const path = this.toPath(key);
82
+ this.validatePath(path);
69
83
  try {
70
84
  const { data } = await this.rest.repos.getContent({
71
85
  owner: this.owner,
72
86
  repo: this.repo,
73
- path: this.toPath(key),
87
+ path,
74
88
  ref: this.ref
75
89
  });
76
- if (Array.isArray(data) || data.type !== "file") return void 0;
90
+ if (Array.isArray(data) || data.type !== "file") {
91
+ this.shaMap.set(path, null);
92
+ return;
93
+ }
94
+ this.shaMap.set(path, data.sha);
77
95
  return Buffer.from(data.content, "base64").toString("utf-8");
78
96
  } catch (e) {
79
- if (KeyvGithub.isHttpError(e) && e.status === 404) return void 0;
97
+ if (KeyvGithub.isHttpError(e) && e.status === 404) {
98
+ this.shaMap.set(path, null);
99
+ return;
100
+ }
80
101
  throw e;
81
102
  }
82
103
  }
83
104
  async set(key, value, ttl) {
84
105
  if (ttl !== void 0) throw new Error("TTL is not supported natively by keyv-github. Use new Keyv(store) which handles TTL via value expiration metadata.");
85
106
  if (typeof value !== "string") throw new Error("keyv-github only supports string values natively. Use new Keyv(store) which serializes values automatically.");
86
- this.validatePath(this.toPath(key));
87
107
  const path = this.toPath(key);
88
- let sha;
89
- try {
90
- const { data } = await this.rest.repos.getContent({
91
- owner: this.owner,
92
- repo: this.repo,
93
- path,
94
- ref: this.ref
95
- });
96
- if (!Array.isArray(data) && data.type === "file") sha = data.sha;
97
- } catch (e) {
98
- if (!KeyvGithub.isHttpError(e) || e.status !== 404) throw e;
108
+ this.validatePath(path);
109
+ let cachedSha = this.shaMap.get(path);
110
+ if (cachedSha === void 0) {
111
+ await this.get(key);
112
+ cachedSha = this.shaMap.get(path);
99
113
  }
100
- await this.rest.repos.createOrUpdateFileContents({
114
+ const sha = cachedSha ?? void 0;
115
+ const { data } = await this.rest.repos.createOrUpdateFileContents({
101
116
  owner: this.owner,
102
117
  repo: this.repo,
103
118
  path,
@@ -106,44 +121,58 @@ var KeyvGithub = class KeyvGithub extends EventEmitter {
106
121
  sha,
107
122
  branch: this.ref
108
123
  });
124
+ this.shaMap.set(path, data.content?.sha ?? null);
109
125
  }
110
126
  async delete(key) {
111
- this.validatePath(this.toPath(key));
112
127
  const path = this.toPath(key);
128
+ this.validatePath(path);
129
+ let cachedSha = this.shaMap.get(path);
130
+ if (cachedSha === void 0) {
131
+ await this.get(key);
132
+ cachedSha = this.shaMap.get(path);
133
+ }
134
+ if (!cachedSha) return false;
135
+ const sha = cachedSha;
113
136
  try {
114
- const { data } = await this.rest.repos.getContent({
115
- owner: this.owner,
116
- repo: this.repo,
117
- path,
118
- ref: this.ref
119
- });
120
- if (Array.isArray(data) || data.type !== "file") return false;
121
137
  await this.rest.repos.deleteFile({
122
138
  owner: this.owner,
123
139
  repo: this.repo,
124
140
  path,
125
141
  message: this.msg(path, null),
126
- sha: data.sha,
142
+ sha,
127
143
  branch: this.ref
128
144
  });
145
+ this.shaMap.set(path, null);
129
146
  return true;
130
147
  } catch (e) {
131
- if (KeyvGithub.isHttpError(e) && e.status === 404) return false;
148
+ if (KeyvGithub.isHttpError(e) && e.status === 404) {
149
+ this.shaMap.set(path, null);
150
+ return false;
151
+ }
132
152
  throw e;
133
153
  }
134
154
  }
135
155
  async has(key) {
136
- this.validatePath(this.toPath(key));
156
+ const path = this.toPath(key);
157
+ this.validatePath(path);
137
158
  try {
138
159
  const { data } = await this.rest.repos.getContent({
139
160
  owner: this.owner,
140
161
  repo: this.repo,
141
- path: this.toPath(key),
162
+ path,
142
163
  ref: this.ref
143
164
  });
144
- return !Array.isArray(data) && data.type === "file";
165
+ if (Array.isArray(data) || data.type !== "file") {
166
+ this.shaMap.set(path, null);
167
+ return false;
168
+ }
169
+ this.shaMap.set(path, data.sha);
170
+ return true;
145
171
  } catch (e) {
146
- if (KeyvGithub.isHttpError(e) && e.status === 404) return false;
172
+ if (KeyvGithub.isHttpError(e) && e.status === 404) {
173
+ this.shaMap.set(path, null);
174
+ return false;
175
+ }
147
176
  throw e;
148
177
  }
149
178
  }
@@ -204,7 +233,8 @@ var KeyvGithub = class KeyvGithub extends EventEmitter {
204
233
  this.validatePath(this.toPath(key));
205
234
  }
206
235
  const entries = values.map(({ key, value }) => [this.toPath(key), String(value)]);
207
- const message = entries.length === 1 ? this.msg(entries[0][0], entries[0][1]) : `batch update ${entries.length} files`;
236
+ const paths = entries.map(([p]) => p);
237
+ const message = entries.length === 1 ? this.msg(entries[0][0], entries[0][1]) : this.batchMsg("set", paths);
208
238
  await this._batchCommit({
209
239
  set: entries,
210
240
  message
@@ -231,7 +261,7 @@ var KeyvGithub = class KeyvGithub extends EventEmitter {
231
261
  const existingPaths = new Set(treeData.tree.filter((i) => i.type === "blob" && i.path).map((i) => i.path));
232
262
  const toDelete = keys.map((k) => this.toPath(k)).filter((p) => existingPaths.has(p));
233
263
  if (toDelete.length === 0) return false;
234
- const message = toDelete.length === 1 ? this.msg(toDelete[0], null) : `batch delete ${toDelete.length} files`;
264
+ const message = toDelete.length === 1 ? this.msg(toDelete[0], null) : this.batchMsg("delete", toDelete);
235
265
  await this._batchCommit({
236
266
  delete: toDelete,
237
267
  message
@@ -254,7 +284,7 @@ var KeyvGithub = class KeyvGithub extends EventEmitter {
254
284
  const allPaths = treeData.tree.filter((i) => i.type === "blob" && i.path && i.path.startsWith(this.prefix) && i.path.endsWith(this.suffix)).map((i) => i.path);
255
285
  if (allPaths.length > 0) await this._batchCommit({
256
286
  delete: allPaths,
257
- message: `clear: remove ${allPaths.length} files`
287
+ message: this.batchMsg("clear", allPaths)
258
288
  });
259
289
  }
260
290
  async *iterator(prefix) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "keyv-github",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "module": "src/index.ts",
5
5
  "main": "dist/index.mjs",
6
6
  "types": "dist/index.d.mts",
@@ -20,6 +20,7 @@
20
20
  "octokit": "^5.0.5"
21
21
  },
22
22
  "devDependencies": {
23
+ "@semantic-release/git": "^10.0.1",
23
24
  "@types/bun": "latest",
24
25
  "semantic-release": "^25.0.3",
25
26
  "tsdown": "^0.20.3",
package/src/index.test.ts CHANGED
@@ -386,7 +386,7 @@ describe("setMany", () => {
386
386
  expect(mockFiles.get("b/2.txt")?.content).toBe("world");
387
387
  expect(mockFiles.get("c/3.txt")?.content).toBe("!");
388
388
  expect(messages).toHaveLength(1); // single commit
389
- expect(messages[0]).toBe("batch update 3 files");
389
+ expect(messages[0]).toBe("batch set 3 files [skip ci]");
390
390
  });
391
391
 
392
392
  test("uses msg hook for single-entry batch", async () => {
@@ -440,7 +440,7 @@ describe("deleteMany", () => {
440
440
  expect(mockFiles.has("b")).toBe(false);
441
441
  expect(mockFiles.has("c")).toBe(true);
442
442
  expect(messages).toHaveLength(1); // single commit
443
- expect(messages[0]).toBe("batch delete 2 files");
443
+ expect(messages[0]).toBe("batch delete 2 files [skip ci]");
444
444
  });
445
445
 
446
446
  test("returns false when no keys exist", async () => {
@@ -527,15 +527,15 @@ describe("msg hook", () => {
527
527
  expect(messages[messages.length - 1]).toBe("chore: rm to/remove");
528
528
  });
529
529
 
530
- test("default msg falls back to 'update <key>' / 'delete <key>'", async () => {
530
+ test("default msg falls back to 'update <key> [skip ci]' / 'delete <key> [skip ci]'", async () => {
531
531
  const { store, messages } = makeStore();
532
532
  await store.set("k", "v");
533
- expect(messages[messages.length - 1]).toBe("update k");
533
+ expect(messages[messages.length - 1]).toBe("update k [skip ci]");
534
534
 
535
535
  const files2 = new Map([["k2", { content: "v", sha: "s1" }]]);
536
536
  const { store: store2, messages: messages2 } = makeStore(files2);
537
537
  await store2.delete("k2");
538
- expect(messages2[messages2.length - 1]).toBe("delete k2");
538
+ expect(messages2[messages2.length - 1]).toBe("delete k2 [skip ci]");
539
539
  });
540
540
  });
541
541
 
package/src/index.ts CHANGED
@@ -2,18 +2,38 @@ import { EventEmitter } from "events";
2
2
  import type { KeyvStoreAdapter, StoredData } from "keyv";
3
3
  import { Octokit } from "octokit";
4
4
 
5
+ /** Minimal Map-like interface for SHA caching. */
6
+ export interface ShaMap {
7
+ get(key: string): string | null | undefined;
8
+ set(key: string, value: string | null): void;
9
+ }
10
+
5
11
  export interface KeyvGithubOptions {
6
12
  url: string;
7
13
  branch?: string;
8
14
  client?: Octokit | Octokit["rest"];
9
- /** Customize the commit message. value is null for deletes. */
15
+ /**
16
+ * Customize the commit message for single-key operations. value is null for deletes.
17
+ * @warning Consider adding `[skip ci]` to your commit messages to prevent
18
+ * triggering CI workflows on each key-value update.
19
+ */
10
20
  msg?: (key: string, value: string | null) => string;
21
+ /**
22
+ * Customize the commit message for batch operations (setMany, deleteMany, clear).
23
+ * @param operation - 'set' | 'delete' | 'clear'
24
+ * @param paths - array of file paths being modified
25
+ * @warning Consider adding `[skip ci]` to your commit messages to prevent
26
+ * triggering CI workflows on each key-value update.
27
+ */
28
+ batchMsg?: (operation: "set" | "delete" | "clear", paths: string[]) => string;
11
29
  /** clear() deletes every file in the repo and is disabled by default. Set to true to allow it. */
12
30
  enableClear?: boolean;
13
31
  /** Path prefix prepended to every key (e.g. 'data/'). Defaults to ''. */
14
32
  prefix?: string;
15
33
  /** Path suffix appended to every key (e.g. '.json'). Defaults to ''. */
16
34
  suffix?: string;
35
+ /** SHA cache map. Defaults to new Map(). Pass any keyv-like object with get/set. */
36
+ shaMap?: ShaMap;
17
37
  }
18
38
 
19
39
  /**
@@ -21,6 +41,10 @@ export interface KeyvGithubOptions {
21
41
  *
22
42
  * Each key is a file path in the repo; the file content is the value.
23
43
  * Example: new KeyvGithub("https://github.com/owner/repo/tree/main", { client })
44
+ *
45
+ * @warning **Keys are validated but NOT sanitized.** You must ensure keys are valid
46
+ * GitHub file paths before calling any method. Invalid keys throw an error.
47
+ * Requirements: non-empty, no leading/trailing `/`, no `//`, no `.`/`..` segments, no null bytes.
24
48
  */
25
49
  export default class KeyvGithub
26
50
  extends EventEmitter
@@ -38,9 +62,15 @@ export default class KeyvGithub
38
62
  }
39
63
  rest: Octokit["rest"];
40
64
  private msg: (key: string, value: string | null) => string;
65
+ private batchMsg: (
66
+ operation: "set" | "delete" | "clear",
67
+ paths: string[],
68
+ ) => string;
41
69
  readonly enableClear: boolean;
42
70
  readonly prefix: string;
43
71
  readonly suffix: string;
72
+ /** SHA cache: key → sha (string), null (file doesn't exist), undefined (unknown). */
73
+ readonly shaMap: ShaMap;
44
74
 
45
75
  constructor(url: string, options: Omit<KeyvGithubOptions, "url"> = {}) {
46
76
  super();
@@ -60,10 +90,19 @@ export default class KeyvGithub
60
90
  : (options.client ?? new Octokit().rest);
61
91
  this.msg =
62
92
  options.msg ??
63
- ((key, value) => (value === null ? `delete ${key}` : `update ${key}`));
93
+ ((key, value) =>
94
+ value === null ? `delete ${key} [skip ci]` : `update ${key} [skip ci]`);
95
+ this.batchMsg =
96
+ options.batchMsg ??
97
+ ((op, paths) => {
98
+ const n = paths.length;
99
+ if (op === "clear") return `clear: remove ${n} files [skip ci]`;
100
+ return `batch ${op} ${n} files [skip ci]`;
101
+ });
64
102
  this.enableClear = options.enableClear ?? false;
65
103
  this.prefix = options.prefix ?? "";
66
104
  this.suffix = options.suffix ?? "";
105
+ this.shaMap = options.shaMap ?? new Map<string, string | null>();
67
106
  }
68
107
 
69
108
  /** Converts a user key to the GitHub file path. */
@@ -106,20 +145,28 @@ export default class KeyvGithub
106
145
  }
107
146
 
108
147
  async get<Value>(key: string): Promise<StoredData<Value> | undefined> {
109
- this.validatePath(this.toPath(key));
148
+ const path = this.toPath(key);
149
+ this.validatePath(path);
110
150
  try {
111
151
  const { data } = await this.rest.repos.getContent({
112
152
  owner: this.owner,
113
153
  repo: this.repo,
114
- path: this.toPath(key),
154
+ path,
115
155
  ref: this.ref,
116
156
  });
117
- if (Array.isArray(data) || data.type !== "file") return undefined;
157
+ if (Array.isArray(data) || data.type !== "file") {
158
+ this.shaMap.set(path, null);
159
+ return undefined;
160
+ }
161
+ this.shaMap.set(path, data.sha);
118
162
  return Buffer.from(data.content, "base64").toString(
119
163
  "utf-8",
120
164
  ) as StoredData<Value>;
121
165
  } catch (e: unknown) {
122
- if (KeyvGithub.isHttpError(e) && e.status === 404) return undefined;
166
+ if (KeyvGithub.isHttpError(e) && e.status === 404) {
167
+ this.shaMap.set(path, null);
168
+ return undefined;
169
+ }
123
170
  throw e;
124
171
  }
125
172
  }
@@ -137,22 +184,19 @@ export default class KeyvGithub
137
184
  "Use new Keyv(store) which serializes values automatically.",
138
185
  );
139
186
  }
140
- this.validatePath(this.toPath(key));
141
187
  const path = this.toPath(key);
142
- let sha: string | undefined;
143
- try {
144
- const { data } = await this.rest.repos.getContent({
145
- owner: this.owner,
146
- repo: this.repo,
147
- path,
148
- ref: this.ref,
149
- });
150
- if (!Array.isArray(data) && data.type === "file") sha = data.sha;
151
- } catch (e: unknown) {
152
- if (!KeyvGithub.isHttpError(e) || e.status !== 404) throw e;
188
+ this.validatePath(path);
189
+
190
+ // Check shaMap first; if unknown (undefined), fetch to populate it
191
+ let cachedSha = this.shaMap.get(path);
192
+ if (cachedSha === undefined) {
193
+ await this.get(key); // populates shaMap
194
+ cachedSha = this.shaMap.get(path);
153
195
  }
196
+ // cachedSha is now string (existing file) or null (doesn't exist)
197
+ const sha = cachedSha ?? undefined;
154
198
 
155
- await this.rest.repos.createOrUpdateFileContents({
199
+ const { data } = await this.rest.repos.createOrUpdateFileContents({
156
200
  owner: this.owner,
157
201
  repo: this.repo,
158
202
  path,
@@ -161,46 +205,65 @@ export default class KeyvGithub
161
205
  sha,
162
206
  branch: this.ref,
163
207
  });
208
+ // Update shaMap with new sha from response
209
+ this.shaMap.set(path, data.content?.sha ?? null);
164
210
  }
165
211
 
166
212
  async delete(key: string): Promise<boolean> {
167
- this.validatePath(this.toPath(key));
168
213
  const path = this.toPath(key);
214
+ this.validatePath(path);
215
+
216
+ // Check shaMap first; if unknown (undefined), fetch to populate it
217
+ let cachedSha = this.shaMap.get(path);
218
+ if (cachedSha === undefined) {
219
+ await this.get(key); // populates shaMap
220
+ cachedSha = this.shaMap.get(path);
221
+ }
222
+ // If null or still undefined, file doesn't exist
223
+ if (!cachedSha) return false;
224
+
225
+ const sha = cachedSha; // narrow to string for TypeScript
169
226
  try {
170
- const { data } = await this.rest.repos.getContent({
171
- owner: this.owner,
172
- repo: this.repo,
173
- path,
174
- ref: this.ref,
175
- });
176
- if (Array.isArray(data) || data.type !== "file") return false;
177
227
  await this.rest.repos.deleteFile({
178
228
  owner: this.owner,
179
229
  repo: this.repo,
180
230
  path,
181
231
  message: this.msg(path, null),
182
- sha: data.sha,
232
+ sha,
183
233
  branch: this.ref,
184
234
  });
235
+ this.shaMap.set(path, null);
185
236
  return true;
186
237
  } catch (e: unknown) {
187
- if (KeyvGithub.isHttpError(e) && e.status === 404) return false;
238
+ if (KeyvGithub.isHttpError(e) && e.status === 404) {
239
+ this.shaMap.set(path, null);
240
+ return false;
241
+ }
188
242
  throw e;
189
243
  }
190
244
  }
191
245
 
192
246
  async has(key: string): Promise<boolean> {
193
- this.validatePath(this.toPath(key));
247
+ const path = this.toPath(key);
248
+ this.validatePath(path);
194
249
  try {
195
250
  const { data } = await this.rest.repos.getContent({
196
251
  owner: this.owner,
197
252
  repo: this.repo,
198
- path: this.toPath(key),
253
+ path,
199
254
  ref: this.ref,
200
255
  });
201
- return !Array.isArray(data) && data.type === "file";
256
+ if (Array.isArray(data) || data.type !== "file") {
257
+ this.shaMap.set(path, null);
258
+ return false;
259
+ }
260
+ this.shaMap.set(path, data.sha);
261
+ return true;
202
262
  } catch (e: unknown) {
203
- if (KeyvGithub.isHttpError(e) && e.status === 404) return false;
263
+ if (KeyvGithub.isHttpError(e) && e.status === 404) {
264
+ this.shaMap.set(path, null);
265
+ return false;
266
+ }
204
267
  throw e;
205
268
  }
206
269
  }
@@ -284,10 +347,11 @@ export default class KeyvGithub
284
347
  this.toPath(key),
285
348
  String(value),
286
349
  ]);
350
+ const paths = entries.map(([p]) => p);
287
351
  const message =
288
352
  entries.length === 1
289
353
  ? this.msg(entries[0]![0], entries[0]![1])
290
- : `batch update ${entries.length} files`;
354
+ : this.batchMsg("set", paths);
291
355
  await this._batchCommit({ set: entries, message });
292
356
  }
293
357
 
@@ -326,7 +390,7 @@ export default class KeyvGithub
326
390
  const message =
327
391
  toDelete.length === 1
328
392
  ? this.msg(toDelete[0]!, null)
329
- : `batch delete ${toDelete.length} files`;
393
+ : this.batchMsg("delete", toDelete);
330
394
  await this._batchCommit({ delete: toDelete, message });
331
395
  return true;
332
396
  }
@@ -362,7 +426,7 @@ export default class KeyvGithub
362
426
  if (allPaths.length > 0) {
363
427
  await this._batchCommit({
364
428
  delete: allPaths,
365
- message: `clear: remove ${allPaths.length} files`,
429
+ message: this.batchMsg("clear", allPaths),
366
430
  });
367
431
  }
368
432
  }