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.
- package/LICENSE +1 -0
- package/README.md +36 -129
- package/dist/browserAuth.d.ts +20 -0
- package/dist/browserAuth.js +156 -0
- package/dist/browserAuth.js.map +1 -0
- package/dist/browserAuth.test.d.ts +1 -0
- package/dist/browserAuth.test.js +83 -0
- package/dist/browserAuth.test.js.map +1 -0
- package/dist/cli/buildInfo.d.ts +3 -0
- package/dist/cli/buildInfo.js +103 -0
- package/dist/cli/buildInfo.js.map +1 -0
- package/dist/cli/commands/auth.d.ts +9 -0
- package/dist/cli/commands/auth.js +75 -0
- package/dist/cli/commands/auth.js.map +1 -0
- package/dist/cli/commands/delete.d.ts +11 -0
- package/dist/cli/commands/delete.js +67 -0
- package/dist/cli/commands/delete.js.map +1 -0
- package/dist/cli/commands/delete.test.d.ts +1 -0
- package/dist/cli/commands/delete.test.js +52 -0
- package/dist/cli/commands/delete.test.js.map +1 -0
- package/dist/cli/commands/publish.d.ts +9 -0
- package/dist/cli/commands/publish.js +87 -0
- package/dist/cli/commands/publish.js.map +1 -0
- package/dist/cli/commands/publish.test.d.ts +1 -0
- package/dist/cli/commands/publish.test.js +104 -0
- package/dist/cli/commands/publish.test.js.map +1 -0
- package/dist/cli/commands/skills.d.ts +23 -0
- package/dist/cli/commands/skills.js +298 -0
- package/dist/cli/commands/skills.js.map +1 -0
- package/dist/cli/commands/skills.test.d.ts +1 -0
- package/dist/cli/commands/skills.test.js +156 -0
- package/dist/cli/commands/skills.test.js.map +1 -0
- package/dist/cli/commands/star.d.ts +8 -0
- package/dist/cli/commands/star.js +38 -0
- package/dist/cli/commands/star.js.map +1 -0
- package/dist/cli/commands/sync.d.ts +3 -0
- package/dist/cli/commands/sync.js +160 -0
- package/dist/cli/commands/sync.js.map +1 -0
- package/dist/cli/commands/sync.test.d.ts +1 -0
- package/dist/cli/commands/sync.test.js +277 -0
- package/dist/cli/commands/sync.test.js.map +1 -0
- package/dist/cli/commands/syncHelpers.d.ts +76 -0
- package/dist/cli/commands/syncHelpers.js +349 -0
- package/dist/cli/commands/syncHelpers.js.map +1 -0
- package/dist/cli/commands/syncHelpers.test.d.ts +1 -0
- package/dist/cli/commands/syncHelpers.test.js +22 -0
- package/dist/cli/commands/syncHelpers.test.js.map +1 -0
- package/dist/cli/commands/syncTypes.d.ts +24 -0
- package/dist/cli/commands/syncTypes.js +2 -0
- package/dist/cli/commands/syncTypes.js.map +1 -0
- package/dist/cli/commands/unstar.d.ts +8 -0
- package/dist/cli/commands/unstar.js +38 -0
- package/dist/cli/commands/unstar.js.map +1 -0
- package/dist/cli/helpStyle.d.ts +13 -0
- package/dist/cli/helpStyle.js +38 -0
- package/dist/cli/helpStyle.js.map +1 -0
- package/dist/cli/pilotbotConfig.d.ts +6 -0
- package/dist/cli/pilotbotConfig.js +110 -0
- package/dist/cli/pilotbotConfig.js.map +1 -0
- package/dist/cli/pilotbotConfig.test.d.ts +1 -0
- package/dist/cli/pilotbotConfig.test.js +133 -0
- package/dist/cli/pilotbotConfig.test.js.map +1 -0
- package/dist/cli/registry.d.ts +7 -0
- package/dist/cli/registry.js +42 -0
- package/dist/cli/registry.js.map +1 -0
- package/dist/cli/registry.test.d.ts +1 -0
- package/dist/cli/registry.test.js +48 -0
- package/dist/cli/registry.test.js.map +1 -0
- package/dist/cli/scanSkills.d.ts +7 -0
- package/dist/cli/scanSkills.js +75 -0
- package/dist/cli/scanSkills.js.map +1 -0
- package/dist/cli/scanSkills.test.d.ts +1 -0
- package/dist/cli/scanSkills.test.js +60 -0
- package/dist/cli/scanSkills.test.js.map +1 -0
- package/dist/cli/slug.d.ts +2 -0
- package/dist/cli/slug.js +16 -0
- package/dist/cli/slug.js.map +1 -0
- package/dist/cli/types.d.ts +15 -0
- package/dist/cli/types.js +2 -0
- package/dist/cli/types.js.map +1 -0
- package/dist/cli/ui.d.ts +7 -0
- package/dist/cli/ui.js +72 -0
- package/dist/cli/ui.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +268 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.js +38 -0
- package/dist/config.js.map +1 -0
- package/dist/discovery.d.ts +5 -0
- package/dist/discovery.js +21 -0
- package/dist/discovery.js.map +1 -0
- package/dist/discovery.test.d.ts +1 -0
- package/dist/discovery.test.js +46 -0
- package/dist/discovery.test.js.map +1 -0
- package/dist/http.d.ts +32 -0
- package/dist/http.js +261 -0
- package/dist/http.js.map +1 -0
- package/dist/http.test.d.ts +1 -0
- package/dist/http.test.js +135 -0
- package/dist/http.test.js.map +1 -0
- package/dist/schema/ark.js.map +1 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/schema/routes.js.map +1 -0
- package/{packages/schema/dist → dist/schema}/schemas.d.ts +0 -39
- package/{packages/schema/dist → dist/schema}/schemas.js +0 -22
- package/dist/schema/schemas.js.map +1 -0
- package/dist/schema/textFiles.js.map +1 -0
- package/dist/schema/textFiles.test.d.ts +1 -0
- package/dist/schema/textFiles.test.js +20 -0
- package/dist/schema/textFiles.test.js.map +1 -0
- package/dist/skills.d.ts +43 -0
- package/dist/skills.js +163 -0
- package/dist/skills.js.map +1 -0
- package/dist/skills.test.d.ts +1 -0
- package/dist/skills.test.js +144 -0
- package/dist/skills.test.js.map +1 -0
- package/dist/types.d.ts +7 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +27 -70
- package/.env.local.example +0 -19
- package/.github/workflows/ci.yml +0 -40
- package/.oxlintrc.json +0 -3
- package/AGENTS.md +0 -45
- package/CHANGELOG.md +0 -138
- package/DEPRECATIONS.md +0 -7
- package/biome.json +0 -41
- package/convex/_generated/api.d.ts +0 -153
- package/convex/_generated/api.js +0 -23
- package/convex/_generated/dataModel.d.ts +0 -60
- package/convex/_generated/server.d.ts +0 -143
- package/convex/_generated/server.js +0 -93
- package/convex/auth.config.ts +0 -8
- package/convex/auth.ts +0 -19
- package/convex/comments.ts +0 -88
- package/convex/crons.ts +0 -34
- package/convex/devSeed.ts +0 -459
- package/convex/devSeedExtra.ts +0 -541
- package/convex/downloads.ts +0 -78
- package/convex/githubBackups.ts +0 -170
- package/convex/githubBackupsNode.ts +0 -183
- package/convex/githubImport.ts +0 -317
- package/convex/githubSoulBackups.ts +0 -170
- package/convex/githubSoulBackupsNode.ts +0 -186
- package/convex/http.ts +0 -194
- package/convex/httpApi.handlers.test.ts +0 -488
- package/convex/httpApi.test.ts +0 -70
- package/convex/httpApi.ts +0 -305
- package/convex/httpApiV1.handlers.test.ts +0 -584
- package/convex/httpApiV1.ts +0 -1172
- package/convex/leaderboards.ts +0 -39
- package/convex/lib/access.ts +0 -36
- package/convex/lib/apiTokenAuth.ts +0 -36
- package/convex/lib/badges.ts +0 -50
- package/convex/lib/changelog.test.ts +0 -34
- package/convex/lib/changelog.ts +0 -278
- package/convex/lib/embeddings.ts +0 -38
- package/convex/lib/githubBackup.ts +0 -443
- package/convex/lib/githubImport.test.ts +0 -247
- package/convex/lib/githubImport.ts +0 -425
- package/convex/lib/githubSoulBackup.ts +0 -443
- package/convex/lib/leaderboards.ts +0 -103
- package/convex/lib/moderation.ts +0 -42
- package/convex/lib/public.ts +0 -89
- package/convex/lib/searchText.test.ts +0 -46
- package/convex/lib/searchText.ts +0 -27
- package/convex/lib/skillBackfill.test.ts +0 -34
- package/convex/lib/skillBackfill.ts +0 -67
- package/convex/lib/skillPublish.test.ts +0 -28
- package/convex/lib/skillPublish.ts +0 -284
- package/convex/lib/skillStats.ts +0 -80
- package/convex/lib/skills.test.ts +0 -197
- package/convex/lib/skills.ts +0 -273
- package/convex/lib/soulChangelog.ts +0 -273
- package/convex/lib/soulPublish.ts +0 -236
- package/convex/lib/tokens.test.ts +0 -33
- package/convex/lib/tokens.ts +0 -51
- package/convex/lib/webhooks.test.ts +0 -91
- package/convex/lib/webhooks.ts +0 -112
- package/convex/maintenance.test.ts +0 -270
- package/convex/maintenance.ts +0 -840
- package/convex/rateLimits.ts +0 -50
- package/convex/schema.ts +0 -472
- package/convex/search.test.ts +0 -12
- package/convex/search.ts +0 -254
- package/convex/seed.test.ts +0 -37
- package/convex/seed.ts +0 -254
- package/convex/seedSouls.ts +0 -111
- package/convex/skillStatEvents.ts +0 -568
- package/convex/skills.ts +0 -1606
- package/convex/soulComments.ts +0 -88
- package/convex/soulDownloads.ts +0 -14
- package/convex/soulStars.ts +0 -71
- package/convex/souls.ts +0 -570
- package/convex/stars.ts +0 -108
- package/convex/statsMaintenance.ts +0 -205
- package/convex/telemetry.ts +0 -434
- package/convex/tokens.ts +0 -88
- package/convex/tsconfig.json +0 -7
- package/convex/uploads.ts +0 -20
- package/convex/users.ts +0 -122
- package/convex/webhooks.ts +0 -50
- package/convex.json +0 -3
- package/docs/README.md +0 -32
- package/docs/api.md +0 -51
- package/docs/architecture.md +0 -61
- package/docs/auth.md +0 -54
- package/docs/cli.md +0 -117
- package/docs/deploy.md +0 -78
- package/docs/diffing.md +0 -84
- package/docs/github-import.md +0 -171
- package/docs/http-api.md +0 -187
- package/docs/manual-testing.md +0 -64
- package/docs/mintlify.md +0 -43
- package/docs/quickstart.md +0 -120
- package/docs/skill-format.md +0 -58
- package/docs/soul-format.md +0 -37
- package/docs/spec.md +0 -177
- package/docs/telemetry.md +0 -91
- package/docs/troubleshooting.md +0 -49
- package/docs/webhook.md +0 -51
- package/e2e/menu-smoke.pw.test.ts +0 -49
- package/e2e/pilothub.e2e.test.ts +0 -494
- package/e2e/search-exact.pw.test.ts +0 -97
- package/packages/pilothub/LICENSE +0 -22
- package/packages/pilothub/README.md +0 -57
- package/packages/pilothub/package.json +0 -41
- package/packages/pilothub/src/browserAuth.test.ts +0 -96
- package/packages/pilothub/src/browserAuth.ts +0 -174
- package/packages/pilothub/src/cli/buildInfo.ts +0 -94
- package/packages/pilothub/src/cli/commands/auth.ts +0 -97
- package/packages/pilothub/src/cli/commands/delete.test.ts +0 -73
- package/packages/pilothub/src/cli/commands/delete.ts +0 -83
- package/packages/pilothub/src/cli/commands/publish.test.ts +0 -122
- package/packages/pilothub/src/cli/commands/publish.ts +0 -108
- package/packages/pilothub/src/cli/commands/skills.test.ts +0 -191
- package/packages/pilothub/src/cli/commands/skills.ts +0 -380
- package/packages/pilothub/src/cli/commands/star.ts +0 -46
- package/packages/pilothub/src/cli/commands/sync.test.ts +0 -310
- package/packages/pilothub/src/cli/commands/sync.ts +0 -200
- package/packages/pilothub/src/cli/commands/syncHelpers.test.ts +0 -26
- package/packages/pilothub/src/cli/commands/syncHelpers.ts +0 -427
- package/packages/pilothub/src/cli/commands/syncTypes.ts +0 -27
- package/packages/pilothub/src/cli/commands/unstar.ts +0 -48
- package/packages/pilothub/src/cli/helpStyle.ts +0 -45
- package/packages/pilothub/src/cli/pilotbotConfig.test.ts +0 -159
- package/packages/pilothub/src/cli/pilotbotConfig.ts +0 -147
- package/packages/pilothub/src/cli/registry.test.ts +0 -63
- package/packages/pilothub/src/cli/registry.ts +0 -43
- package/packages/pilothub/src/cli/scanSkills.test.ts +0 -64
- package/packages/pilothub/src/cli/scanSkills.ts +0 -84
- package/packages/pilothub/src/cli/slug.ts +0 -16
- package/packages/pilothub/src/cli/types.ts +0 -12
- package/packages/pilothub/src/cli/ui.ts +0 -75
- package/packages/pilothub/src/cli.ts +0 -311
- package/packages/pilothub/src/config.ts +0 -36
- package/packages/pilothub/src/discovery.test.ts +0 -75
- package/packages/pilothub/src/discovery.ts +0 -19
- package/packages/pilothub/src/http.test.ts +0 -156
- package/packages/pilothub/src/http.ts +0 -301
- package/packages/pilothub/src/schema/ark.ts +0 -29
- package/packages/pilothub/src/schema/index.ts +0 -5
- package/packages/pilothub/src/schema/routes.ts +0 -22
- package/packages/pilothub/src/schema/schemas.ts +0 -260
- package/packages/pilothub/src/schema/textFiles.test.ts +0 -23
- package/packages/pilothub/src/schema/textFiles.ts +0 -66
- package/packages/pilothub/src/skills.test.ts +0 -191
- package/packages/pilothub/src/skills.ts +0 -172
- package/packages/pilothub/src/types.ts +0 -10
- package/packages/pilothub/tsconfig.json +0 -14
- package/packages/schema/README.md +0 -3
- package/packages/schema/dist/ark.js.map +0 -1
- package/packages/schema/dist/index.js.map +0 -1
- package/packages/schema/dist/routes.js.map +0 -1
- package/packages/schema/dist/schemas.js.map +0 -1
- package/packages/schema/dist/textFiles.js.map +0 -1
- package/packages/schema/package.json +0 -26
- package/packages/schema/src/ark.ts +0 -29
- package/packages/schema/src/index.ts +0 -5
- package/packages/schema/src/routes.ts +0 -22
- package/packages/schema/src/schemas.test.ts +0 -123
- package/packages/schema/src/schemas.ts +0 -287
- package/packages/schema/src/textFiles.test.ts +0 -23
- package/packages/schema/src/textFiles.ts +0 -66
- package/packages/schema/tsconfig.json +0 -15
- package/pilothub +0 -46
- package/playwright.config.ts +0 -33
- package/public/.well-known/pilothub.json +0 -6
- package/public/api/v1/openapi.json +0 -379
- package/public/favicon.ico +0 -0
- package/public/logo192.png +0 -0
- package/public/logo512.png +0 -0
- package/public/manifest.json +0 -25
- package/public/og.png +0 -0
- package/public/og.svg +0 -98
- package/public/pilot-logo.png +0 -0
- package/public/pilot-mark.png +0 -0
- package/public/robots.txt +0 -3
- package/public/tanstack-circle-logo.png +0 -0
- package/public/tanstack-word-logo-white.svg +0 -1
- package/scripts/check-peer-deps.ts +0 -56
- package/scripts/docs-list.ts +0 -148
- package/scripts/run-playwright-local.sh +0 -14
- package/server/og/fetchSkillOgMeta.ts +0 -27
- package/server/og/fetchSoulOgMeta.ts +0 -27
- package/server/og/ogAssets.ts +0 -80
- package/server/og/skillOgSvg.test.ts +0 -59
- package/server/og/skillOgSvg.ts +0 -258
- package/server/og/soulOgSvg.ts +0 -209
- package/server/routes/og/skill.png.ts +0 -103
- package/server/routes/og/soul.png.ts +0 -111
- package/src/__tests__/skill-detail-page.test.tsx +0 -86
- package/src/__tests__/skills-index.test.tsx +0 -145
- package/src/__tests__/upload.route.test.tsx +0 -228
- package/src/components/AppProviders.tsx +0 -19
- package/src/components/ClientOnly.tsx +0 -18
- package/src/components/Footer.tsx +0 -29
- package/src/components/Header.tsx +0 -295
- package/src/components/InstallSwitcher.tsx +0 -53
- package/src/components/SkillCard.tsx +0 -36
- package/src/components/SkillDetailPage.tsx +0 -817
- package/src/components/SkillDiffCard.tsx +0 -485
- package/src/components/SoulCard.tsx +0 -19
- package/src/components/SoulDetailPage.tsx +0 -263
- package/src/components/UserBootstrap.tsx +0 -18
- package/src/components/ui/dropdown-menu.tsx +0 -67
- package/src/components/ui/toggle-group.tsx +0 -35
- package/src/convex/client.ts +0 -3
- package/src/lib/badges.ts +0 -29
- package/src/lib/diffing.test.ts +0 -163
- package/src/lib/diffing.ts +0 -106
- package/src/lib/gravatar.test.ts +0 -9
- package/src/lib/gravatar.ts +0 -158
- package/src/lib/og.test.ts +0 -142
- package/src/lib/og.ts +0 -156
- package/src/lib/publicUser.ts +0 -39
- package/src/lib/roles.ts +0 -19
- package/src/lib/site.test.ts +0 -130
- package/src/lib/site.ts +0 -84
- package/src/lib/theme-transition.test.ts +0 -134
- package/src/lib/theme-transition.ts +0 -134
- package/src/lib/theme.test.tsx +0 -88
- package/src/lib/theme.ts +0 -43
- package/src/lib/uploadFiles.jsdom.test.ts +0 -33
- package/src/lib/uploadFiles.test.ts +0 -123
- package/src/lib/uploadFiles.ts +0 -245
- package/src/lib/uploadUtils.test.ts +0 -78
- package/src/lib/uploadUtils.ts +0 -93
- package/src/lib/useAuthStatus.ts +0 -12
- package/src/lib/utils.test.ts +0 -9
- package/src/lib/utils.ts +0 -6
- package/src/logo.svg +0 -12
- package/src/routeTree.gen.ts +0 -345
- package/src/router.tsx +0 -17
- package/src/routes/$owner/$slug.tsx +0 -55
- package/src/routes/__root.tsx +0 -136
- package/src/routes/admin.tsx +0 -11
- package/src/routes/cli/auth.tsx +0 -168
- package/src/routes/dashboard.tsx +0 -97
- package/src/routes/import.tsx +0 -415
- package/src/routes/index.tsx +0 -252
- package/src/routes/management.tsx +0 -529
- package/src/routes/settings.tsx +0 -203
- package/src/routes/skills/index.tsx +0 -422
- package/src/routes/souls/$slug.tsx +0 -55
- package/src/routes/souls/index.tsx +0 -243
- package/src/routes/stars.tsx +0 -68
- package/src/routes/u/$handle.tsx +0 -307
- package/src/routes/upload/utils.ts +0 -81
- package/src/routes/upload.tsx +0 -499
- package/src/styles.css +0 -2718
- package/tsconfig.json +0 -24
- package/tsconfig.oxlint.json +0 -16
- package/vercel.json +0 -8
- package/vite.config.ts +0 -48
- package/vitest.config.ts +0 -47
- package/vitest.e2e.config.ts +0 -11
- package/vitest.setup.ts +0 -1
- /package/{packages/pilothub/bin → bin}/pilothub.js +0 -0
- /package/{packages/schema/dist → dist/schema}/ark.d.ts +0 -0
- /package/{packages/schema/dist → dist/schema}/ark.js +0 -0
- /package/{packages/schema/dist → dist/schema}/index.d.ts +0 -0
- /package/{packages/schema/dist → dist/schema}/index.js +0 -0
- /package/{packages/schema/dist → dist/schema}/routes.d.ts +0 -0
- /package/{packages/schema/dist → dist/schema}/routes.js +0 -0
- /package/{packages/schema/dist → dist/schema}/textFiles.d.ts +0 -0
- /package/{packages/schema/dist → dist/schema}/textFiles.js +0 -0
package/convex/githubBackups.ts
DELETED
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
import { v } from 'convex/values'
|
|
2
|
-
import { internal } from './_generated/api'
|
|
3
|
-
import type { Doc, Id } from './_generated/dataModel'
|
|
4
|
-
import { action, internalMutation, internalQuery } from './_generated/server'
|
|
5
|
-
import { assertRole, requireUserFromAction } from './lib/access'
|
|
6
|
-
|
|
7
|
-
const DEFAULT_BATCH_SIZE = 50
|
|
8
|
-
const MAX_BATCH_SIZE = 200
|
|
9
|
-
const SYNC_STATE_KEY = 'default'
|
|
10
|
-
|
|
11
|
-
type BackupPageItem =
|
|
12
|
-
| {
|
|
13
|
-
kind: 'ok'
|
|
14
|
-
skillId: Id<'skills'>
|
|
15
|
-
versionId: Id<'skillVersions'>
|
|
16
|
-
slug: string
|
|
17
|
-
displayName: string
|
|
18
|
-
version: string
|
|
19
|
-
ownerHandle: string
|
|
20
|
-
files: Doc<'skillVersions'>['files']
|
|
21
|
-
publishedAt: number
|
|
22
|
-
}
|
|
23
|
-
| { kind: 'missingLatestVersion'; skillId: Id<'skills'> }
|
|
24
|
-
| { kind: 'missingVersionDoc'; skillId: Id<'skills'>; versionId: Id<'skillVersions'> }
|
|
25
|
-
| { kind: 'missingOwner'; skillId: Id<'skills'>; ownerUserId: Id<'users'> }
|
|
26
|
-
|
|
27
|
-
type BackupPageResult = {
|
|
28
|
-
items: BackupPageItem[]
|
|
29
|
-
cursor: string | null
|
|
30
|
-
isDone: boolean
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
type BackupSyncState = {
|
|
34
|
-
cursor: string | null
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export type SyncGitHubBackupsResult = {
|
|
38
|
-
stats: {
|
|
39
|
-
skillsScanned: number
|
|
40
|
-
skillsSkipped: number
|
|
41
|
-
skillsBackedUp: number
|
|
42
|
-
skillsMissingVersion: number
|
|
43
|
-
skillsMissingOwner: number
|
|
44
|
-
errors: number
|
|
45
|
-
}
|
|
46
|
-
cursor: string | null
|
|
47
|
-
isDone: boolean
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export const getGitHubBackupPageInternal = internalQuery({
|
|
51
|
-
args: {
|
|
52
|
-
cursor: v.optional(v.string()),
|
|
53
|
-
batchSize: v.optional(v.number()),
|
|
54
|
-
},
|
|
55
|
-
handler: async (ctx, args): Promise<BackupPageResult> => {
|
|
56
|
-
const batchSize = clampInt(args.batchSize ?? DEFAULT_BATCH_SIZE, 1, MAX_BATCH_SIZE)
|
|
57
|
-
const { page, isDone, continueCursor } = await ctx.db
|
|
58
|
-
.query('skills')
|
|
59
|
-
.order('asc')
|
|
60
|
-
.paginate({ cursor: args.cursor ?? null, numItems: batchSize })
|
|
61
|
-
|
|
62
|
-
const items: BackupPageItem[] = []
|
|
63
|
-
for (const skill of page) {
|
|
64
|
-
if (skill.softDeletedAt) continue
|
|
65
|
-
if (!skill.latestVersionId) {
|
|
66
|
-
items.push({ kind: 'missingLatestVersion', skillId: skill._id })
|
|
67
|
-
continue
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const version = await ctx.db.get(skill.latestVersionId)
|
|
71
|
-
if (!version) {
|
|
72
|
-
items.push({
|
|
73
|
-
kind: 'missingVersionDoc',
|
|
74
|
-
skillId: skill._id,
|
|
75
|
-
versionId: skill.latestVersionId,
|
|
76
|
-
})
|
|
77
|
-
continue
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const owner = await ctx.db.get(skill.ownerUserId)
|
|
81
|
-
if (!owner || owner.deletedAt) {
|
|
82
|
-
items.push({ kind: 'missingOwner', skillId: skill._id, ownerUserId: skill.ownerUserId })
|
|
83
|
-
continue
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
items.push({
|
|
87
|
-
kind: 'ok',
|
|
88
|
-
skillId: skill._id,
|
|
89
|
-
versionId: version._id,
|
|
90
|
-
slug: skill.slug,
|
|
91
|
-
displayName: skill.displayName,
|
|
92
|
-
version: version.version,
|
|
93
|
-
ownerHandle: owner.handle ?? owner._id,
|
|
94
|
-
files: version.files,
|
|
95
|
-
publishedAt: version.createdAt,
|
|
96
|
-
})
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return { items, cursor: continueCursor, isDone }
|
|
100
|
-
},
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
export const getGitHubBackupSyncStateInternal = internalQuery({
|
|
104
|
-
args: {},
|
|
105
|
-
handler: async (ctx): Promise<BackupSyncState> => {
|
|
106
|
-
const state = await ctx.db
|
|
107
|
-
.query('githubBackupSyncState')
|
|
108
|
-
.withIndex('by_key', (q) => q.eq('key', SYNC_STATE_KEY))
|
|
109
|
-
.unique()
|
|
110
|
-
return { cursor: state?.cursor ?? null }
|
|
111
|
-
},
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
export const setGitHubBackupSyncStateInternal = internalMutation({
|
|
115
|
-
args: {
|
|
116
|
-
cursor: v.optional(v.string()),
|
|
117
|
-
},
|
|
118
|
-
handler: async (ctx, args) => {
|
|
119
|
-
const now = Date.now()
|
|
120
|
-
const state = await ctx.db
|
|
121
|
-
.query('githubBackupSyncState')
|
|
122
|
-
.withIndex('by_key', (q) => q.eq('key', SYNC_STATE_KEY))
|
|
123
|
-
.unique()
|
|
124
|
-
|
|
125
|
-
if (!state) {
|
|
126
|
-
await ctx.db.insert('githubBackupSyncState', {
|
|
127
|
-
key: SYNC_STATE_KEY,
|
|
128
|
-
cursor: args.cursor,
|
|
129
|
-
updatedAt: now,
|
|
130
|
-
})
|
|
131
|
-
return { ok: true as const }
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
await ctx.db.patch(state._id, {
|
|
135
|
-
cursor: args.cursor,
|
|
136
|
-
updatedAt: now,
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
return { ok: true as const }
|
|
140
|
-
},
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
export const syncGitHubBackups: ReturnType<typeof action> = action({
|
|
144
|
-
args: {
|
|
145
|
-
dryRun: v.optional(v.boolean()),
|
|
146
|
-
batchSize: v.optional(v.number()),
|
|
147
|
-
maxBatches: v.optional(v.number()),
|
|
148
|
-
resetCursor: v.optional(v.boolean()),
|
|
149
|
-
},
|
|
150
|
-
handler: async (ctx, args): Promise<SyncGitHubBackupsResult> => {
|
|
151
|
-
const { user } = await requireUserFromAction(ctx)
|
|
152
|
-
assertRole(user, ['admin'])
|
|
153
|
-
|
|
154
|
-
if (args.resetCursor && !args.dryRun) {
|
|
155
|
-
await ctx.runMutation(internal.githubBackups.setGitHubBackupSyncStateInternal, {
|
|
156
|
-
cursor: undefined,
|
|
157
|
-
})
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
return ctx.runAction(internal.githubBackupsNode.syncGitHubBackupsInternal, {
|
|
161
|
-
dryRun: args.dryRun,
|
|
162
|
-
batchSize: args.batchSize,
|
|
163
|
-
maxBatches: args.maxBatches,
|
|
164
|
-
}) as Promise<SyncGitHubBackupsResult>
|
|
165
|
-
},
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
function clampInt(value: number, min: number, max: number) {
|
|
169
|
-
return Math.max(min, Math.min(max, Math.floor(value)))
|
|
170
|
-
}
|
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
'use node'
|
|
2
|
-
|
|
3
|
-
import { v } from 'convex/values'
|
|
4
|
-
import { internal } from './_generated/api'
|
|
5
|
-
import type { Doc } from './_generated/dataModel'
|
|
6
|
-
import type { ActionCtx } from './_generated/server'
|
|
7
|
-
import { internalAction } from './_generated/server'
|
|
8
|
-
import {
|
|
9
|
-
backupSkillToGitHub,
|
|
10
|
-
fetchGitHubSkillMeta,
|
|
11
|
-
getGitHubBackupContext,
|
|
12
|
-
isGitHubBackupConfigured,
|
|
13
|
-
} from './lib/githubBackup'
|
|
14
|
-
|
|
15
|
-
const DEFAULT_BATCH_SIZE = 50
|
|
16
|
-
const MAX_BATCH_SIZE = 200
|
|
17
|
-
const DEFAULT_MAX_BATCHES = 5
|
|
18
|
-
const MAX_MAX_BATCHES = 200
|
|
19
|
-
|
|
20
|
-
type BackupPageItem =
|
|
21
|
-
| {
|
|
22
|
-
kind: 'ok'
|
|
23
|
-
slug: string
|
|
24
|
-
version: string
|
|
25
|
-
displayName: string
|
|
26
|
-
ownerHandle: string
|
|
27
|
-
files: Doc<'skillVersions'>['files']
|
|
28
|
-
publishedAt: number
|
|
29
|
-
}
|
|
30
|
-
| { kind: 'missingLatestVersion' }
|
|
31
|
-
| { kind: 'missingVersionDoc' }
|
|
32
|
-
| { kind: 'missingOwner' }
|
|
33
|
-
|
|
34
|
-
export type GitHubBackupSyncStats = {
|
|
35
|
-
skillsScanned: number
|
|
36
|
-
skillsSkipped: number
|
|
37
|
-
skillsBackedUp: number
|
|
38
|
-
skillsMissingVersion: number
|
|
39
|
-
skillsMissingOwner: number
|
|
40
|
-
errors: number
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export type SyncGitHubBackupsInternalArgs = {
|
|
44
|
-
dryRun?: boolean
|
|
45
|
-
batchSize?: number
|
|
46
|
-
maxBatches?: number
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export type SyncGitHubBackupsInternalResult = {
|
|
50
|
-
stats: GitHubBackupSyncStats
|
|
51
|
-
cursor: string | null
|
|
52
|
-
isDone: boolean
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export const backupSkillForPublishInternal = internalAction({
|
|
56
|
-
args: {
|
|
57
|
-
slug: v.string(),
|
|
58
|
-
version: v.string(),
|
|
59
|
-
displayName: v.string(),
|
|
60
|
-
ownerHandle: v.string(),
|
|
61
|
-
files: v.array(
|
|
62
|
-
v.object({
|
|
63
|
-
path: v.string(),
|
|
64
|
-
size: v.number(),
|
|
65
|
-
storageId: v.id('_storage'),
|
|
66
|
-
sha256: v.string(),
|
|
67
|
-
contentType: v.optional(v.string()),
|
|
68
|
-
}),
|
|
69
|
-
),
|
|
70
|
-
publishedAt: v.number(),
|
|
71
|
-
},
|
|
72
|
-
handler: async (ctx, args) => {
|
|
73
|
-
if (!isGitHubBackupConfigured()) {
|
|
74
|
-
return { skipped: true as const }
|
|
75
|
-
}
|
|
76
|
-
await backupSkillToGitHub(ctx, args)
|
|
77
|
-
return { skipped: false as const }
|
|
78
|
-
},
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
export async function syncGitHubBackupsInternalHandler(
|
|
82
|
-
ctx: ActionCtx,
|
|
83
|
-
args: SyncGitHubBackupsInternalArgs,
|
|
84
|
-
): Promise<SyncGitHubBackupsInternalResult> {
|
|
85
|
-
const dryRun = Boolean(args.dryRun)
|
|
86
|
-
const stats: GitHubBackupSyncStats = {
|
|
87
|
-
skillsScanned: 0,
|
|
88
|
-
skillsSkipped: 0,
|
|
89
|
-
skillsBackedUp: 0,
|
|
90
|
-
skillsMissingVersion: 0,
|
|
91
|
-
skillsMissingOwner: 0,
|
|
92
|
-
errors: 0,
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (!isGitHubBackupConfigured()) {
|
|
96
|
-
return { stats, cursor: null, isDone: true }
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const batchSize = clampInt(args.batchSize ?? DEFAULT_BATCH_SIZE, 1, MAX_BATCH_SIZE)
|
|
100
|
-
const maxBatches = clampInt(args.maxBatches ?? DEFAULT_MAX_BATCHES, 1, MAX_MAX_BATCHES)
|
|
101
|
-
const context = await getGitHubBackupContext()
|
|
102
|
-
|
|
103
|
-
const state = dryRun
|
|
104
|
-
? { cursor: null as string | null }
|
|
105
|
-
: ((await ctx.runQuery(internal.githubBackups.getGitHubBackupSyncStateInternal, {})) as {
|
|
106
|
-
cursor: string | null
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
let cursor: string | null = state.cursor
|
|
110
|
-
let isDone = false
|
|
111
|
-
|
|
112
|
-
for (let batch = 0; batch < maxBatches; batch++) {
|
|
113
|
-
const page = (await ctx.runQuery(internal.githubBackups.getGitHubBackupPageInternal, {
|
|
114
|
-
cursor: cursor ?? undefined,
|
|
115
|
-
batchSize,
|
|
116
|
-
})) as { items: BackupPageItem[]; cursor: string | null; isDone: boolean }
|
|
117
|
-
|
|
118
|
-
cursor = page.cursor
|
|
119
|
-
isDone = page.isDone
|
|
120
|
-
|
|
121
|
-
for (const item of page.items) {
|
|
122
|
-
if (item.kind !== 'ok') {
|
|
123
|
-
if (item.kind === 'missingLatestVersion' || item.kind === 'missingVersionDoc') {
|
|
124
|
-
stats.skillsMissingVersion += 1
|
|
125
|
-
} else if (item.kind === 'missingOwner') {
|
|
126
|
-
stats.skillsMissingOwner += 1
|
|
127
|
-
}
|
|
128
|
-
continue
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
stats.skillsScanned += 1
|
|
132
|
-
try {
|
|
133
|
-
const meta = await fetchGitHubSkillMeta(context, item.ownerHandle, item.slug)
|
|
134
|
-
if (meta?.latest?.version === item.version) {
|
|
135
|
-
stats.skillsSkipped += 1
|
|
136
|
-
continue
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
if (!dryRun) {
|
|
140
|
-
await backupSkillToGitHub(
|
|
141
|
-
ctx,
|
|
142
|
-
{
|
|
143
|
-
slug: item.slug,
|
|
144
|
-
version: item.version,
|
|
145
|
-
displayName: item.displayName,
|
|
146
|
-
ownerHandle: item.ownerHandle,
|
|
147
|
-
files: item.files,
|
|
148
|
-
publishedAt: item.publishedAt,
|
|
149
|
-
},
|
|
150
|
-
context,
|
|
151
|
-
)
|
|
152
|
-
stats.skillsBackedUp += 1
|
|
153
|
-
}
|
|
154
|
-
} catch (error) {
|
|
155
|
-
console.error('GitHub backup sync failed', error)
|
|
156
|
-
stats.errors += 1
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if (!dryRun) {
|
|
161
|
-
await ctx.runMutation(internal.githubBackups.setGitHubBackupSyncStateInternal, {
|
|
162
|
-
cursor: isDone ? undefined : (cursor ?? undefined),
|
|
163
|
-
})
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if (isDone) break
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return { stats, cursor, isDone }
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
export const syncGitHubBackupsInternal = internalAction({
|
|
173
|
-
args: {
|
|
174
|
-
dryRun: v.optional(v.boolean()),
|
|
175
|
-
batchSize: v.optional(v.number()),
|
|
176
|
-
maxBatches: v.optional(v.number()),
|
|
177
|
-
},
|
|
178
|
-
handler: syncGitHubBackupsInternalHandler,
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
function clampInt(value: number, min: number, max: number) {
|
|
182
|
-
return Math.max(min, Math.min(max, Math.floor(value)))
|
|
183
|
-
}
|
package/convex/githubImport.ts
DELETED
|
@@ -1,317 +0,0 @@
|
|
|
1
|
-
import { ConvexError, v } from 'convex/values'
|
|
2
|
-
import { unzipSync } from 'fflate'
|
|
3
|
-
import semver from 'semver'
|
|
4
|
-
import { api, internal } from './_generated/api'
|
|
5
|
-
import type { Id } from './_generated/dataModel'
|
|
6
|
-
import type { ActionCtx } from './_generated/server'
|
|
7
|
-
import { action } from './_generated/server'
|
|
8
|
-
import { requireUserFromAction } from './lib/access'
|
|
9
|
-
import {
|
|
10
|
-
buildGitHubImportFileList,
|
|
11
|
-
computeDefaultSelectedPaths,
|
|
12
|
-
detectGitHubImportCandidates,
|
|
13
|
-
fetchGitHubZipBytes,
|
|
14
|
-
listTextFilesUnderCandidate,
|
|
15
|
-
normalizeRepoPath,
|
|
16
|
-
parseGitHubImportUrl,
|
|
17
|
-
resolveGitHubCommit,
|
|
18
|
-
stripGitHubZipRoot,
|
|
19
|
-
suggestDisplayName,
|
|
20
|
-
suggestVersion,
|
|
21
|
-
} from './lib/githubImport'
|
|
22
|
-
import { publishVersionForUser } from './lib/skillPublish'
|
|
23
|
-
import { sanitizePath } from './lib/skills'
|
|
24
|
-
|
|
25
|
-
const MAX_SELECTED_BYTES = 50 * 1024 * 1024
|
|
26
|
-
const MAX_UNZIPPED_BYTES = 80 * 1024 * 1024
|
|
27
|
-
const MAX_FILE_COUNT = 7_500
|
|
28
|
-
const MAX_SINGLE_FILE_BYTES = 10 * 1024 * 1024
|
|
29
|
-
|
|
30
|
-
export const previewGitHubImport = action({
|
|
31
|
-
args: { url: v.string() },
|
|
32
|
-
handler: async (ctx, args) => {
|
|
33
|
-
await requireUserFromAction(ctx)
|
|
34
|
-
|
|
35
|
-
const parsed = parseGitHubImportUrl(args.url)
|
|
36
|
-
const resolved = await resolveGitHubCommit(parsed, fetch)
|
|
37
|
-
const zipBytes = await fetchGitHubZipBytes(resolved, fetch)
|
|
38
|
-
const entries = unzipToEntries(zipBytes)
|
|
39
|
-
const stripped = stripGitHubZipRoot(entries)
|
|
40
|
-
const candidates = detectGitHubImportCandidates(stripped).filter((candidate) =>
|
|
41
|
-
isCandidateUnderResolvedPath(candidate.path, resolved.path),
|
|
42
|
-
)
|
|
43
|
-
if (candidates.length === 0) throw new ConvexError('No SKILL.md found in this repo')
|
|
44
|
-
|
|
45
|
-
return {
|
|
46
|
-
resolved,
|
|
47
|
-
candidates: candidates.map((candidate) => ({
|
|
48
|
-
path: candidate.path,
|
|
49
|
-
readmePath: candidate.readmePath,
|
|
50
|
-
name: candidate.name ?? null,
|
|
51
|
-
description: candidate.description ?? null,
|
|
52
|
-
})),
|
|
53
|
-
}
|
|
54
|
-
},
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
export const previewGitHubImportCandidate = action({
|
|
58
|
-
args: { url: v.string(), candidatePath: v.string() },
|
|
59
|
-
handler: async (ctx, args) => {
|
|
60
|
-
const { userId } = await requireUserFromAction(ctx)
|
|
61
|
-
|
|
62
|
-
const parsed = parseGitHubImportUrl(args.url)
|
|
63
|
-
const resolved = await resolveGitHubCommit(parsed, fetch)
|
|
64
|
-
const zipBytes = await fetchGitHubZipBytes(resolved, fetch)
|
|
65
|
-
const entries = unzipToEntries(zipBytes)
|
|
66
|
-
const stripped = stripGitHubZipRoot(entries)
|
|
67
|
-
|
|
68
|
-
const normalizedCandidatePath = normalizeRepoPath(args.candidatePath)
|
|
69
|
-
if (!isCandidateUnderResolvedPath(normalizedCandidatePath, resolved.path)) {
|
|
70
|
-
throw new ConvexError('Candidate path is outside the requested import scope')
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const candidates = detectGitHubImportCandidates(stripped).filter((candidate) =>
|
|
74
|
-
isCandidateUnderResolvedPath(candidate.path, resolved.path),
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
const candidate = candidates.find((item) => item.path === normalizedCandidatePath)
|
|
78
|
-
if (!candidate) throw new ConvexError('Candidate not found')
|
|
79
|
-
|
|
80
|
-
const files = listTextFilesUnderCandidate(stripped, candidate.path)
|
|
81
|
-
const defaultSelectedPaths = computeDefaultSelectedPaths({ candidate, files })
|
|
82
|
-
const fileList = buildGitHubImportFileList({
|
|
83
|
-
candidate,
|
|
84
|
-
files,
|
|
85
|
-
defaultSelectedPaths,
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
const baseForNaming = candidate.path ? (candidate.path.split('/').at(-1) ?? '') : resolved.repo
|
|
89
|
-
const suggestedDisplayName = suggestDisplayName(candidate, baseForNaming)
|
|
90
|
-
|
|
91
|
-
const rawSlugBase = sanitizeSlug(candidate.path ? baseForNaming : resolved.repo)
|
|
92
|
-
const suggestedSlug = await suggestAvailableSlug(ctx, userId, rawSlugBase)
|
|
93
|
-
|
|
94
|
-
const existing = await ctx.runQuery(api.skills.getBySlug, { slug: suggestedSlug })
|
|
95
|
-
const existingLatest =
|
|
96
|
-
existing?.skill && existing.skill.ownerUserId === userId
|
|
97
|
-
? (existing.latestVersion?.version ?? null)
|
|
98
|
-
: null
|
|
99
|
-
const suggestedVersion = suggestVersion(existingLatest)
|
|
100
|
-
|
|
101
|
-
return {
|
|
102
|
-
resolved,
|
|
103
|
-
candidate: {
|
|
104
|
-
path: candidate.path,
|
|
105
|
-
readmePath: candidate.readmePath,
|
|
106
|
-
name: candidate.name ?? null,
|
|
107
|
-
description: candidate.description ?? null,
|
|
108
|
-
},
|
|
109
|
-
defaults: {
|
|
110
|
-
selectedPaths: defaultSelectedPaths,
|
|
111
|
-
slug: suggestedSlug,
|
|
112
|
-
displayName: suggestedDisplayName,
|
|
113
|
-
version: suggestedVersion,
|
|
114
|
-
tags: ['latest'],
|
|
115
|
-
},
|
|
116
|
-
files: fileList,
|
|
117
|
-
}
|
|
118
|
-
},
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
export const importGitHubSkill = action({
|
|
122
|
-
args: {
|
|
123
|
-
url: v.string(),
|
|
124
|
-
commit: v.string(),
|
|
125
|
-
candidatePath: v.string(),
|
|
126
|
-
selectedPaths: v.array(v.string()),
|
|
127
|
-
slug: v.optional(v.string()),
|
|
128
|
-
displayName: v.optional(v.string()),
|
|
129
|
-
version: v.optional(v.string()),
|
|
130
|
-
tags: v.optional(v.array(v.string())),
|
|
131
|
-
},
|
|
132
|
-
handler: async (ctx, args) => {
|
|
133
|
-
const { userId } = await requireUserFromAction(ctx)
|
|
134
|
-
|
|
135
|
-
const parsed = parseGitHubImportUrl(args.url)
|
|
136
|
-
const resolved = await resolveGitHubCommit(parsed, fetch)
|
|
137
|
-
if (!/^[a-f0-9]{40}$/i.test(args.commit)) throw new ConvexError('Invalid commit')
|
|
138
|
-
if (args.commit.toLowerCase() !== resolved.commit.toLowerCase()) {
|
|
139
|
-
throw new ConvexError('Import is out of date. Re-run preview.')
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const normalizedCandidatePath = normalizeRepoPath(args.candidatePath)
|
|
143
|
-
if (!isCandidateUnderResolvedPath(normalizedCandidatePath, resolved.path)) {
|
|
144
|
-
throw new ConvexError('Candidate path is outside the requested import scope')
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const zipBytes = await fetchGitHubZipBytes(resolved, fetch)
|
|
148
|
-
const entries = stripGitHubZipRoot(unzipToEntries(zipBytes))
|
|
149
|
-
|
|
150
|
-
const candidates = detectGitHubImportCandidates(entries).filter((candidate) =>
|
|
151
|
-
isCandidateUnderResolvedPath(candidate.path, resolved.path),
|
|
152
|
-
)
|
|
153
|
-
const candidate = candidates.find((item) => item.path === normalizedCandidatePath)
|
|
154
|
-
if (!candidate) throw new ConvexError('Candidate not found')
|
|
155
|
-
|
|
156
|
-
const filesUnderCandidate = listTextFilesUnderCandidate(entries, candidate.path)
|
|
157
|
-
const byPath = new Map(filesUnderCandidate.map((file) => [file.path, file.bytes]))
|
|
158
|
-
|
|
159
|
-
const selected = Array.from(
|
|
160
|
-
new Set(args.selectedPaths.map((path) => normalizeRepoPath(path)).filter(Boolean)),
|
|
161
|
-
)
|
|
162
|
-
if (selected.length === 0) throw new ConvexError('No files selected')
|
|
163
|
-
|
|
164
|
-
const candidateRoot = candidate.path ? `${candidate.path}/` : ''
|
|
165
|
-
const normalizedReadmePath = normalizeRepoPath(candidate.readmePath)
|
|
166
|
-
if (!selected.includes(normalizedReadmePath)) {
|
|
167
|
-
throw new ConvexError('SKILL.md must be selected')
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
let totalBytes = 0
|
|
171
|
-
const storedFiles: Array<{
|
|
172
|
-
path: string
|
|
173
|
-
size: number
|
|
174
|
-
storageId: Id<'_storage'>
|
|
175
|
-
sha256: string
|
|
176
|
-
contentType?: string
|
|
177
|
-
}> = []
|
|
178
|
-
|
|
179
|
-
for (const path of selected.sort()) {
|
|
180
|
-
if (candidateRoot && !path.startsWith(candidateRoot)) {
|
|
181
|
-
throw new ConvexError('Selected file is outside the chosen skill folder')
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const bytes = byPath.get(path)
|
|
185
|
-
if (!bytes) continue
|
|
186
|
-
totalBytes += bytes.byteLength
|
|
187
|
-
if (totalBytes > MAX_SELECTED_BYTES) throw new ConvexError('Selected files exceed 50MB limit')
|
|
188
|
-
|
|
189
|
-
const relPath = candidateRoot ? path.slice(candidateRoot.length) : path
|
|
190
|
-
const sanitized = sanitizePath(relPath)
|
|
191
|
-
if (!sanitized) throw new ConvexError('Invalid file paths')
|
|
192
|
-
|
|
193
|
-
const sha256 = await sha256Hex(bytes)
|
|
194
|
-
const safeBytes = new Uint8Array(bytes)
|
|
195
|
-
const storageId = await ctx.storage.store(new Blob([safeBytes], { type: 'text/plain' }))
|
|
196
|
-
storedFiles.push({
|
|
197
|
-
path: sanitized,
|
|
198
|
-
size: bytes.byteLength,
|
|
199
|
-
storageId,
|
|
200
|
-
sha256,
|
|
201
|
-
contentType: 'text/plain',
|
|
202
|
-
})
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
if (storedFiles.length === 0) throw new ConvexError('No files selected')
|
|
206
|
-
|
|
207
|
-
const slugBase = (args.slug ?? '').trim().toLowerCase()
|
|
208
|
-
const displayName = (args.displayName ?? '').trim()
|
|
209
|
-
const tags = (args.tags ?? ['latest']).map((tag) => tag.trim()).filter(Boolean)
|
|
210
|
-
const version = (args.version ?? '').trim()
|
|
211
|
-
|
|
212
|
-
if (!slugBase) throw new ConvexError('Slug required')
|
|
213
|
-
if (!displayName) throw new ConvexError('Display name required')
|
|
214
|
-
if (!version || !semver.valid(version)) throw new ConvexError('Version must be valid semver')
|
|
215
|
-
|
|
216
|
-
const result = await publishVersionForUser(ctx, userId, {
|
|
217
|
-
slug: slugBase,
|
|
218
|
-
displayName,
|
|
219
|
-
version,
|
|
220
|
-
changelog: '',
|
|
221
|
-
tags,
|
|
222
|
-
files: storedFiles,
|
|
223
|
-
source: {
|
|
224
|
-
kind: 'github',
|
|
225
|
-
url: resolved.originalUrl,
|
|
226
|
-
repo: `${resolved.owner}/${resolved.repo}`,
|
|
227
|
-
ref: resolved.ref,
|
|
228
|
-
commit: resolved.commit,
|
|
229
|
-
path: candidate.path,
|
|
230
|
-
importedAt: Date.now(),
|
|
231
|
-
},
|
|
232
|
-
})
|
|
233
|
-
|
|
234
|
-
return { ok: true, slug: slugBase, version, ...result }
|
|
235
|
-
},
|
|
236
|
-
})
|
|
237
|
-
|
|
238
|
-
function unzipToEntries(zipBytes: Uint8Array) {
|
|
239
|
-
const entries = unzipSync(zipBytes)
|
|
240
|
-
const out: Record<string, Uint8Array> = {}
|
|
241
|
-
const rawPaths = Object.keys(entries)
|
|
242
|
-
if (rawPaths.length > MAX_FILE_COUNT) throw new ConvexError('Repo archive has too many files')
|
|
243
|
-
let totalBytes = 0
|
|
244
|
-
for (const [rawPath, bytes] of Object.entries(entries)) {
|
|
245
|
-
const normalizedPath = normalizeZipPath(rawPath)
|
|
246
|
-
if (!normalizedPath) continue
|
|
247
|
-
if (isJunkPath(normalizedPath)) continue
|
|
248
|
-
if (!bytes) continue
|
|
249
|
-
if (bytes.byteLength > MAX_SINGLE_FILE_BYTES) continue
|
|
250
|
-
totalBytes += bytes.byteLength
|
|
251
|
-
if (totalBytes > MAX_UNZIPPED_BYTES) throw new ConvexError('Repo archive is too large')
|
|
252
|
-
out[normalizedPath] = bytes
|
|
253
|
-
}
|
|
254
|
-
return out
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
function isCandidateUnderResolvedPath(candidatePath: string, resolvedPath: string) {
|
|
258
|
-
const root = normalizeRepoPath(resolvedPath)
|
|
259
|
-
if (!root) return true
|
|
260
|
-
if (!candidatePath) return false
|
|
261
|
-
if (candidatePath === root) return true
|
|
262
|
-
return candidatePath.startsWith(`${root}/`)
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
function sanitizeSlug(value: string) {
|
|
266
|
-
return value
|
|
267
|
-
.trim()
|
|
268
|
-
.toLowerCase()
|
|
269
|
-
.replace(/[^a-z0-9-]+/g, '-')
|
|
270
|
-
.replace(/^-+/, '')
|
|
271
|
-
.replace(/-+$/, '')
|
|
272
|
-
.replace(/--+/g, '-')
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
async function suggestAvailableSlug(ctx: ActionCtx, userId: Id<'users'>, base: string) {
|
|
276
|
-
const cleaned = sanitizeSlug(base)
|
|
277
|
-
if (!cleaned) throw new ConvexError('Could not derive slug')
|
|
278
|
-
for (let i = 0; i < 50; i += 1) {
|
|
279
|
-
const candidate = i === 0 ? cleaned : `${cleaned}-${i + 1}`
|
|
280
|
-
const existing = await ctx.runQuery(internal.skills.getSkillBySlugInternal, { slug: candidate })
|
|
281
|
-
if (!existing) return candidate
|
|
282
|
-
if (existing.ownerUserId === userId) return candidate
|
|
283
|
-
}
|
|
284
|
-
throw new ConvexError('Could not find an available slug')
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
async function sha256Hex(bytes: Uint8Array) {
|
|
288
|
-
const normalized = new Uint8Array(bytes)
|
|
289
|
-
const digest = await crypto.subtle.digest('SHA-256', normalized.buffer)
|
|
290
|
-
return toHex(new Uint8Array(digest))
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
function toHex(bytes: Uint8Array) {
|
|
294
|
-
let out = ''
|
|
295
|
-
for (const byte of bytes) out += byte.toString(16).padStart(2, '0')
|
|
296
|
-
return out
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
function normalizeZipPath(path: string) {
|
|
300
|
-
const normalized = path
|
|
301
|
-
.replaceAll('\u0000', '')
|
|
302
|
-
.replaceAll('\\', '/')
|
|
303
|
-
.trim()
|
|
304
|
-
.replace(/^\.\/+/, '')
|
|
305
|
-
.replace(/^\/+/, '')
|
|
306
|
-
if (!normalized) return ''
|
|
307
|
-
if (normalized.includes('..')) return ''
|
|
308
|
-
return normalized
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
function isJunkPath(path: string) {
|
|
312
|
-
const normalized = path.toLowerCase()
|
|
313
|
-
if (normalized.startsWith('__macosx/')) return true
|
|
314
|
-
if (normalized.endsWith('/.ds_store')) return true
|
|
315
|
-
if (normalized === '.ds_store') return true
|
|
316
|
-
return false
|
|
317
|
-
}
|