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
|
@@ -1,263 +0,0 @@
|
|
|
1
|
-
import { useAction, useMutation, useQuery } from 'convex/react'
|
|
2
|
-
import { useEffect, useMemo, useRef, useState } from 'react'
|
|
3
|
-
import ReactMarkdown from 'react-markdown'
|
|
4
|
-
import remarkGfm from 'remark-gfm'
|
|
5
|
-
import { api } from '../../convex/_generated/api'
|
|
6
|
-
import type { Doc } from '../../convex/_generated/dataModel'
|
|
7
|
-
import type { PublicSoul, PublicUser } from '../lib/publicUser'
|
|
8
|
-
import { isModerator } from '../lib/roles'
|
|
9
|
-
import { useAuthStatus } from '../lib/useAuthStatus'
|
|
10
|
-
|
|
11
|
-
type SoulDetailPageProps = {
|
|
12
|
-
slug: string
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
type SoulBySlugResult = {
|
|
16
|
-
soul: PublicSoul
|
|
17
|
-
latestVersion: Doc<'soulVersions'> | null
|
|
18
|
-
owner: PublicUser | null
|
|
19
|
-
} | null
|
|
20
|
-
|
|
21
|
-
export function SoulDetailPage({ slug }: SoulDetailPageProps) {
|
|
22
|
-
const { isAuthenticated, me } = useAuthStatus()
|
|
23
|
-
const result = useQuery(api.souls.getBySlug, { slug }) as SoulBySlugResult | undefined
|
|
24
|
-
const toggleStar = useMutation(api.soulStars.toggle)
|
|
25
|
-
const addComment = useMutation(api.soulComments.add)
|
|
26
|
-
const removeComment = useMutation(api.soulComments.remove)
|
|
27
|
-
const getReadme = useAction(api.souls.getReadme)
|
|
28
|
-
const ensureSoulSeeds = useAction(api.seed.ensureSoulSeeds)
|
|
29
|
-
const seedEnsuredRef = useRef(false)
|
|
30
|
-
const [readme, setReadme] = useState<string | null>(null)
|
|
31
|
-
const [readmeError, setReadmeError] = useState<string | null>(null)
|
|
32
|
-
const [comment, setComment] = useState('')
|
|
33
|
-
|
|
34
|
-
const isLoadingSoul = result === undefined
|
|
35
|
-
const soul = result?.soul
|
|
36
|
-
const owner = result?.owner
|
|
37
|
-
const latestVersion = result?.latestVersion
|
|
38
|
-
const versions = useQuery(
|
|
39
|
-
api.souls.listVersions,
|
|
40
|
-
soul ? { soulId: soul._id, limit: 50 } : 'skip',
|
|
41
|
-
) as Doc<'soulVersions'>[] | undefined
|
|
42
|
-
|
|
43
|
-
const isStarred = useQuery(
|
|
44
|
-
api.soulStars.isStarred,
|
|
45
|
-
isAuthenticated && soul ? { soulId: soul._id } : 'skip',
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
const comments = useQuery(
|
|
49
|
-
api.soulComments.listBySoul,
|
|
50
|
-
soul ? { soulId: soul._id, limit: 50 } : 'skip',
|
|
51
|
-
) as Array<{ comment: Doc<'soulComments'>; user: PublicUser | null }> | undefined
|
|
52
|
-
|
|
53
|
-
const readmeContent = useMemo(() => {
|
|
54
|
-
if (!readme) return null
|
|
55
|
-
return stripFrontmatter(readme)
|
|
56
|
-
}, [readme])
|
|
57
|
-
|
|
58
|
-
useEffect(() => {
|
|
59
|
-
if (seedEnsuredRef.current) return
|
|
60
|
-
seedEnsuredRef.current = true
|
|
61
|
-
void ensureSoulSeeds({})
|
|
62
|
-
}, [ensureSoulSeeds])
|
|
63
|
-
|
|
64
|
-
useEffect(() => {
|
|
65
|
-
if (!latestVersion) return
|
|
66
|
-
setReadme(null)
|
|
67
|
-
setReadmeError(null)
|
|
68
|
-
let cancelled = false
|
|
69
|
-
void getReadme({ versionId: latestVersion._id })
|
|
70
|
-
.then((data) => {
|
|
71
|
-
if (cancelled) return
|
|
72
|
-
setReadme(data.text)
|
|
73
|
-
})
|
|
74
|
-
.catch((error) => {
|
|
75
|
-
if (cancelled) return
|
|
76
|
-
setReadmeError(error instanceof Error ? error.message : 'Failed to load SOUL.md')
|
|
77
|
-
setReadme(null)
|
|
78
|
-
})
|
|
79
|
-
return () => {
|
|
80
|
-
cancelled = true
|
|
81
|
-
}
|
|
82
|
-
}, [latestVersion, getReadme])
|
|
83
|
-
|
|
84
|
-
if (isLoadingSoul) {
|
|
85
|
-
return (
|
|
86
|
-
<main className="section">
|
|
87
|
-
<div className="card">
|
|
88
|
-
<div className="loading-indicator">Loading soul…</div>
|
|
89
|
-
</div>
|
|
90
|
-
</main>
|
|
91
|
-
)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (result === null || !soul) {
|
|
95
|
-
return (
|
|
96
|
-
<main className="section">
|
|
97
|
-
<div className="card">Soul not found.</div>
|
|
98
|
-
</main>
|
|
99
|
-
)
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const ownerHandle = owner?.handle ?? owner?.name ?? null
|
|
103
|
-
const downloadBase = `${import.meta.env.VITE_CONVEX_SITE_URL}/api/v1/souls/${soul.slug}/file`
|
|
104
|
-
|
|
105
|
-
return (
|
|
106
|
-
<main className="section">
|
|
107
|
-
<div className="skill-detail-stack">
|
|
108
|
-
<div className="card skill-hero">
|
|
109
|
-
<div className="skill-hero-header">
|
|
110
|
-
<div className="skill-hero-title">
|
|
111
|
-
<h1 className="section-title" style={{ margin: 0 }}>
|
|
112
|
-
{soul.displayName}
|
|
113
|
-
</h1>
|
|
114
|
-
<p className="section-subtitle">{soul.summary ?? 'No summary provided.'}</p>
|
|
115
|
-
<div className="stat">
|
|
116
|
-
⭐ {soul.stats.stars} · ⤓ {soul.stats.downloads} · {soul.stats.versions} versions
|
|
117
|
-
</div>
|
|
118
|
-
{ownerHandle ? (
|
|
119
|
-
<div className="stat">
|
|
120
|
-
by <a href={`/u/${ownerHandle}`}>@{ownerHandle}</a>
|
|
121
|
-
</div>
|
|
122
|
-
) : null}
|
|
123
|
-
<div className="skill-actions">
|
|
124
|
-
{isAuthenticated ? (
|
|
125
|
-
<button
|
|
126
|
-
className={`star-toggle${isStarred ? ' is-active' : ''}`}
|
|
127
|
-
type="button"
|
|
128
|
-
onClick={() => void toggleStar({ soulId: soul._id })}
|
|
129
|
-
aria-label={isStarred ? 'Unstar soul' : 'Star soul'}
|
|
130
|
-
>
|
|
131
|
-
<span aria-hidden="true">★</span>
|
|
132
|
-
</button>
|
|
133
|
-
) : null}
|
|
134
|
-
</div>
|
|
135
|
-
</div>
|
|
136
|
-
<div className="skill-hero-cta">
|
|
137
|
-
<div className="skill-version-pill">
|
|
138
|
-
<span className="skill-version-label">Current version</span>
|
|
139
|
-
<strong>v{latestVersion?.version ?? '—'}</strong>
|
|
140
|
-
</div>
|
|
141
|
-
<a
|
|
142
|
-
className="btn btn-primary"
|
|
143
|
-
href={`${downloadBase}?path=SOUL.md`}
|
|
144
|
-
aria-label="Download SOUL.md"
|
|
145
|
-
>
|
|
146
|
-
Download SOUL.md
|
|
147
|
-
</a>
|
|
148
|
-
</div>
|
|
149
|
-
</div>
|
|
150
|
-
</div>
|
|
151
|
-
|
|
152
|
-
<div className="card">
|
|
153
|
-
<div className="skill-readme markdown">
|
|
154
|
-
{readmeContent ? (
|
|
155
|
-
<ReactMarkdown remarkPlugins={[remarkGfm]}>{readmeContent}</ReactMarkdown>
|
|
156
|
-
) : readmeError ? (
|
|
157
|
-
<div className="stat">Failed to load SOUL.md: {readmeError}</div>
|
|
158
|
-
) : (
|
|
159
|
-
<div className="loading-indicator">Loading SOUL.md…</div>
|
|
160
|
-
)}
|
|
161
|
-
</div>
|
|
162
|
-
</div>
|
|
163
|
-
|
|
164
|
-
<div className="card">
|
|
165
|
-
<h2 className="section-title" style={{ fontSize: '1.2rem', marginBottom: 8 }}>
|
|
166
|
-
Versions
|
|
167
|
-
</h2>
|
|
168
|
-
<div className="version-scroll">
|
|
169
|
-
<div className="version-list">
|
|
170
|
-
{(versions ?? []).map((version) => (
|
|
171
|
-
<div key={version._id} className="version-row">
|
|
172
|
-
<div className="version-info">
|
|
173
|
-
<div>
|
|
174
|
-
v{version.version} · {new Date(version.createdAt).toLocaleDateString()}
|
|
175
|
-
{version.changelogSource === 'auto' ? (
|
|
176
|
-
<span style={{ color: 'var(--ink-soft)' }}> · auto</span>
|
|
177
|
-
) : null}
|
|
178
|
-
</div>
|
|
179
|
-
<div style={{ color: '#5c554e', whiteSpace: 'pre-wrap' }}>
|
|
180
|
-
{version.changelog}
|
|
181
|
-
</div>
|
|
182
|
-
</div>
|
|
183
|
-
<div className="version-actions">
|
|
184
|
-
<a
|
|
185
|
-
className="btn version-zip"
|
|
186
|
-
href={`${downloadBase}?path=SOUL.md&version=${encodeURIComponent(
|
|
187
|
-
version.version,
|
|
188
|
-
)}`}
|
|
189
|
-
>
|
|
190
|
-
SOUL.md
|
|
191
|
-
</a>
|
|
192
|
-
</div>
|
|
193
|
-
</div>
|
|
194
|
-
))}
|
|
195
|
-
</div>
|
|
196
|
-
</div>
|
|
197
|
-
</div>
|
|
198
|
-
|
|
199
|
-
<div className="card">
|
|
200
|
-
<h2 className="section-title" style={{ fontSize: '1.2rem', margin: 0 }}>
|
|
201
|
-
Comments
|
|
202
|
-
</h2>
|
|
203
|
-
{isAuthenticated ? (
|
|
204
|
-
<form
|
|
205
|
-
onSubmit={(event) => {
|
|
206
|
-
event.preventDefault()
|
|
207
|
-
if (!comment.trim()) return
|
|
208
|
-
void addComment({ soulId: soul._id, body: comment.trim() }).then(() =>
|
|
209
|
-
setComment(''),
|
|
210
|
-
)
|
|
211
|
-
}}
|
|
212
|
-
className="comment-form"
|
|
213
|
-
>
|
|
214
|
-
<textarea
|
|
215
|
-
className="comment-input"
|
|
216
|
-
rows={4}
|
|
217
|
-
value={comment}
|
|
218
|
-
onChange={(event) => setComment(event.target.value)}
|
|
219
|
-
placeholder="Leave a note…"
|
|
220
|
-
/>
|
|
221
|
-
<button className="btn comment-submit" type="submit">
|
|
222
|
-
Post comment
|
|
223
|
-
</button>
|
|
224
|
-
</form>
|
|
225
|
-
) : (
|
|
226
|
-
<p className="section-subtitle">Sign in to comment.</p>
|
|
227
|
-
)}
|
|
228
|
-
<div style={{ display: 'grid', gap: 12, marginTop: 16 }}>
|
|
229
|
-
{(comments ?? []).length === 0 ? (
|
|
230
|
-
<div className="stat">No comments yet.</div>
|
|
231
|
-
) : (
|
|
232
|
-
(comments ?? []).map((entry) => (
|
|
233
|
-
<div key={entry.comment._id} className="stat" style={{ alignItems: 'flex-start' }}>
|
|
234
|
-
<div>
|
|
235
|
-
<strong>@{entry.user?.handle ?? entry.user?.name ?? 'user'}</strong>
|
|
236
|
-
<div style={{ color: '#5c554e' }}>{entry.comment.body}</div>
|
|
237
|
-
</div>
|
|
238
|
-
{isAuthenticated && me && (me._id === entry.comment.userId || isModerator(me)) ? (
|
|
239
|
-
<button
|
|
240
|
-
className="btn"
|
|
241
|
-
type="button"
|
|
242
|
-
onClick={() => void removeComment({ commentId: entry.comment._id })}
|
|
243
|
-
>
|
|
244
|
-
Delete
|
|
245
|
-
</button>
|
|
246
|
-
) : null}
|
|
247
|
-
</div>
|
|
248
|
-
))
|
|
249
|
-
)}
|
|
250
|
-
</div>
|
|
251
|
-
</div>
|
|
252
|
-
</div>
|
|
253
|
-
</main>
|
|
254
|
-
)
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
function stripFrontmatter(content: string) {
|
|
258
|
-
const normalized = content.replace(/\r\n/g, '\n').replace(/\r/g, '\n')
|
|
259
|
-
if (!normalized.startsWith('---')) return content
|
|
260
|
-
const endIndex = normalized.indexOf('\n---', 3)
|
|
261
|
-
if (endIndex === -1) return content
|
|
262
|
-
return normalized.slice(endIndex + 4).replace(/^\n+/, '')
|
|
263
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { useMutation } from 'convex/react'
|
|
2
|
-
import { useEffect, useRef } from 'react'
|
|
3
|
-
import { api } from '../../convex/_generated/api'
|
|
4
|
-
import { useAuthStatus } from '../lib/useAuthStatus'
|
|
5
|
-
|
|
6
|
-
export function UserBootstrap() {
|
|
7
|
-
const { isAuthenticated, isLoading } = useAuthStatus()
|
|
8
|
-
const ensureUser = useMutation(api.users.ensure)
|
|
9
|
-
const didRun = useRef(false)
|
|
10
|
-
|
|
11
|
-
useEffect(() => {
|
|
12
|
-
if (isLoading || !isAuthenticated || didRun.current) return
|
|
13
|
-
didRun.current = true
|
|
14
|
-
void ensureUser()
|
|
15
|
-
}, [isAuthenticated, isLoading, ensureUser])
|
|
16
|
-
|
|
17
|
-
return null
|
|
18
|
-
}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
|
|
2
|
-
import * as React from 'react'
|
|
3
|
-
import { cn } from '../../lib/utils'
|
|
4
|
-
|
|
5
|
-
const DropdownMenu = DropdownMenuPrimitive.Root
|
|
6
|
-
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
|
|
7
|
-
const DropdownMenuGroup = DropdownMenuPrimitive.Group
|
|
8
|
-
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
|
|
9
|
-
const DropdownMenuSub = DropdownMenuPrimitive.Sub
|
|
10
|
-
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
|
|
11
|
-
|
|
12
|
-
const DropdownMenuContent = React.forwardRef<
|
|
13
|
-
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
|
14
|
-
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
|
|
15
|
-
>(({ className, sideOffset = 8, ...props }, ref) => (
|
|
16
|
-
<DropdownMenuPrimitive.Portal>
|
|
17
|
-
<DropdownMenuPrimitive.Content
|
|
18
|
-
ref={ref}
|
|
19
|
-
sideOffset={sideOffset}
|
|
20
|
-
className={cn(
|
|
21
|
-
'z-50 min-w-[180px] rounded-xl border border-[color:var(--line)] bg-[color:var(--surface)] p-2 text-[color:var(--ink)] shadow-[var(--shadow)]',
|
|
22
|
-
className,
|
|
23
|
-
)}
|
|
24
|
-
{...props}
|
|
25
|
-
/>
|
|
26
|
-
</DropdownMenuPrimitive.Portal>
|
|
27
|
-
))
|
|
28
|
-
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
|
|
29
|
-
|
|
30
|
-
const DropdownMenuItem = React.forwardRef<
|
|
31
|
-
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
|
32
|
-
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item>
|
|
33
|
-
>(({ className, ...props }, ref) => (
|
|
34
|
-
<DropdownMenuPrimitive.Item
|
|
35
|
-
ref={ref}
|
|
36
|
-
className={cn(
|
|
37
|
-
'flex cursor-pointer select-none items-center gap-2 rounded-lg px-3 py-2 text-sm font-semibold text-[color:var(--ink)] outline-none transition-colors focus:bg-[color:rgba(255,107,74,0.12)] data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
|
38
|
-
className,
|
|
39
|
-
)}
|
|
40
|
-
{...props}
|
|
41
|
-
/>
|
|
42
|
-
))
|
|
43
|
-
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
|
|
44
|
-
|
|
45
|
-
const DropdownMenuSeparator = React.forwardRef<
|
|
46
|
-
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
|
47
|
-
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
|
|
48
|
-
>(({ className, ...props }, ref) => (
|
|
49
|
-
<DropdownMenuPrimitive.Separator
|
|
50
|
-
ref={ref}
|
|
51
|
-
className={cn('my-1 h-px bg-[color:var(--line)]', className)}
|
|
52
|
-
{...props}
|
|
53
|
-
/>
|
|
54
|
-
))
|
|
55
|
-
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
|
|
56
|
-
|
|
57
|
-
export {
|
|
58
|
-
DropdownMenu,
|
|
59
|
-
DropdownMenuTrigger,
|
|
60
|
-
DropdownMenuContent,
|
|
61
|
-
DropdownMenuItem,
|
|
62
|
-
DropdownMenuSeparator,
|
|
63
|
-
DropdownMenuGroup,
|
|
64
|
-
DropdownMenuPortal,
|
|
65
|
-
DropdownMenuSub,
|
|
66
|
-
DropdownMenuRadioGroup,
|
|
67
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import * as ToggleGroupPrimitive from '@radix-ui/react-toggle-group'
|
|
2
|
-
import * as React from 'react'
|
|
3
|
-
import { cn } from '../../lib/utils'
|
|
4
|
-
|
|
5
|
-
const ToggleGroup = React.forwardRef<
|
|
6
|
-
React.ElementRef<typeof ToggleGroupPrimitive.Root>,
|
|
7
|
-
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root>
|
|
8
|
-
>(({ className, ...props }, ref) => (
|
|
9
|
-
<ToggleGroupPrimitive.Root
|
|
10
|
-
ref={ref}
|
|
11
|
-
className={cn(
|
|
12
|
-
'inline-flex items-center gap-1 rounded-full border border-[color:var(--line)] bg-[color:var(--surface)] p-1',
|
|
13
|
-
className,
|
|
14
|
-
)}
|
|
15
|
-
{...props}
|
|
16
|
-
/>
|
|
17
|
-
))
|
|
18
|
-
ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName
|
|
19
|
-
|
|
20
|
-
const ToggleGroupItem = React.forwardRef<
|
|
21
|
-
React.ElementRef<typeof ToggleGroupPrimitive.Item>,
|
|
22
|
-
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item>
|
|
23
|
-
>(({ className, ...props }, ref) => (
|
|
24
|
-
<ToggleGroupPrimitive.Item
|
|
25
|
-
ref={ref}
|
|
26
|
-
className={cn(
|
|
27
|
-
'inline-flex h-9 w-9 items-center justify-center rounded-full text-[color:var(--ink-soft)] transition-colors hover:text-[color:var(--ink)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[color:rgba(255,107,74,0.4)] data-[state=on]:bg-[color:var(--accent)] data-[state=on]:text-white',
|
|
28
|
-
className,
|
|
29
|
-
)}
|
|
30
|
-
{...props}
|
|
31
|
-
/>
|
|
32
|
-
))
|
|
33
|
-
ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName
|
|
34
|
-
|
|
35
|
-
export { ToggleGroup, ToggleGroupItem }
|
package/src/convex/client.ts
DELETED
package/src/lib/badges.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import type { Doc, Id } from '../../convex/_generated/dataModel'
|
|
2
|
-
|
|
3
|
-
type BadgeKind = Doc<'skillBadges'>['kind']
|
|
4
|
-
|
|
5
|
-
type SkillBadgeMap = Partial<Record<BadgeKind, { byUserId: Id<'users'>; at: number }>>
|
|
6
|
-
|
|
7
|
-
type SkillLike = { badges?: SkillBadgeMap | null }
|
|
8
|
-
|
|
9
|
-
type BadgeLabel = 'Deprecated' | 'Official' | 'Highlighted'
|
|
10
|
-
|
|
11
|
-
export function isSkillHighlighted(skill: SkillLike) {
|
|
12
|
-
return Boolean(skill.badges?.highlighted)
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function isSkillOfficial(skill: SkillLike) {
|
|
16
|
-
return Boolean(skill.badges?.official)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function isSkillDeprecated(skill: SkillLike) {
|
|
20
|
-
return Boolean(skill.badges?.deprecated)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function getSkillBadges(skill: SkillLike): BadgeLabel[] {
|
|
24
|
-
const badges: BadgeLabel[] = []
|
|
25
|
-
if (isSkillDeprecated(skill)) badges.push('Deprecated')
|
|
26
|
-
if (isSkillOfficial(skill)) badges.push('Official')
|
|
27
|
-
if (isSkillHighlighted(skill)) badges.push('Highlighted')
|
|
28
|
-
return badges
|
|
29
|
-
}
|
package/src/lib/diffing.test.ts
DELETED
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import {
|
|
3
|
-
buildFileDiffList,
|
|
4
|
-
getDefaultDiffSelection,
|
|
5
|
-
resolveLatestVersionId,
|
|
6
|
-
resolvePreviousVersionId,
|
|
7
|
-
selectDefaultFilePath,
|
|
8
|
-
sortVersionsBySemver,
|
|
9
|
-
} from './diffing'
|
|
10
|
-
|
|
11
|
-
describe('diffing', () => {
|
|
12
|
-
it('sorts versions by semver descending', () => {
|
|
13
|
-
const ordered = sortVersionsBySemver([
|
|
14
|
-
{ id: 'a', version: '1.0.0' },
|
|
15
|
-
{ id: 'b', version: '2.0.0' },
|
|
16
|
-
{ id: 'c', version: '1.5.0' },
|
|
17
|
-
])
|
|
18
|
-
expect(ordered.map((entry) => entry.version)).toEqual(['2.0.0', '1.5.0', '1.0.0'])
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
it('sorts valid semver ahead of invalid entries', () => {
|
|
22
|
-
const ordered = sortVersionsBySemver([
|
|
23
|
-
{ id: 'a', version: 'not-a-version' },
|
|
24
|
-
{ id: 'b', version: '1.0.0' },
|
|
25
|
-
{ id: 'c', version: '2.0.0' },
|
|
26
|
-
])
|
|
27
|
-
expect(ordered.map((entry) => entry.version)).toEqual(['2.0.0', '1.0.0', 'not-a-version'])
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
it('sorts when only one entry is valid', () => {
|
|
31
|
-
const ordered = sortVersionsBySemver([
|
|
32
|
-
{ id: 'a', version: 'nope' },
|
|
33
|
-
{ id: 'b', version: '1.0.0' },
|
|
34
|
-
])
|
|
35
|
-
expect(ordered.map((entry) => entry.version)).toEqual(['1.0.0', 'nope'])
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
it('sorts invalid entries lexicographically', () => {
|
|
39
|
-
const ordered = sortVersionsBySemver([
|
|
40
|
-
{ id: 'a', version: 'beta' },
|
|
41
|
-
{ id: 'b', version: 'alpha' },
|
|
42
|
-
])
|
|
43
|
-
expect(ordered.map((entry) => entry.version)).toEqual(['alpha', 'beta'])
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
it('resolves latest from tag when present', () => {
|
|
47
|
-
const latestId = resolveLatestVersionId(
|
|
48
|
-
[
|
|
49
|
-
{ id: 'a', version: '1.0.0' },
|
|
50
|
-
{ id: 'b', version: '2.0.0' },
|
|
51
|
-
],
|
|
52
|
-
{ latest: 'a' },
|
|
53
|
-
)
|
|
54
|
-
expect(latestId).toBe('a')
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
it('returns null when no versions exist', () => {
|
|
58
|
-
const latestId = resolveLatestVersionId([], undefined)
|
|
59
|
-
expect(latestId).toBeNull()
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
it('resolves previous via semver predecessor', () => {
|
|
63
|
-
const latestId = 'b'
|
|
64
|
-
const previousId = resolvePreviousVersionId(
|
|
65
|
-
[
|
|
66
|
-
{ id: 'a', version: '1.0.0' },
|
|
67
|
-
{ id: 'b', version: '2.0.0' },
|
|
68
|
-
{ id: 'c', version: '1.5.0' },
|
|
69
|
-
],
|
|
70
|
-
latestId,
|
|
71
|
-
)
|
|
72
|
-
expect(previousId).toBe('c')
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
it('falls back to second entry when latest missing', () => {
|
|
76
|
-
const previousId = resolvePreviousVersionId(
|
|
77
|
-
[
|
|
78
|
-
{ id: 'a', version: '2.0.0' },
|
|
79
|
-
{ id: 'b', version: '1.0.0' },
|
|
80
|
-
{ id: 'c', version: '0.5.0' },
|
|
81
|
-
],
|
|
82
|
-
'missing',
|
|
83
|
-
)
|
|
84
|
-
expect(previousId).toBe('b')
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
it('returns default selection previous vs latest', () => {
|
|
88
|
-
const selection = getDefaultDiffSelection(
|
|
89
|
-
[
|
|
90
|
-
{ id: 'a', version: '1.0.0' },
|
|
91
|
-
{ id: 'b', version: '2.0.0' },
|
|
92
|
-
],
|
|
93
|
-
{ latest: 'b' },
|
|
94
|
-
)
|
|
95
|
-
expect(selection).toEqual({ leftId: 'a', rightId: 'b' })
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
it('builds file diff list with statuses', () => {
|
|
99
|
-
const diff = buildFileDiffList(
|
|
100
|
-
[
|
|
101
|
-
{ path: 'SKILL.md', sha256: 'aaa', size: 10 },
|
|
102
|
-
{ path: 'a.ts', sha256: 'bbb', size: 10 },
|
|
103
|
-
],
|
|
104
|
-
[
|
|
105
|
-
{ path: 'SKILL.md', sha256: 'aaa', size: 10 },
|
|
106
|
-
{ path: 'b.ts', sha256: 'ccc', size: 10 },
|
|
107
|
-
{ path: 'a.ts', sha256: 'ddd', size: 10 },
|
|
108
|
-
],
|
|
109
|
-
)
|
|
110
|
-
const statusByPath = Object.fromEntries(diff.map((item) => [item.path, item.status]))
|
|
111
|
-
expect(statusByPath['SKILL.md']).toBe('same')
|
|
112
|
-
expect(statusByPath['a.ts']).toBe('changed')
|
|
113
|
-
expect(statusByPath['b.ts']).toBe('added')
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
it('orders file diff list by change status then path', () => {
|
|
117
|
-
const diff = buildFileDiffList(
|
|
118
|
-
[
|
|
119
|
-
{ path: 'c.txt', sha256: 'aaa', size: 1 },
|
|
120
|
-
{ path: 'a.txt', sha256: 'bbb', size: 1 },
|
|
121
|
-
],
|
|
122
|
-
[
|
|
123
|
-
{ path: 'a.txt', sha256: 'ccc', size: 1 },
|
|
124
|
-
{ path: 'b.txt', sha256: 'ddd', size: 1 },
|
|
125
|
-
],
|
|
126
|
-
)
|
|
127
|
-
expect(diff.map((item) => item.path)).toEqual(['a.txt', 'b.txt', 'c.txt'])
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
it('orders file diff list alphabetically for same status', () => {
|
|
131
|
-
const diff = buildFileDiffList(
|
|
132
|
-
[
|
|
133
|
-
{ path: 'b.txt', sha256: 'aaa', size: 1 },
|
|
134
|
-
{ path: 'a.txt', sha256: 'bbb', size: 1 },
|
|
135
|
-
],
|
|
136
|
-
[
|
|
137
|
-
{ path: 'b.txt', sha256: 'aaa', size: 1 },
|
|
138
|
-
{ path: 'a.txt', sha256: 'bbb', size: 1 },
|
|
139
|
-
],
|
|
140
|
-
)
|
|
141
|
-
expect(diff.map((item) => item.path)).toEqual(['a.txt', 'b.txt'])
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
it('selects SKILL.md as default file when present', () => {
|
|
145
|
-
const path = selectDefaultFilePath([
|
|
146
|
-
{ path: 'notes.md', status: 'changed' },
|
|
147
|
-
{ path: 'SKILL.md', status: 'same' },
|
|
148
|
-
])
|
|
149
|
-
expect(path).toBe('SKILL.md')
|
|
150
|
-
})
|
|
151
|
-
|
|
152
|
-
it('falls back to first changed file when SKILL.md missing', () => {
|
|
153
|
-
const path = selectDefaultFilePath([
|
|
154
|
-
{ path: 'alpha.txt', status: 'same' },
|
|
155
|
-
{ path: 'beta.txt', status: 'changed' },
|
|
156
|
-
])
|
|
157
|
-
expect(path).toBe('beta.txt')
|
|
158
|
-
})
|
|
159
|
-
|
|
160
|
-
it('returns null when no file entries exist', () => {
|
|
161
|
-
expect(selectDefaultFilePath([])).toBeNull()
|
|
162
|
-
})
|
|
163
|
-
})
|
package/src/lib/diffing.ts
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import semver from 'semver'
|
|
2
|
-
|
|
3
|
-
export const MAX_DIFF_FILE_BYTES = 200 * 1024
|
|
4
|
-
|
|
5
|
-
type TagMap<IdType extends string> = Record<string, IdType>
|
|
6
|
-
|
|
7
|
-
export type VersionEntry<IdType extends string = string> = {
|
|
8
|
-
id: IdType
|
|
9
|
-
version: string
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export type FileMeta = {
|
|
13
|
-
path: string
|
|
14
|
-
sha256: string
|
|
15
|
-
size: number
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export type FileDiffStatus = 'added' | 'removed' | 'changed' | 'same'
|
|
19
|
-
|
|
20
|
-
export type FileDiffItem = {
|
|
21
|
-
path: string
|
|
22
|
-
status: FileDiffStatus
|
|
23
|
-
left?: FileMeta
|
|
24
|
-
right?: FileMeta
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function sortVersionsBySemver<IdType extends string>(versions: VersionEntry<IdType>[]) {
|
|
28
|
-
return [...versions].sort((a, b) => {
|
|
29
|
-
const aValid = Boolean(semver.valid(a.version))
|
|
30
|
-
const bValid = Boolean(semver.valid(b.version))
|
|
31
|
-
if (aValid && bValid) return semver.rcompare(a.version, b.version)
|
|
32
|
-
if (aValid) return -1
|
|
33
|
-
if (bValid) return 1
|
|
34
|
-
return a.version.localeCompare(b.version)
|
|
35
|
-
})
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function resolveLatestVersionId<IdType extends string>(
|
|
39
|
-
versions: VersionEntry<IdType>[],
|
|
40
|
-
tags?: TagMap<IdType>,
|
|
41
|
-
) {
|
|
42
|
-
if (tags?.latest) return tags.latest
|
|
43
|
-
return sortVersionsBySemver(versions)[0]?.id ?? null
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function resolvePreviousVersionId<IdType extends string>(
|
|
47
|
-
versions: VersionEntry<IdType>[],
|
|
48
|
-
latestId?: IdType | null,
|
|
49
|
-
) {
|
|
50
|
-
const ordered = sortVersionsBySemver(versions)
|
|
51
|
-
if (!latestId) return ordered[1]?.id ?? null
|
|
52
|
-
const latestIndex = ordered.findIndex((entry) => entry.id === latestId)
|
|
53
|
-
if (latestIndex === -1) return ordered[1]?.id ?? null
|
|
54
|
-
return ordered[latestIndex + 1]?.id ?? null
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export function getDefaultDiffSelection<IdType extends string>(
|
|
58
|
-
versions: VersionEntry<IdType>[],
|
|
59
|
-
tags?: TagMap<IdType>,
|
|
60
|
-
) {
|
|
61
|
-
const latestId = resolveLatestVersionId(versions, tags)
|
|
62
|
-
const previousId = resolvePreviousVersionId(versions, latestId)
|
|
63
|
-
return {
|
|
64
|
-
leftId: previousId ?? latestId ?? null,
|
|
65
|
-
rightId: latestId ?? previousId ?? null,
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export function buildFileDiffList(leftFiles: FileMeta[], rightFiles: FileMeta[]) {
|
|
70
|
-
const entries = new Map<string, { left?: FileMeta; right?: FileMeta }>()
|
|
71
|
-
for (const file of leftFiles) {
|
|
72
|
-
entries.set(file.path, { left: file })
|
|
73
|
-
}
|
|
74
|
-
for (const file of rightFiles) {
|
|
75
|
-
const existing = entries.get(file.path) ?? {}
|
|
76
|
-
entries.set(file.path, { ...existing, right: file })
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const statusRank: Record<FileDiffStatus, number> = {
|
|
80
|
-
changed: 0,
|
|
81
|
-
added: 1,
|
|
82
|
-
removed: 2,
|
|
83
|
-
same: 3,
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return Array.from(entries.entries())
|
|
87
|
-
.map(([path, info]) => {
|
|
88
|
-
let status: FileDiffStatus = 'same'
|
|
89
|
-
if (info.left && !info.right) status = 'removed'
|
|
90
|
-
else if (!info.left && info.right) status = 'added'
|
|
91
|
-
else if (info.left?.sha256 !== info.right?.sha256) status = 'changed'
|
|
92
|
-
return { path, status, left: info.left, right: info.right }
|
|
93
|
-
})
|
|
94
|
-
.sort((a, b) => {
|
|
95
|
-
const statusDiff = statusRank[a.status] - statusRank[b.status]
|
|
96
|
-
if (statusDiff !== 0) return statusDiff
|
|
97
|
-
return a.path.localeCompare(b.path)
|
|
98
|
-
})
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export function selectDefaultFilePath(items: FileDiffItem[]) {
|
|
102
|
-
const readme = items.find((item) => item.path.toLowerCase() === 'skill.md')
|
|
103
|
-
if (readme) return readme.path
|
|
104
|
-
const changed = items.find((item) => item.status !== 'same')
|
|
105
|
-
return changed?.path ?? items[0]?.path ?? null
|
|
106
|
-
}
|