pilothub 0.0.1 → 0.0.2

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.
Files changed (388) hide show
  1. package/LICENSE +1 -0
  2. package/README.md +36 -129
  3. package/dist/browserAuth.d.ts +20 -0
  4. package/dist/browserAuth.js +156 -0
  5. package/dist/browserAuth.js.map +1 -0
  6. package/dist/browserAuth.test.d.ts +1 -0
  7. package/dist/browserAuth.test.js +83 -0
  8. package/dist/browserAuth.test.js.map +1 -0
  9. package/dist/cli/buildInfo.d.ts +3 -0
  10. package/dist/cli/buildInfo.js +103 -0
  11. package/dist/cli/buildInfo.js.map +1 -0
  12. package/dist/cli/commands/auth.d.ts +9 -0
  13. package/dist/cli/commands/auth.js +75 -0
  14. package/dist/cli/commands/auth.js.map +1 -0
  15. package/dist/cli/commands/delete.d.ts +11 -0
  16. package/dist/cli/commands/delete.js +67 -0
  17. package/dist/cli/commands/delete.js.map +1 -0
  18. package/dist/cli/commands/delete.test.d.ts +1 -0
  19. package/dist/cli/commands/delete.test.js +52 -0
  20. package/dist/cli/commands/delete.test.js.map +1 -0
  21. package/dist/cli/commands/publish.d.ts +9 -0
  22. package/dist/cli/commands/publish.js +87 -0
  23. package/dist/cli/commands/publish.js.map +1 -0
  24. package/dist/cli/commands/publish.test.d.ts +1 -0
  25. package/dist/cli/commands/publish.test.js +104 -0
  26. package/dist/cli/commands/publish.test.js.map +1 -0
  27. package/dist/cli/commands/skills.d.ts +23 -0
  28. package/dist/cli/commands/skills.js +298 -0
  29. package/dist/cli/commands/skills.js.map +1 -0
  30. package/dist/cli/commands/skills.test.d.ts +1 -0
  31. package/dist/cli/commands/skills.test.js +156 -0
  32. package/dist/cli/commands/skills.test.js.map +1 -0
  33. package/dist/cli/commands/star.d.ts +8 -0
  34. package/dist/cli/commands/star.js +38 -0
  35. package/dist/cli/commands/star.js.map +1 -0
  36. package/dist/cli/commands/sync.d.ts +3 -0
  37. package/dist/cli/commands/sync.js +160 -0
  38. package/dist/cli/commands/sync.js.map +1 -0
  39. package/dist/cli/commands/sync.test.d.ts +1 -0
  40. package/dist/cli/commands/sync.test.js +277 -0
  41. package/dist/cli/commands/sync.test.js.map +1 -0
  42. package/dist/cli/commands/syncHelpers.d.ts +76 -0
  43. package/dist/cli/commands/syncHelpers.js +349 -0
  44. package/dist/cli/commands/syncHelpers.js.map +1 -0
  45. package/dist/cli/commands/syncHelpers.test.d.ts +1 -0
  46. package/dist/cli/commands/syncHelpers.test.js +22 -0
  47. package/dist/cli/commands/syncHelpers.test.js.map +1 -0
  48. package/dist/cli/commands/syncTypes.d.ts +24 -0
  49. package/dist/cli/commands/syncTypes.js +2 -0
  50. package/dist/cli/commands/syncTypes.js.map +1 -0
  51. package/dist/cli/commands/unstar.d.ts +8 -0
  52. package/dist/cli/commands/unstar.js +38 -0
  53. package/dist/cli/commands/unstar.js.map +1 -0
  54. package/dist/cli/helpStyle.d.ts +13 -0
  55. package/dist/cli/helpStyle.js +38 -0
  56. package/dist/cli/helpStyle.js.map +1 -0
  57. package/dist/cli/pilotbotConfig.d.ts +6 -0
  58. package/dist/cli/pilotbotConfig.js +110 -0
  59. package/dist/cli/pilotbotConfig.js.map +1 -0
  60. package/dist/cli/pilotbotConfig.test.d.ts +1 -0
  61. package/dist/cli/pilotbotConfig.test.js +133 -0
  62. package/dist/cli/pilotbotConfig.test.js.map +1 -0
  63. package/dist/cli/registry.d.ts +7 -0
  64. package/dist/cli/registry.js +42 -0
  65. package/dist/cli/registry.js.map +1 -0
  66. package/dist/cli/registry.test.d.ts +1 -0
  67. package/dist/cli/registry.test.js +48 -0
  68. package/dist/cli/registry.test.js.map +1 -0
  69. package/dist/cli/scanSkills.d.ts +7 -0
  70. package/dist/cli/scanSkills.js +75 -0
  71. package/dist/cli/scanSkills.js.map +1 -0
  72. package/dist/cli/scanSkills.test.d.ts +1 -0
  73. package/dist/cli/scanSkills.test.js +60 -0
  74. package/dist/cli/scanSkills.test.js.map +1 -0
  75. package/dist/cli/slug.d.ts +2 -0
  76. package/dist/cli/slug.js +16 -0
  77. package/dist/cli/slug.js.map +1 -0
  78. package/dist/cli/types.d.ts +15 -0
  79. package/dist/cli/types.js +2 -0
  80. package/dist/cli/types.js.map +1 -0
  81. package/dist/cli/ui.d.ts +7 -0
  82. package/dist/cli/ui.js +72 -0
  83. package/dist/cli/ui.js.map +1 -0
  84. package/dist/cli.d.ts +2 -0
  85. package/dist/cli.js +268 -0
  86. package/dist/cli.js.map +1 -0
  87. package/dist/config.d.ts +4 -0
  88. package/dist/config.js +38 -0
  89. package/dist/config.js.map +1 -0
  90. package/dist/discovery.d.ts +5 -0
  91. package/dist/discovery.js +21 -0
  92. package/dist/discovery.js.map +1 -0
  93. package/dist/discovery.test.d.ts +1 -0
  94. package/dist/discovery.test.js +46 -0
  95. package/dist/discovery.test.js.map +1 -0
  96. package/dist/http.d.ts +32 -0
  97. package/dist/http.js +261 -0
  98. package/dist/http.js.map +1 -0
  99. package/dist/http.test.d.ts +1 -0
  100. package/dist/http.test.js +135 -0
  101. package/dist/http.test.js.map +1 -0
  102. package/dist/schema/ark.js.map +1 -0
  103. package/dist/schema/index.js.map +1 -0
  104. package/dist/schema/routes.js.map +1 -0
  105. package/{packages/schema/dist → dist/schema}/schemas.d.ts +0 -39
  106. package/{packages/schema/dist → dist/schema}/schemas.js +0 -22
  107. package/dist/schema/schemas.js.map +1 -0
  108. package/dist/schema/textFiles.js.map +1 -0
  109. package/dist/schema/textFiles.test.d.ts +1 -0
  110. package/dist/schema/textFiles.test.js +20 -0
  111. package/dist/schema/textFiles.test.js.map +1 -0
  112. package/dist/skills.d.ts +43 -0
  113. package/dist/skills.js +163 -0
  114. package/dist/skills.js.map +1 -0
  115. package/dist/skills.test.d.ts +1 -0
  116. package/dist/skills.test.js +144 -0
  117. package/dist/skills.test.js.map +1 -0
  118. package/dist/types.d.ts +7 -0
  119. package/dist/types.js +2 -0
  120. package/dist/types.js.map +1 -0
  121. package/package.json +27 -70
  122. package/.env.local.example +0 -19
  123. package/.github/workflows/ci.yml +0 -40
  124. package/.oxlintrc.json +0 -3
  125. package/AGENTS.md +0 -45
  126. package/CHANGELOG.md +0 -138
  127. package/DEPRECATIONS.md +0 -7
  128. package/biome.json +0 -41
  129. package/convex/_generated/api.d.ts +0 -153
  130. package/convex/_generated/api.js +0 -23
  131. package/convex/_generated/dataModel.d.ts +0 -60
  132. package/convex/_generated/server.d.ts +0 -143
  133. package/convex/_generated/server.js +0 -93
  134. package/convex/auth.config.ts +0 -8
  135. package/convex/auth.ts +0 -19
  136. package/convex/comments.ts +0 -88
  137. package/convex/crons.ts +0 -34
  138. package/convex/devSeed.ts +0 -459
  139. package/convex/devSeedExtra.ts +0 -541
  140. package/convex/downloads.ts +0 -78
  141. package/convex/githubBackups.ts +0 -170
  142. package/convex/githubBackupsNode.ts +0 -183
  143. package/convex/githubImport.ts +0 -317
  144. package/convex/githubSoulBackups.ts +0 -170
  145. package/convex/githubSoulBackupsNode.ts +0 -186
  146. package/convex/http.ts +0 -194
  147. package/convex/httpApi.handlers.test.ts +0 -488
  148. package/convex/httpApi.test.ts +0 -70
  149. package/convex/httpApi.ts +0 -305
  150. package/convex/httpApiV1.handlers.test.ts +0 -584
  151. package/convex/httpApiV1.ts +0 -1172
  152. package/convex/leaderboards.ts +0 -39
  153. package/convex/lib/access.ts +0 -36
  154. package/convex/lib/apiTokenAuth.ts +0 -36
  155. package/convex/lib/badges.ts +0 -50
  156. package/convex/lib/changelog.test.ts +0 -34
  157. package/convex/lib/changelog.ts +0 -278
  158. package/convex/lib/embeddings.ts +0 -38
  159. package/convex/lib/githubBackup.ts +0 -443
  160. package/convex/lib/githubImport.test.ts +0 -247
  161. package/convex/lib/githubImport.ts +0 -425
  162. package/convex/lib/githubSoulBackup.ts +0 -443
  163. package/convex/lib/leaderboards.ts +0 -103
  164. package/convex/lib/moderation.ts +0 -42
  165. package/convex/lib/public.ts +0 -89
  166. package/convex/lib/searchText.test.ts +0 -46
  167. package/convex/lib/searchText.ts +0 -27
  168. package/convex/lib/skillBackfill.test.ts +0 -34
  169. package/convex/lib/skillBackfill.ts +0 -67
  170. package/convex/lib/skillPublish.test.ts +0 -28
  171. package/convex/lib/skillPublish.ts +0 -284
  172. package/convex/lib/skillStats.ts +0 -80
  173. package/convex/lib/skills.test.ts +0 -197
  174. package/convex/lib/skills.ts +0 -273
  175. package/convex/lib/soulChangelog.ts +0 -273
  176. package/convex/lib/soulPublish.ts +0 -236
  177. package/convex/lib/tokens.test.ts +0 -33
  178. package/convex/lib/tokens.ts +0 -51
  179. package/convex/lib/webhooks.test.ts +0 -91
  180. package/convex/lib/webhooks.ts +0 -112
  181. package/convex/maintenance.test.ts +0 -270
  182. package/convex/maintenance.ts +0 -840
  183. package/convex/rateLimits.ts +0 -50
  184. package/convex/schema.ts +0 -472
  185. package/convex/search.test.ts +0 -12
  186. package/convex/search.ts +0 -254
  187. package/convex/seed.test.ts +0 -37
  188. package/convex/seed.ts +0 -254
  189. package/convex/seedSouls.ts +0 -111
  190. package/convex/skillStatEvents.ts +0 -568
  191. package/convex/skills.ts +0 -1606
  192. package/convex/soulComments.ts +0 -88
  193. package/convex/soulDownloads.ts +0 -14
  194. package/convex/soulStars.ts +0 -71
  195. package/convex/souls.ts +0 -570
  196. package/convex/stars.ts +0 -108
  197. package/convex/statsMaintenance.ts +0 -205
  198. package/convex/telemetry.ts +0 -434
  199. package/convex/tokens.ts +0 -88
  200. package/convex/tsconfig.json +0 -7
  201. package/convex/uploads.ts +0 -20
  202. package/convex/users.ts +0 -122
  203. package/convex/webhooks.ts +0 -50
  204. package/convex.json +0 -3
  205. package/docs/README.md +0 -32
  206. package/docs/api.md +0 -51
  207. package/docs/architecture.md +0 -61
  208. package/docs/auth.md +0 -54
  209. package/docs/cli.md +0 -117
  210. package/docs/deploy.md +0 -78
  211. package/docs/diffing.md +0 -84
  212. package/docs/github-import.md +0 -171
  213. package/docs/http-api.md +0 -187
  214. package/docs/manual-testing.md +0 -64
  215. package/docs/mintlify.md +0 -43
  216. package/docs/quickstart.md +0 -120
  217. package/docs/skill-format.md +0 -58
  218. package/docs/soul-format.md +0 -37
  219. package/docs/spec.md +0 -177
  220. package/docs/telemetry.md +0 -91
  221. package/docs/troubleshooting.md +0 -49
  222. package/docs/webhook.md +0 -51
  223. package/e2e/menu-smoke.pw.test.ts +0 -49
  224. package/e2e/pilothub.e2e.test.ts +0 -494
  225. package/e2e/search-exact.pw.test.ts +0 -97
  226. package/packages/pilothub/LICENSE +0 -22
  227. package/packages/pilothub/README.md +0 -57
  228. package/packages/pilothub/package.json +0 -41
  229. package/packages/pilothub/src/browserAuth.test.ts +0 -96
  230. package/packages/pilothub/src/browserAuth.ts +0 -174
  231. package/packages/pilothub/src/cli/buildInfo.ts +0 -94
  232. package/packages/pilothub/src/cli/commands/auth.ts +0 -97
  233. package/packages/pilothub/src/cli/commands/delete.test.ts +0 -73
  234. package/packages/pilothub/src/cli/commands/delete.ts +0 -83
  235. package/packages/pilothub/src/cli/commands/publish.test.ts +0 -122
  236. package/packages/pilothub/src/cli/commands/publish.ts +0 -108
  237. package/packages/pilothub/src/cli/commands/skills.test.ts +0 -191
  238. package/packages/pilothub/src/cli/commands/skills.ts +0 -380
  239. package/packages/pilothub/src/cli/commands/star.ts +0 -46
  240. package/packages/pilothub/src/cli/commands/sync.test.ts +0 -310
  241. package/packages/pilothub/src/cli/commands/sync.ts +0 -200
  242. package/packages/pilothub/src/cli/commands/syncHelpers.test.ts +0 -26
  243. package/packages/pilothub/src/cli/commands/syncHelpers.ts +0 -427
  244. package/packages/pilothub/src/cli/commands/syncTypes.ts +0 -27
  245. package/packages/pilothub/src/cli/commands/unstar.ts +0 -48
  246. package/packages/pilothub/src/cli/helpStyle.ts +0 -45
  247. package/packages/pilothub/src/cli/pilotbotConfig.test.ts +0 -159
  248. package/packages/pilothub/src/cli/pilotbotConfig.ts +0 -147
  249. package/packages/pilothub/src/cli/registry.test.ts +0 -63
  250. package/packages/pilothub/src/cli/registry.ts +0 -43
  251. package/packages/pilothub/src/cli/scanSkills.test.ts +0 -64
  252. package/packages/pilothub/src/cli/scanSkills.ts +0 -84
  253. package/packages/pilothub/src/cli/slug.ts +0 -16
  254. package/packages/pilothub/src/cli/types.ts +0 -12
  255. package/packages/pilothub/src/cli/ui.ts +0 -75
  256. package/packages/pilothub/src/cli.ts +0 -311
  257. package/packages/pilothub/src/config.ts +0 -36
  258. package/packages/pilothub/src/discovery.test.ts +0 -75
  259. package/packages/pilothub/src/discovery.ts +0 -19
  260. package/packages/pilothub/src/http.test.ts +0 -156
  261. package/packages/pilothub/src/http.ts +0 -301
  262. package/packages/pilothub/src/schema/ark.ts +0 -29
  263. package/packages/pilothub/src/schema/index.ts +0 -5
  264. package/packages/pilothub/src/schema/routes.ts +0 -22
  265. package/packages/pilothub/src/schema/schemas.ts +0 -260
  266. package/packages/pilothub/src/schema/textFiles.test.ts +0 -23
  267. package/packages/pilothub/src/schema/textFiles.ts +0 -66
  268. package/packages/pilothub/src/skills.test.ts +0 -191
  269. package/packages/pilothub/src/skills.ts +0 -172
  270. package/packages/pilothub/src/types.ts +0 -10
  271. package/packages/pilothub/tsconfig.json +0 -14
  272. package/packages/schema/README.md +0 -3
  273. package/packages/schema/dist/ark.js.map +0 -1
  274. package/packages/schema/dist/index.js.map +0 -1
  275. package/packages/schema/dist/routes.js.map +0 -1
  276. package/packages/schema/dist/schemas.js.map +0 -1
  277. package/packages/schema/dist/textFiles.js.map +0 -1
  278. package/packages/schema/package.json +0 -26
  279. package/packages/schema/src/ark.ts +0 -29
  280. package/packages/schema/src/index.ts +0 -5
  281. package/packages/schema/src/routes.ts +0 -22
  282. package/packages/schema/src/schemas.test.ts +0 -123
  283. package/packages/schema/src/schemas.ts +0 -287
  284. package/packages/schema/src/textFiles.test.ts +0 -23
  285. package/packages/schema/src/textFiles.ts +0 -66
  286. package/packages/schema/tsconfig.json +0 -15
  287. package/pilothub +0 -46
  288. package/playwright.config.ts +0 -33
  289. package/public/.well-known/pilothub.json +0 -6
  290. package/public/api/v1/openapi.json +0 -379
  291. package/public/favicon.ico +0 -0
  292. package/public/logo192.png +0 -0
  293. package/public/logo512.png +0 -0
  294. package/public/manifest.json +0 -25
  295. package/public/og.png +0 -0
  296. package/public/og.svg +0 -98
  297. package/public/pilot-logo.png +0 -0
  298. package/public/pilot-mark.png +0 -0
  299. package/public/robots.txt +0 -3
  300. package/public/tanstack-circle-logo.png +0 -0
  301. package/public/tanstack-word-logo-white.svg +0 -1
  302. package/scripts/check-peer-deps.ts +0 -56
  303. package/scripts/docs-list.ts +0 -148
  304. package/scripts/run-playwright-local.sh +0 -14
  305. package/server/og/fetchSkillOgMeta.ts +0 -27
  306. package/server/og/fetchSoulOgMeta.ts +0 -27
  307. package/server/og/ogAssets.ts +0 -80
  308. package/server/og/skillOgSvg.test.ts +0 -59
  309. package/server/og/skillOgSvg.ts +0 -258
  310. package/server/og/soulOgSvg.ts +0 -209
  311. package/server/routes/og/skill.png.ts +0 -103
  312. package/server/routes/og/soul.png.ts +0 -111
  313. package/src/__tests__/skill-detail-page.test.tsx +0 -86
  314. package/src/__tests__/skills-index.test.tsx +0 -145
  315. package/src/__tests__/upload.route.test.tsx +0 -228
  316. package/src/components/AppProviders.tsx +0 -19
  317. package/src/components/ClientOnly.tsx +0 -18
  318. package/src/components/Footer.tsx +0 -29
  319. package/src/components/Header.tsx +0 -295
  320. package/src/components/InstallSwitcher.tsx +0 -53
  321. package/src/components/SkillCard.tsx +0 -36
  322. package/src/components/SkillDetailPage.tsx +0 -817
  323. package/src/components/SkillDiffCard.tsx +0 -485
  324. package/src/components/SoulCard.tsx +0 -19
  325. package/src/components/SoulDetailPage.tsx +0 -263
  326. package/src/components/UserBootstrap.tsx +0 -18
  327. package/src/components/ui/dropdown-menu.tsx +0 -67
  328. package/src/components/ui/toggle-group.tsx +0 -35
  329. package/src/convex/client.ts +0 -3
  330. package/src/lib/badges.ts +0 -29
  331. package/src/lib/diffing.test.ts +0 -163
  332. package/src/lib/diffing.ts +0 -106
  333. package/src/lib/gravatar.test.ts +0 -9
  334. package/src/lib/gravatar.ts +0 -158
  335. package/src/lib/og.test.ts +0 -142
  336. package/src/lib/og.ts +0 -156
  337. package/src/lib/publicUser.ts +0 -39
  338. package/src/lib/roles.ts +0 -19
  339. package/src/lib/site.test.ts +0 -130
  340. package/src/lib/site.ts +0 -84
  341. package/src/lib/theme-transition.test.ts +0 -134
  342. package/src/lib/theme-transition.ts +0 -134
  343. package/src/lib/theme.test.tsx +0 -88
  344. package/src/lib/theme.ts +0 -43
  345. package/src/lib/uploadFiles.jsdom.test.ts +0 -33
  346. package/src/lib/uploadFiles.test.ts +0 -123
  347. package/src/lib/uploadFiles.ts +0 -245
  348. package/src/lib/uploadUtils.test.ts +0 -78
  349. package/src/lib/uploadUtils.ts +0 -93
  350. package/src/lib/useAuthStatus.ts +0 -12
  351. package/src/lib/utils.test.ts +0 -9
  352. package/src/lib/utils.ts +0 -6
  353. package/src/logo.svg +0 -12
  354. package/src/routeTree.gen.ts +0 -345
  355. package/src/router.tsx +0 -17
  356. package/src/routes/$owner/$slug.tsx +0 -55
  357. package/src/routes/__root.tsx +0 -136
  358. package/src/routes/admin.tsx +0 -11
  359. package/src/routes/cli/auth.tsx +0 -168
  360. package/src/routes/dashboard.tsx +0 -97
  361. package/src/routes/import.tsx +0 -415
  362. package/src/routes/index.tsx +0 -252
  363. package/src/routes/management.tsx +0 -529
  364. package/src/routes/settings.tsx +0 -203
  365. package/src/routes/skills/index.tsx +0 -422
  366. package/src/routes/souls/$slug.tsx +0 -55
  367. package/src/routes/souls/index.tsx +0 -243
  368. package/src/routes/stars.tsx +0 -68
  369. package/src/routes/u/$handle.tsx +0 -307
  370. package/src/routes/upload/utils.ts +0 -81
  371. package/src/routes/upload.tsx +0 -499
  372. package/src/styles.css +0 -2718
  373. package/tsconfig.json +0 -24
  374. package/tsconfig.oxlint.json +0 -16
  375. package/vercel.json +0 -8
  376. package/vite.config.ts +0 -48
  377. package/vitest.config.ts +0 -47
  378. package/vitest.e2e.config.ts +0 -11
  379. package/vitest.setup.ts +0 -1
  380. /package/{packages/pilothub/bin → bin}/pilothub.js +0 -0
  381. /package/{packages/schema/dist → dist/schema}/ark.d.ts +0 -0
  382. /package/{packages/schema/dist → dist/schema}/ark.js +0 -0
  383. /package/{packages/schema/dist → dist/schema}/index.d.ts +0 -0
  384. /package/{packages/schema/dist → dist/schema}/index.js +0 -0
  385. /package/{packages/schema/dist → dist/schema}/routes.d.ts +0 -0
  386. /package/{packages/schema/dist → dist/schema}/routes.js +0 -0
  387. /package/{packages/schema/dist → dist/schema}/textFiles.d.ts +0 -0
  388. /package/{packages/schema/dist → dist/schema}/textFiles.js +0 -0
@@ -1,443 +0,0 @@
1
- 'use node'
2
-
3
- import { createPrivateKey, createSign } from 'node:crypto'
4
- import type { Id } from '../_generated/dataModel'
5
- import type { ActionCtx } from '../_generated/server'
6
-
7
- const GITHUB_API = 'https://api.github.com'
8
- const DEFAULT_REPO = 'pilotbot/skills'
9
- const DEFAULT_ROOT = 'skills'
10
- const META_FILENAME = '_meta.json'
11
- const USER_AGENT = 'pilothub/skills-backup'
12
-
13
- type BackupFile = {
14
- path: string
15
- size: number
16
- storageId: Id<'_storage'>
17
- sha256: string
18
- contentType?: string
19
- }
20
-
21
- type BackupParams = {
22
- slug: string
23
- version: string
24
- displayName: string
25
- ownerHandle: string
26
- files: BackupFile[]
27
- publishedAt: number
28
- }
29
-
30
- type RepoInfo = {
31
- default_branch?: string
32
- }
33
-
34
- type GitRef = {
35
- object: { sha: string }
36
- }
37
-
38
- type GitCommit = {
39
- sha: string
40
- tree: { sha: string }
41
- }
42
-
43
- type GitTreeEntry = {
44
- path?: string
45
- type?: string
46
- }
47
-
48
- type GitTree = {
49
- tree?: GitTreeEntry[]
50
- }
51
-
52
- type MetaFile = {
53
- owner: string
54
- slug: string
55
- displayName: string
56
- latest: {
57
- version: string
58
- publishedAt: number
59
- commit: string | null
60
- }
61
- history: Array<{
62
- version: string
63
- publishedAt: number
64
- commit: string
65
- }>
66
- }
67
-
68
- export type GitHubBackupContext = {
69
- token: string
70
- repo: string
71
- repoOwner: string
72
- repoName: string
73
- branch: string
74
- root: string
75
- }
76
-
77
- export function isGitHubBackupConfigured() {
78
- return Boolean(
79
- process.env.GITHUB_APP_ID &&
80
- process.env.GITHUB_APP_PRIVATE_KEY &&
81
- process.env.GITHUB_APP_INSTALLATION_ID,
82
- )
83
- }
84
-
85
- export async function getGitHubBackupContext(): Promise<GitHubBackupContext> {
86
- const repo = process.env.GITHUB_SKILLS_REPO ?? DEFAULT_REPO
87
- const root = process.env.GITHUB_SKILLS_ROOT ?? DEFAULT_ROOT
88
- const [repoOwner, repoName] = parseRepo(repo)
89
- const token = await createInstallationToken()
90
- const repoInfo = await githubGet<RepoInfo>(token, `/repos/${repoOwner}/${repoName}`)
91
- const branch = repoInfo.default_branch ?? 'main'
92
-
93
- return { token, repo, repoOwner, repoName, branch, root }
94
- }
95
-
96
- export async function fetchGitHubSkillMeta(
97
- context: GitHubBackupContext,
98
- ownerHandle: string,
99
- slug: string,
100
- ): Promise<MetaFile | null> {
101
- const skillRoot = buildSkillRoot(context.root, ownerHandle, slug)
102
- return fetchMetaFile(
103
- context.token,
104
- context.repoOwner,
105
- context.repoName,
106
- `${skillRoot}/${META_FILENAME}`,
107
- context.branch,
108
- )
109
- }
110
-
111
- export async function backupSkillToGitHub(
112
- ctx: ActionCtx,
113
- params: BackupParams,
114
- context?: GitHubBackupContext,
115
- ) {
116
- if (!isGitHubBackupConfigured()) return
117
-
118
- const resolved = context ?? (await getGitHubBackupContext())
119
- const skillRoot = buildSkillRoot(resolved.root, params.ownerHandle, params.slug)
120
- const ref = await githubGet<GitRef>(
121
- resolved.token,
122
- `/repos/${resolved.repoOwner}/${resolved.repoName}/git/ref/heads/${resolved.branch}`,
123
- )
124
- const baseCommitSha = ref.object.sha
125
- const baseCommit = await githubGet<GitCommit>(
126
- resolved.token,
127
- `/repos/${resolved.repoOwner}/${resolved.repoName}/git/commits/${baseCommitSha}`,
128
- )
129
- const baseTreeSha = baseCommit.tree.sha
130
- const existingTree = await githubGet<GitTree>(
131
- resolved.token,
132
- `/repos/${resolved.repoOwner}/${resolved.repoName}/git/trees/${baseTreeSha}?recursive=1`,
133
- )
134
-
135
- const prefix = `${skillRoot}/`
136
- const existingPaths = new Set(
137
- (existingTree.tree ?? [])
138
- .filter((entry) => entry.type === 'blob' && entry.path?.startsWith(prefix))
139
- .map((entry) => entry.path ?? ''),
140
- )
141
-
142
- const newPaths = new Set<string>()
143
- const treeEntries: Array<{
144
- path: string
145
- mode: '100644'
146
- type: 'blob'
147
- sha: string | null
148
- }> = []
149
-
150
- for (const file of params.files) {
151
- const content = await fetchStorageBase64(ctx, file.storageId)
152
- const blobSha = await createBlob(resolved.token, resolved.repoOwner, resolved.repoName, content)
153
- const path = `${skillRoot}/${file.path}`
154
- newPaths.add(path)
155
- treeEntries.push({ path, mode: '100644', type: 'blob', sha: blobSha })
156
- }
157
-
158
- const existingMeta = await fetchMetaFile(
159
- resolved.token,
160
- resolved.repoOwner,
161
- resolved.repoName,
162
- `${skillRoot}/${META_FILENAME}`,
163
- resolved.branch,
164
- )
165
- const metaPath = `${skillRoot}/${META_FILENAME}`
166
- const metaDraft = buildMetaFile(params, existingMeta, resolved.repo, baseCommitSha, null)
167
- const metaDraftContent = `${JSON.stringify(metaDraft, null, 2)}\n`
168
- const metaDraftSha = await createBlob(
169
- resolved.token,
170
- resolved.repoOwner,
171
- resolved.repoName,
172
- toBase64(metaDraftContent),
173
- )
174
- newPaths.add(metaPath)
175
- treeEntries.push({ path: metaPath, mode: '100644', type: 'blob', sha: metaDraftSha })
176
-
177
- for (const path of existingPaths) {
178
- if (newPaths.has(path)) continue
179
- treeEntries.push({ path, mode: '100644', type: 'blob', sha: null })
180
- }
181
-
182
- const newTree = await githubPost<{ sha: string }>(
183
- resolved.token,
184
- `/repos/${resolved.repoOwner}/${resolved.repoName}/git/trees`,
185
- {
186
- base_tree: baseTreeSha,
187
- tree: treeEntries,
188
- },
189
- )
190
-
191
- const commit = await githubPost<GitCommit>(
192
- resolved.token,
193
- `/repos/${resolved.repoOwner}/${resolved.repoName}/git/commits`,
194
- {
195
- message: `skill: ${params.slug} v${params.version}`,
196
- tree: newTree.sha,
197
- parents: [baseCommitSha],
198
- },
199
- )
200
-
201
- const metaFinal = buildMetaFile(params, existingMeta, resolved.repo, baseCommitSha, commit.sha)
202
- const metaFinalContent = `${JSON.stringify(metaFinal, null, 2)}\n`
203
- const metaFinalSha = await createBlob(
204
- resolved.token,
205
- resolved.repoOwner,
206
- resolved.repoName,
207
- toBase64(metaFinalContent),
208
- )
209
- const metaTree = await githubPost<{ sha: string }>(
210
- resolved.token,
211
- `/repos/${resolved.repoOwner}/${resolved.repoName}/git/trees`,
212
- {
213
- base_tree: commit.tree.sha,
214
- tree: [{ path: metaPath, mode: '100644', type: 'blob', sha: metaFinalSha }],
215
- },
216
- )
217
- const metaCommit = await githubPost<GitCommit>(
218
- resolved.token,
219
- `/repos/${resolved.repoOwner}/${resolved.repoName}/git/commits`,
220
- {
221
- message: `meta: ${params.slug} v${params.version}`,
222
- tree: metaTree.sha,
223
- parents: [commit.sha],
224
- },
225
- )
226
-
227
- await githubPatch(
228
- resolved.token,
229
- `/repos/${resolved.repoOwner}/${resolved.repoName}/git/refs/heads/${resolved.branch}`,
230
- {
231
- sha: metaCommit.sha,
232
- },
233
- )
234
- }
235
-
236
- function buildMetaFile(
237
- params: BackupParams,
238
- existing: MetaFile | null,
239
- repo: string,
240
- baseCommitSha: string,
241
- latestCommitSha: string | null,
242
- ): MetaFile {
243
- let history = [...(existing?.history ?? [])]
244
- if (existing?.latest?.version) {
245
- const previousCommit = existing.latest.commit ?? commitUrl(repo, baseCommitSha)
246
- const previous = {
247
- version: existing.latest.version,
248
- publishedAt: existing.latest.publishedAt,
249
- commit: previousCommit,
250
- }
251
- history = [previous, ...history.filter((entry) => entry.version !== previous.version)]
252
- }
253
-
254
- return {
255
- owner: normalizeOwner(params.ownerHandle),
256
- slug: params.slug,
257
- displayName: params.displayName,
258
- latest: {
259
- version: params.version,
260
- publishedAt: params.publishedAt,
261
- commit: latestCommitSha ? commitUrl(repo, latestCommitSha) : null,
262
- },
263
- history: history.slice(0, 200),
264
- }
265
- }
266
-
267
- async function fetchMetaFile(
268
- token: string,
269
- repoOwner: string,
270
- repoName: string,
271
- path: string,
272
- branch: string,
273
- ): Promise<MetaFile | null> {
274
- try {
275
- const response = await githubGet<{ content?: string }>(
276
- token,
277
- `/repos/${repoOwner}/${repoName}/contents/${encodePath(path)}?ref=${branch}`,
278
- )
279
- if (!response.content) return null
280
- const raw = fromBase64(response.content)
281
- return JSON.parse(raw) as MetaFile
282
- } catch (error) {
283
- if (isNotFoundError(error)) return null
284
- throw error
285
- }
286
- }
287
-
288
- async function fetchStorageBase64(ctx: ActionCtx, storageId: Id<'_storage'>) {
289
- const blob = await ctx.storage.get(storageId)
290
- if (!blob) throw new Error('File missing in storage')
291
- const buffer = Buffer.from(await blob.arrayBuffer())
292
- return buffer.toString('base64')
293
- }
294
-
295
- async function createInstallationToken() {
296
- const appId = process.env.GITHUB_APP_ID
297
- const installationId = process.env.GITHUB_APP_INSTALLATION_ID
298
- if (!appId || !installationId) {
299
- throw new Error('GitHub App credentials missing')
300
- }
301
- const jwt = createAppJwt(appId)
302
- const response = await fetch(`${GITHUB_API}/app/installations/${installationId}/access_tokens`, {
303
- method: 'POST',
304
- headers: buildHeaders(jwt, true),
305
- })
306
- if (!response.ok) {
307
- const message = await response.text()
308
- throw new Error(`GitHub App token failed: ${message}`)
309
- }
310
- const payload = (await response.json()) as { token?: string }
311
- if (!payload.token) throw new Error('GitHub App token missing')
312
- return payload.token
313
- }
314
-
315
- function createAppJwt(appId: string) {
316
- const privateKey = loadPrivateKey()
317
- const now = Math.floor(Date.now() / 1000)
318
- const header = { alg: 'RS256', typ: 'JWT' }
319
- const payload = { iat: now - 60, exp: now + 9 * 60, iss: appId }
320
- const encodedHeader = base64Url(JSON.stringify(header))
321
- const encodedPayload = base64Url(JSON.stringify(payload))
322
- const signingInput = `${encodedHeader}.${encodedPayload}`
323
- const sign = createSign('RSA-SHA256')
324
- sign.update(signingInput)
325
- sign.end()
326
- const signature = sign.sign(privateKey)
327
- return `${signingInput}.${base64Url(signature)}`
328
- }
329
-
330
- function loadPrivateKey() {
331
- const raw = process.env.GITHUB_APP_PRIVATE_KEY
332
- if (!raw) throw new Error('GITHUB_APP_PRIVATE_KEY is not configured')
333
- const normalized = raw.replace(/\\n/g, '\n')
334
- return createPrivateKey(normalized)
335
- }
336
-
337
- async function createBlob(token: string, repoOwner: string, repoName: string, content: string) {
338
- const result = await githubPost<{ sha: string }>(
339
- token,
340
- `/repos/${repoOwner}/${repoName}/git/blobs`,
341
- {
342
- content,
343
- encoding: 'base64',
344
- },
345
- )
346
- if (!result.sha) throw new Error('GitHub blob missing sha')
347
- return result.sha
348
- }
349
-
350
- async function githubGet<T>(token: string, path: string): Promise<T> {
351
- const response = await fetch(`${GITHUB_API}${path}`, {
352
- headers: buildHeaders(token),
353
- })
354
- if (!response.ok) {
355
- const message = await response.text()
356
- throw new Error(`GitHub GET ${path} failed: ${message}`)
357
- }
358
- return (await response.json()) as T
359
- }
360
-
361
- async function githubPost<T>(token: string, path: string, body: unknown): Promise<T> {
362
- const response = await fetch(`${GITHUB_API}${path}`, {
363
- method: 'POST',
364
- headers: buildHeaders(token),
365
- body: JSON.stringify(body),
366
- })
367
- if (!response.ok) {
368
- const message = await response.text()
369
- throw new Error(`GitHub POST ${path} failed: ${message}`)
370
- }
371
- return (await response.json()) as T
372
- }
373
-
374
- async function githubPatch(token: string, path: string, body: unknown) {
375
- const response = await fetch(`${GITHUB_API}${path}`, {
376
- method: 'PATCH',
377
- headers: buildHeaders(token),
378
- body: JSON.stringify(body),
379
- })
380
- if (!response.ok) {
381
- const message = await response.text()
382
- throw new Error(`GitHub PATCH ${path} failed: ${message}`)
383
- }
384
- }
385
-
386
- function buildHeaders(token: string, isAppJwt = false) {
387
- return {
388
- Authorization: `${isAppJwt ? 'Bearer' : 'token'} ${token}`,
389
- Accept: 'application/vnd.github+json',
390
- 'User-Agent': USER_AGENT,
391
- }
392
- }
393
-
394
- function parseRepo(repo: string) {
395
- const [owner, name] = repo.split('/')
396
- if (!owner || !name) throw new Error('GITHUB_SKILLS_REPO must be owner/repo')
397
- return [owner, name] as const
398
- }
399
-
400
- function normalizeOwner(value: string) {
401
- const normalized = value
402
- .trim()
403
- .toLowerCase()
404
- .replace(/[^a-z0-9-]/g, '-')
405
- .replace(/-+/g, '-')
406
- .replace(/^-+|-+$/g, '')
407
- return normalized || 'unknown'
408
- }
409
-
410
- function commitUrl(repo: string, sha: string) {
411
- return `https://github.com/${repo}/commit/${sha}`
412
- }
413
-
414
- function buildSkillRoot(root: string, ownerHandle: string, slug: string) {
415
- const ownerSegment = normalizeOwner(ownerHandle)
416
- return `${root}/${ownerSegment}/${slug}`
417
- }
418
-
419
- function encodePath(path: string) {
420
- return path
421
- .split('/')
422
- .map((segment) => encodeURIComponent(segment))
423
- .join('/')
424
- }
425
-
426
- function base64Url(value: string | Buffer) {
427
- const buffer = typeof value === 'string' ? Buffer.from(value) : value
428
- return buffer.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '')
429
- }
430
-
431
- function toBase64(value: string) {
432
- return Buffer.from(value).toString('base64')
433
- }
434
-
435
- function fromBase64(value: string) {
436
- return Buffer.from(value, 'base64').toString('utf8')
437
- }
438
-
439
- function isNotFoundError(error: unknown) {
440
- return (
441
- error instanceof Error && (error.message.includes('404') || error.message.includes('Not Found'))
442
- )
443
- }
@@ -1,247 +0,0 @@
1
- /* @vitest-environment node */
2
-
3
- import { unzipSync } from 'fflate'
4
- import { describe, expect, it } from 'vitest'
5
- import {
6
- buildGitHubZipForTests,
7
- computeDefaultSelectedPaths,
8
- detectGitHubImportCandidates,
9
- extractMarkdownRelativeTargets,
10
- fetchGitHubZipBytes,
11
- parseGitHubImportUrl,
12
- resolveGitHubCommit,
13
- resolveMarkdownTarget,
14
- stripGitHubZipRoot,
15
- } from './githubImport'
16
-
17
- function requestInfoToUrlString(input: RequestInfo | URL): string {
18
- if (typeof input === 'string') return input
19
- if (input instanceof URL) return input.toString()
20
- if (input instanceof Request) return input.url
21
-
22
- throw new Error('Unexpected fetch input type')
23
- }
24
-
25
- describe('github import', () => {
26
- it('parses repo root urls', () => {
27
- expect(parseGitHubImportUrl('https://github.com/visionik/ouracli')).toEqual({
28
- owner: 'visionik',
29
- repo: 'ouracli',
30
- originalUrl: 'https://github.com/visionik/ouracli',
31
- })
32
- })
33
-
34
- it('rejects non-https and non-github urls', () => {
35
- expect(() => parseGitHubImportUrl('http://github.com/a/b')).toThrow(/https/i)
36
- expect(() => parseGitHubImportUrl('https://example.com/a/b')).toThrow(/github\.com/i)
37
- expect(() => parseGitHubImportUrl('not-a-url')).toThrow(/Invalid URL/i)
38
- })
39
-
40
- it('rejects malformed tree/blob urls', () => {
41
- expect(() => parseGitHubImportUrl('https://github.com/a/b/tree/')).toThrow(/Missing ref/i)
42
- expect(() => parseGitHubImportUrl('https://github.com/a/b/blob/main')).toThrow(/Missing path/i)
43
- expect(() => parseGitHubImportUrl('https://github.com/a/b/tree/main/bad%5cpath')).toThrow()
44
- })
45
-
46
- it('parses tree urls with ref and path', () => {
47
- expect(parseGitHubImportUrl('https://github.com/a/b/tree/main/skills/foo')).toEqual({
48
- owner: 'a',
49
- repo: 'b',
50
- ref: 'main',
51
- path: 'skills/foo',
52
- originalUrl: 'https://github.com/a/b/tree/main/skills/foo',
53
- })
54
- })
55
-
56
- it('parses blob urls and derives folder path', () => {
57
- expect(parseGitHubImportUrl('https://github.com/a/b/blob/main/skills/foo/SKILL.md')).toEqual({
58
- owner: 'a',
59
- repo: 'b',
60
- ref: 'main',
61
- path: 'skills/foo',
62
- originalUrl: 'https://github.com/a/b/blob/main/skills/foo/SKILL.md',
63
- })
64
- })
65
-
66
- it('strips single top-level folder from GitHub zip entries', () => {
67
- const zip = buildGitHubZipForTests({
68
- 'repo-1/skill/SKILL.md': 'Body',
69
- 'repo-1/skill/a.txt': 'a',
70
- })
71
- const stripped = stripGitHubZipRoot(unzipSync(zip))
72
- expect(Object.keys(stripped).sort()).toEqual(['skill/SKILL.md', 'skill/a.txt'])
73
- })
74
-
75
- it('keeps paths when zip has multiple top-level roots', () => {
76
- const zip = buildGitHubZipForTests({
77
- 'a/SKILL.md': 'Body',
78
- 'b/SKILL.md': 'Body',
79
- })
80
- const stripped = stripGitHubZipRoot(unzipSync(zip))
81
- expect(Object.keys(stripped).sort()).toEqual(['a/SKILL.md', 'b/SKILL.md'])
82
- })
83
-
84
- it('detects candidates in a GitHub zip and strips the root folder', () => {
85
- const zip = buildGitHubZipForTests({
86
- 'ouracli-123/SKILL.md': `---\nname: demo\ndescription: Hello\n---\nBody`,
87
- 'ouracli-123/src/index.ts': 'export {}',
88
- })
89
- const stripped = stripGitHubZipRoot(unzipSync(zip))
90
- const candidates = detectGitHubImportCandidates(stripped)
91
- expect(candidates.map((c) => c.path)).toEqual([''])
92
- expect(candidates[0]?.name).toBe('demo')
93
- })
94
-
95
- it('detects multiple candidates and supports skills.md', () => {
96
- const zip = buildGitHubZipForTests({
97
- 'repo-1/alpha/SKILL.md': `---\nname: Alpha\n---\nBody`,
98
- 'repo-1/beta/skills.md': `---\nname: Beta\n---\nBody`,
99
- 'repo-1/readme.md': 'x',
100
- })
101
- const stripped = stripGitHubZipRoot(unzipSync(zip))
102
- const candidates = detectGitHubImportCandidates(stripped)
103
- expect(candidates.map((c) => c.path)).toEqual(['alpha', 'beta'])
104
- expect(candidates.map((c) => c.name)).toEqual(['Alpha', 'Beta'])
105
- })
106
-
107
- it('computes default selection via markdown references', () => {
108
- const entries = {
109
- 'skill/SKILL.md': `---\nname: demo\n---\nSee [usage](docs/usage.md) and ![logo](img/logo.svg).\nIgnore [web](https://example.com).`,
110
- 'skill/docs/usage.md': `See [more](more.md)`,
111
- 'skill/docs/more.md': `Ok`,
112
- 'skill/img/logo.svg': `<svg/>`,
113
- 'skill/extra.txt': 'not referenced',
114
- }
115
- const zip = buildGitHubZipForTests(
116
- Object.fromEntries(Object.entries(entries).map(([k, v]) => [`repo-1/${k}`, v])),
117
- )
118
- const raw = unzipSync(zip)
119
- const stripped = stripGitHubZipRoot(raw)
120
- const candidates = detectGitHubImportCandidates(stripped)
121
- const candidate = candidates.find((c) => c.path === 'skill')
122
- expect(candidate).toBeTruthy()
123
- if (!candidate) throw new Error('candidate not found')
124
-
125
- const files = Object.entries(stripped)
126
- .filter(([path]) => path.startsWith('skill/'))
127
- .map(([path, bytes]) => ({ path, bytes }))
128
- const selected = computeDefaultSelectedPaths({ candidate, files })
129
- expect(selected).toContain('skill/SKILL.md')
130
- expect(selected).toContain('skill/docs/usage.md')
131
- expect(selected).toContain('skill/docs/more.md')
132
- expect(selected).toContain('skill/img/logo.svg')
133
- expect(selected).not.toContain('skill/extra.txt')
134
- })
135
-
136
- it('does not select files outside skill folder (even when referenced)', () => {
137
- const entries = {
138
- 'skill/SKILL.md': `See [outside](../outside.md) and [abs](/abs.md) and [mail](mailto:test@example.com).`,
139
- 'outside.md': `secret`,
140
- 'skill/docs/usage.md': `Ok`,
141
- }
142
- const zip = buildGitHubZipForTests(
143
- Object.fromEntries(Object.entries(entries).map(([k, v]) => [`repo-1/${k}`, v])),
144
- )
145
- const stripped = stripGitHubZipRoot(unzipSync(zip))
146
- const candidate = detectGitHubImportCandidates(stripped).find((c) => c.path === 'skill')
147
- expect(candidate).toBeTruthy()
148
- if (!candidate) throw new Error('candidate not found')
149
- const files = Object.entries(stripped).map(([path, bytes]) => ({ path, bytes }))
150
- const selected = computeDefaultSelectedPaths({ candidate, files })
151
- expect(selected).toContain('skill/SKILL.md')
152
- expect(selected).not.toContain('outside.md')
153
- })
154
-
155
- it('extracts markdown targets with titles and angle brackets', () => {
156
- const targets = extractMarkdownRelativeTargets(
157
- `See [a](docs/usage.md "Title") and [b](<docs/my file.md>) and ![c](img/logo.svg)`,
158
- )
159
- expect(targets).toEqual(['docs/usage.md', 'docs/my file.md', 'img/logo.svg'])
160
- })
161
-
162
- it('resolves markdown targets safely', () => {
163
- expect(resolveMarkdownTarget('a/SKILL.md', 'docs/usage.md')).toBe('a/docs/usage.md')
164
- expect(resolveMarkdownTarget('a/SKILL.md', '../oops.md')).toBeNull()
165
- expect(resolveMarkdownTarget('a/SKILL.md', '/abs.md')).toBeNull()
166
- expect(resolveMarkdownTarget('a/SKILL.md', 'docs/usage.md#section')).toBe('a/docs/usage.md')
167
- expect(resolveMarkdownTarget('a/SKILL.md', 'docs/usage.md?x=1')).toBe('a/docs/usage.md')
168
- })
169
-
170
- it('resolves HEAD commit via redirect chain and refuses unexpected redirect hosts', async () => {
171
- const fetcher: typeof fetch = async (input) => {
172
- const url = requestInfoToUrlString(input)
173
- if (url.includes('/archive/HEAD.zip')) {
174
- return new Response(null, {
175
- status: 302,
176
- headers: {
177
- location:
178
- 'https://codeload.github.com/a/b/zip/0123456789012345678901234567890123456789',
179
- },
180
- })
181
- }
182
- if (url.startsWith('https://codeload.github.com/a/b/zip/')) {
183
- return new Response(null, { status: 200 })
184
- }
185
- throw new Error(`Unexpected fetch: ${url}`)
186
- }
187
- const resolved = await resolveGitHubCommit(
188
- { owner: 'a', repo: 'b', originalUrl: 'https://github.com/a/b' },
189
- fetcher,
190
- )
191
- expect(resolved.commit).toBe('0123456789012345678901234567890123456789')
192
-
193
- const badFetcher: typeof fetch = async (input) => {
194
- const url = requestInfoToUrlString(input)
195
- if (url.includes('/archive/HEAD.zip')) {
196
- return new Response(null, {
197
- status: 302,
198
- headers: { location: 'https://evil.example/zip/abc' },
199
- })
200
- }
201
- throw new Error(`Unexpected fetch: ${url}`)
202
- }
203
- await expect(
204
- resolveGitHubCommit(
205
- { owner: 'a', repo: 'b', originalUrl: 'https://github.com/a/b' },
206
- badFetcher,
207
- ),
208
- ).rejects.toThrow(/redirect/i)
209
- })
210
-
211
- it('resolves explicit ref commit via GitHub API', async () => {
212
- const fetcher: typeof fetch = async (input) => {
213
- const url = requestInfoToUrlString(input)
214
- if (url.startsWith('https://api.github.com/repos/a/b/commits/')) {
215
- return new Response(JSON.stringify({ sha: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' }), {
216
- status: 200,
217
- })
218
- }
219
- throw new Error(`Unexpected fetch: ${url}`)
220
- }
221
- const resolved = await resolveGitHubCommit(
222
- { owner: 'a', repo: 'b', ref: 'main', originalUrl: 'https://github.com/a/b' },
223
- fetcher,
224
- )
225
- expect(resolved.commit).toBe('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
226
- })
227
-
228
- it('enforces zip byte cap when content-length is too large', async () => {
229
- const resolved = {
230
- owner: 'a',
231
- repo: 'b',
232
- ref: 'main',
233
- commit: '0123456789012345678901234567890123456789',
234
- path: '',
235
- repoUrl: 'https://github.com/a/b',
236
- originalUrl: 'https://github.com/a/b',
237
- } as const
238
- const fetcher: typeof fetch = async () =>
239
- new Response(new Blob([new Uint8Array([1, 2, 3])]), {
240
- status: 200,
241
- headers: { 'content-length': String(999_999_999) },
242
- })
243
- await expect(fetchGitHubZipBytes(resolved, fetcher, { maxZipBytes: 10 })).rejects.toThrow(
244
- /too large/i,
245
- )
246
- })
247
- })