@tscircuit/fake-snippets 0.0.104 → 0.0.106

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,70 @@
1
+ import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
2
+ import { z } from "zod"
3
+ import { publicMapPackageBuild } from "fake-snippets-api/lib/public-mapping/public-map-package-build"
4
+
5
+ export default withRouteSpec({
6
+ methods: ["GET"],
7
+ auth: "session",
8
+ queryParams: z.object({
9
+ package_build_id: z.string(),
10
+ }),
11
+ jsonResponse: z.object({
12
+ package_build: z.any(),
13
+ }),
14
+ })(async (req, ctx) => {
15
+ const { package_build_id } = req.query
16
+
17
+ if (!package_build_id) {
18
+ return ctx.error(400, {
19
+ error_code: "invalid_request",
20
+ message: "package_build_id is required",
21
+ })
22
+ }
23
+
24
+ const packageBuild = ctx.db.packageBuilds.find(
25
+ (build) => build.package_build_id === package_build_id,
26
+ )
27
+
28
+ if (!packageBuild) {
29
+ return ctx.error(404, {
30
+ error_code: "package_build_not_found",
31
+ message: "Package build not found",
32
+ })
33
+ }
34
+
35
+ const packageRelease = ctx.db.packageReleases.find(
36
+ (pr) => pr.package_release_id === packageBuild.package_release_id,
37
+ )
38
+
39
+ if (!packageRelease) {
40
+ return ctx.error(404, {
41
+ error_code: "package_release_not_found",
42
+ message: "Package release not found",
43
+ })
44
+ }
45
+
46
+ const pkg = ctx.db.packages.find(
47
+ (p) => p.package_id === packageRelease.package_id,
48
+ )
49
+ if (!pkg) {
50
+ return ctx.error(404, {
51
+ error_code: "package_not_found",
52
+ message: "Package not found",
53
+ })
54
+ }
55
+
56
+ if (pkg.creator_account_id !== ctx.auth.account_id) {
57
+ return ctx.error(403, {
58
+ error_code: "unauthorized",
59
+ message: "You are not authorized to access this package build",
60
+ })
61
+ }
62
+
63
+ const publicBuild = publicMapPackageBuild(packageBuild, {
64
+ include_logs: true,
65
+ })
66
+
67
+ return ctx.json({
68
+ package_build: publicBuild,
69
+ })
70
+ })
@@ -0,0 +1,109 @@
1
+ import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
2
+ import { z } from "zod"
3
+ import { publicMapPackageBuild } from "fake-snippets-api/lib/public-mapping/public-map-package-build"
4
+
5
+ export default withRouteSpec({
6
+ methods: ["GET"],
7
+ auth: "session",
8
+ queryParams: z.object({
9
+ package_id: z.string().optional(),
10
+ package_release_id: z.string().optional(),
11
+ }),
12
+ jsonResponse: z.object({
13
+ package_build: z.any().nullable(),
14
+ }),
15
+ })(async (req, ctx) => {
16
+ const { package_id, package_release_id } = req.query
17
+
18
+ if (!package_id && !package_release_id) {
19
+ return ctx.error(400, {
20
+ error_code: "invalid_request",
21
+ message: "Either package_id or package_release_id must be provided",
22
+ })
23
+ }
24
+
25
+ let targetPackageId = package_id
26
+
27
+ if (package_release_id) {
28
+ const packageRelease = ctx.db.packageReleases.find(
29
+ (pr) => pr.package_release_id === package_release_id,
30
+ )
31
+ if (!packageRelease) {
32
+ return ctx.error(404, {
33
+ error_code: "package_release_not_found",
34
+ message: "Package release not found",
35
+ })
36
+ }
37
+ targetPackageId = packageRelease.package_id
38
+ }
39
+
40
+ if (targetPackageId) {
41
+ const pkg = ctx.db.packages.find((p) => p.package_id === targetPackageId)
42
+ if (!pkg) {
43
+ return ctx.error(404, {
44
+ error_code: "package_not_found",
45
+ message: "Package not found",
46
+ })
47
+ }
48
+ if (pkg.creator_account_id !== ctx.auth.account_id) {
49
+ console.log(
50
+ pkg.creator_account_id !== ctx.auth.account_id,
51
+ pkg.creator_account_id,
52
+ ctx.auth.account_id,
53
+ )
54
+ return ctx.error(403, {
55
+ error_code: "unauthorized",
56
+ message: "You are not authorized to access this package",
57
+ })
58
+ }
59
+ }
60
+
61
+ let builds = ctx.db.packageBuilds
62
+
63
+ if (package_id) {
64
+ const packageReleases = ctx.db.packageReleases.filter(
65
+ (x) => x.package_id === package_id,
66
+ )
67
+ if (packageReleases.length === 0) {
68
+ return ctx.error(404, {
69
+ error_code: "package_not_found",
70
+ message: "Package not found",
71
+ })
72
+ }
73
+
74
+ const packageReleaseIds = packageReleases
75
+ .filter((pr) => pr.package_id === package_id)
76
+ .map((pr) => pr.package_release_id)
77
+
78
+ builds = builds.filter((build) =>
79
+ packageReleaseIds.includes(build.package_release_id),
80
+ )
81
+ }
82
+
83
+ if (package_release_id) {
84
+ builds = builds.filter(
85
+ (build) => build.package_release_id === package_release_id,
86
+ )
87
+ }
88
+
89
+ builds = builds.sort(
90
+ (a, b) =>
91
+ new Date(b.created_at).getTime() - new Date(a.created_at).getTime(),
92
+ )
93
+
94
+ const latestBuild = builds[0] || null
95
+
96
+ if (!latestBuild) {
97
+ return ctx.json({
98
+ package_build: null,
99
+ })
100
+ }
101
+
102
+ const publicBuild = publicMapPackageBuild(latestBuild, {
103
+ include_logs: true,
104
+ })
105
+
106
+ return ctx.json({
107
+ package_build: publicBuild,
108
+ })
109
+ })
@@ -0,0 +1,98 @@
1
+ import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
2
+ import { z } from "zod"
3
+ import { publicMapPackageBuild } from "fake-snippets-api/lib/public-mapping/public-map-package-build"
4
+
5
+ export default withRouteSpec({
6
+ methods: ["GET"],
7
+ auth: "session",
8
+ queryParams: z.object({
9
+ package_id: z.string().optional(),
10
+ package_release_id: z.string().optional(),
11
+ }),
12
+ jsonResponse: z.object({
13
+ package_builds: z.array(z.any()),
14
+ }),
15
+ })(async (req, ctx) => {
16
+ const { package_id, package_release_id } = req.query
17
+ console.log(ctx.db.packageBuilds)
18
+ if (!package_id && !package_release_id) {
19
+ return ctx.error(400, {
20
+ error_code: "invalid_request",
21
+ message: "Either package_id or package_release_id must be provided",
22
+ })
23
+ }
24
+
25
+ let targetPackageId = package_id
26
+
27
+ if (package_release_id) {
28
+ const packageRelease = ctx.db.packageReleases.find(
29
+ (pr) => pr.package_release_id === package_release_id,
30
+ )
31
+ if (!packageRelease) {
32
+ return ctx.error(404, {
33
+ error_code: "package_release_not_found",
34
+ message: "Package release not found",
35
+ })
36
+ }
37
+ targetPackageId = packageRelease.package_id
38
+ }
39
+
40
+ if (targetPackageId) {
41
+ const pkg = ctx.db.packages.find((p) => p.package_id === targetPackageId)
42
+ if (!pkg) {
43
+ return ctx.error(404, {
44
+ error_code: "package_not_found",
45
+ message: "Package not found",
46
+ })
47
+ }
48
+ if (pkg.creator_account_id !== ctx.auth.account_id) {
49
+ return ctx.error(403, {
50
+ error_code: "unauthorized",
51
+ message: "You are not authorized to access this package",
52
+ })
53
+ }
54
+ }
55
+
56
+ let builds = ctx.db.packageBuilds
57
+
58
+ if (package_id) {
59
+ const packageReleases = ctx.db.packageReleases.filter(
60
+ (x) => x.package_id === package_id,
61
+ )
62
+ if (packageReleases.length === 0) {
63
+ return ctx.error(404, {
64
+ error_code: "package_not_found",
65
+ message: "Package not found",
66
+ })
67
+ }
68
+
69
+ const packageReleaseIds = packageReleases
70
+ .filter((pr) => pr.package_id === package_id)
71
+ .map((pr) => pr.package_release_id)
72
+
73
+ builds = builds.filter((build) =>
74
+ packageReleaseIds.includes(build.package_release_id),
75
+ )
76
+ }
77
+
78
+ if (package_release_id) {
79
+ builds = builds.filter(
80
+ (build) => build.package_release_id === package_release_id,
81
+ )
82
+ }
83
+
84
+ builds = builds.sort(
85
+ (a, b) =>
86
+ new Date(b.created_at).getTime() - new Date(a.created_at).getTime(),
87
+ )
88
+
89
+ const publicBuilds = builds.map((build) =>
90
+ publicMapPackageBuild(build, {
91
+ include_logs: false,
92
+ }),
93
+ )
94
+
95
+ return ctx.json({
96
+ package_builds: publicBuilds,
97
+ })
98
+ })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/fake-snippets",
3
- "version": "0.0.104",
3
+ "version": "0.0.106",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
package/src/App.tsx CHANGED
@@ -143,7 +143,8 @@ class ErrorBoundary extends React.Component<
143
143
  this.cleanup() // Clean up listeners before reload
144
144
  this.setState({ reloading: true })
145
145
  this.reloadTimeout = window.setTimeout(() => {
146
- // window.location.reload()
146
+ if (window?.location.href.includes("localhost:")) return
147
+ window.location.reload()
147
148
  }, 500)
148
149
  }
149
150
 
@@ -5,12 +5,9 @@ import { usePackageReleaseById } from "@/hooks/use-package-release"
5
5
  import { timeAgo } from "@/lib/utils/timeAgo"
6
6
  import { BuildStatus, BuildStep } from "./build-status"
7
7
  import type { PackageRelease } from "fake-snippets-api/lib/db/schema"
8
- import {
9
- getBuildStatus,
10
- getLatestBuildFromPackageRelease,
11
- StatusIcon,
12
- } from "@/components/preview"
8
+ import { getBuildStatus, StatusIcon } from "@/components/preview"
13
9
  import { PrefetchPageLink } from "@/components/PrefetchPageLink"
10
+ import { useLatestPackageBuildByReleaseId } from "@/hooks/use-package-builds"
14
11
 
15
12
  function getTranspilationStatus(
16
13
  pr?: PackageRelease | null,
@@ -45,6 +42,9 @@ export default function SidebarReleasesSection() {
45
42
  const { data: packageRelease } = usePackageReleaseById(
46
43
  packageInfo?.latest_package_release_id,
47
44
  )
45
+ const { data: latestBuild } = useLatestPackageBuildByReleaseId(
46
+ packageRelease?.package_release_id,
47
+ )
48
48
 
49
49
  const buildSteps: BuildStep[] = [
50
50
  {
@@ -74,8 +74,9 @@ export default function SidebarReleasesSection() {
74
74
  )
75
75
  }
76
76
 
77
- const latestBuild = getLatestBuildFromPackageRelease(packageRelease)
78
- const { status, label } = getBuildStatus(latestBuild)
77
+ const { status, label } = latestBuild
78
+ ? getBuildStatus(latestBuild)
79
+ : { status: "pending", label: "pending" }
79
80
  return (
80
81
  <div className="mb-6">
81
82
  <h2 className="text-lg font-semibold mb-2">Releases</h2>
@@ -1,17 +1,24 @@
1
- import { useRef } from "react"
1
+ import { useRef, useState, useMemo } from "react"
2
+ import { Check, ChevronsUpDown } from "lucide-react"
2
3
  import {
3
- Select,
4
- SelectContent,
5
- SelectItem,
6
- SelectTrigger,
7
- SelectValue,
8
- } from "@/components/ui/select"
4
+ Command,
5
+ CommandEmpty,
6
+ CommandGroup,
7
+ CommandInput,
8
+ CommandItem,
9
+ } from "@/components/ui/command"
10
+ import {
11
+ Popover,
12
+ PopoverContent,
13
+ PopoverTrigger,
14
+ } from "@/components/ui/popover"
15
+ import { cn } from "@/lib/utils"
9
16
  import { useAxios } from "@/hooks/use-axios"
10
17
  import { useApiBaseUrl } from "@/hooks/use-packages-base-api-url"
11
18
  import { useQuery } from "react-query"
12
19
  import { Button } from "../ui/button"
13
20
  import { Label } from "../ui/label"
14
- import { Minus, Plus } from "lucide-react"
21
+ import { Minus, Plus, RefreshCw } from "lucide-react"
15
22
  import { Switch } from "../ui/switch"
16
23
 
17
24
  interface GitHubRepositorySelectorProps {
@@ -36,8 +43,15 @@ export const GitHubRepositorySelector = ({
36
43
  const axios = useAxios()
37
44
  const apiBaseUrl = useApiBaseUrl()
38
45
  const initialValue = useRef(selectedRepository).current
46
+ const [comboboxOpen, setComboboxOpen] = useState(false)
47
+ const [searchValue, setSearchValue] = useState("")
39
48
  // Fetch available repositories
40
- const { data: repositoriesData, error: repositoriesError } = useQuery(
49
+ const {
50
+ data: repositoriesData,
51
+ error: repositoriesError,
52
+ refetch: refetchRepositories,
53
+ isLoading,
54
+ } = useQuery(
41
55
  ["github-repositories"],
42
56
  async () => {
43
57
  const response = await axios.get("/github/repos/list_available")
@@ -53,20 +67,103 @@ export const GitHubRepositorySelector = ({
53
67
  window.location.href = `${apiBaseUrl}/github/installations/create_new_installation_redirect?return_to_page=${window.location.pathname}`
54
68
  }
55
69
 
56
- const handleValueChange = (newValue: string) => {
57
- if (newValue === "connect-more") {
70
+ const handleRefreshRepositories = async () => {
71
+ try {
72
+ // First call the refresh endpoint to update repositories
73
+ await axios.post("/github/repos/refresh")
74
+ // Then refetch the repositories list
75
+ refetchRepositories()
76
+ } catch (error) {
77
+ console.error("Failed to refresh repositories:", error)
78
+ // Still try to refetch in case the error is not critical
79
+ refetchRepositories()
80
+ }
81
+ }
82
+
83
+ // Create searchable options for the combobox
84
+ const comboboxOptions = useMemo(() => {
85
+ const repos = repositoriesData?.repos || []
86
+ const repoOptions = repos.map((repo: any) => ({
87
+ value: repo.full_name,
88
+ label: repo.unscoped_name,
89
+ isPrivate: repo.private,
90
+ type: "repo" as const,
91
+ }))
92
+
93
+ const specialOptions = [
94
+ {
95
+ value: "connect-more",
96
+ label: "Connect More Repos",
97
+ type: "special" as const,
98
+ icon: "plus" as const,
99
+ },
100
+ ...(initialValue
101
+ ? [
102
+ {
103
+ value: "unlink//repo",
104
+ label: "Unlink Repo",
105
+ type: "special" as const,
106
+ icon: "minus" as const,
107
+ },
108
+ ]
109
+ : []),
110
+ ]
111
+
112
+ return [...repoOptions, ...specialOptions]
113
+ }, [repositoriesData?.repos, initialValue])
114
+
115
+ // Filter options based on search
116
+ const filteredOptions = useMemo(() => {
117
+ if (!searchValue) return comboboxOptions
118
+ return comboboxOptions.filter(
119
+ (option) =>
120
+ option.label.toLowerCase().includes(searchValue.toLowerCase()) ||
121
+ option.value.toLowerCase().includes(searchValue.toLowerCase()),
122
+ )
123
+ }, [comboboxOptions, searchValue])
124
+
125
+ const handleComboboxSelect = (value: string) => {
126
+ if (value === "connect-more") {
58
127
  handleConnectMoreRepos()
59
- } else if (newValue === "unlink//repo") {
60
- setSelectedRepository?.("unlink//repo")
61
128
  } else {
62
- setSelectedRepository?.(newValue)
129
+ setSelectedRepository?.(value)
63
130
  }
131
+ setComboboxOpen(false)
132
+ setSearchValue("")
133
+ }
134
+
135
+ const getDisplayValue = () => {
136
+ if (!selectedRepository) return "Select a repository"
137
+ const option = comboboxOptions.find(
138
+ (opt) => opt.value === selectedRepository,
139
+ )
140
+ return option?.label || selectedRepository
64
141
  }
65
142
 
66
143
  return (
67
144
  <>
68
145
  <div className="space-y-1 mb-3">
69
- <Label htmlFor="repository">GitHub Repository</Label>
146
+ <div className="flex items-center justify-between">
147
+ <Label htmlFor="repository">GitHub Repository</Label>
148
+ {!(
149
+ (repositoriesError as any)?.response?.status === 400 &&
150
+ (repositoriesError as any)?.response?.data?.error_code ===
151
+ "github_not_connected"
152
+ ) && (
153
+ <Button
154
+ type="button"
155
+ variant="ghost"
156
+ size="sm"
157
+ onClick={handleRefreshRepositories}
158
+ disabled={disabled || isLoading}
159
+ className="h-auto p-1"
160
+ >
161
+ <RefreshCw
162
+ className={`w-3 h-3 ${isLoading ? "animate-spin" : ""}`}
163
+ />
164
+ </Button>
165
+ )}
166
+ </div>
70
167
  {(repositoriesError as any)?.response?.status === 400 &&
71
168
  (repositoriesError as any)?.response?.data?.error_code ===
72
169
  "github_not_connected" ? (
@@ -93,43 +190,79 @@ export const GitHubRepositorySelector = ({
93
190
  </div>
94
191
  ) : (
95
192
  <div className="space-y-2">
96
- <Select
97
- value={selectedRepository}
98
- onValueChange={handleValueChange}
99
- disabled={disabled}
100
- >
101
- <SelectTrigger className="w-full">
102
- <SelectValue placeholder="Select a repository" />
103
- </SelectTrigger>
104
- <SelectContent className="!z-[999]">
105
- {repositoriesData?.repos?.map((repo: any) => (
106
- <SelectItem key={repo.full_name} value={repo.full_name}>
107
- <div className="flex items-center space-x-2">
108
- <span>{repo.unscoped_name}</span>
109
- {repo.private && (
110
- <span className="text-xs text-muted-foreground">
111
- (private)
112
- </span>
113
- )}
114
- </div>
115
- </SelectItem>
116
- ))}
117
- <SelectItem value="connect-more">
118
- <div className="flex items-center space-x-2 text-blue-600">
119
- <Plus className="w-3 h-3" />
120
- <span>Connect More Repos</span>
121
- </div>
122
- </SelectItem>
123
- {Boolean(initialValue) && (
124
- <SelectItem value="unlink//repo">
125
- <div className="flex items-center space-x-2 text-red-600">
126
- <Minus className="w-3 h-3" />
127
- <span>Unlink Repo</span>
128
- </div>
129
- </SelectItem>
130
- )}
131
- </SelectContent>
132
- </Select>
193
+ <Popover open={comboboxOpen} onOpenChange={setComboboxOpen}>
194
+ <PopoverTrigger asChild>
195
+ <Button
196
+ variant="outline"
197
+ role="combobox"
198
+ aria-expanded={comboboxOpen}
199
+ className="w-full justify-between"
200
+ disabled={disabled}
201
+ >
202
+ {getDisplayValue()}
203
+ <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
204
+ </Button>
205
+ </PopoverTrigger>
206
+ <PopoverContent className="w-full p-0 z-[999]">
207
+ <Command shouldFilter={false}>
208
+ <CommandInput
209
+ value={searchValue}
210
+ onValueChange={setSearchValue}
211
+ placeholder="Search repositories..."
212
+ />
213
+ <CommandEmpty className="text-sm text-slate-500 py-6">
214
+ No repositories found.
215
+ </CommandEmpty>
216
+ <CommandGroup className="max-h-[400px] overflow-y-auto">
217
+ {filteredOptions.map((option) => (
218
+ <CommandItem
219
+ key={option.value}
220
+ onSelect={() => handleComboboxSelect(option.value)}
221
+ className="cursor-pointer"
222
+ >
223
+ <div className="flex items-center space-x-2 w-full">
224
+ {option.type === "repo" ? (
225
+ <>
226
+ <Check
227
+ className={cn(
228
+ "mr-2 h-4 w-4",
229
+ selectedRepository === option.value
230
+ ? "opacity-100"
231
+ : "opacity-0",
232
+ )}
233
+ />
234
+ <span>{option.label}</span>
235
+ {option.isPrivate && (
236
+ <span className="text-xs text-muted-foreground">
237
+ (private)
238
+ </span>
239
+ )}
240
+ </>
241
+ ) : (
242
+ <>
243
+ {option.icon === "plus" ? (
244
+ <Plus className="w-3 h-3 text-blue-600" />
245
+ ) : (
246
+ <Minus className="w-3 h-3 text-red-600" />
247
+ )}
248
+ <span
249
+ className={
250
+ option.icon === "plus"
251
+ ? "text-blue-600"
252
+ : "text-red-600"
253
+ }
254
+ >
255
+ {option.label}
256
+ </span>
257
+ </>
258
+ )}
259
+ </div>
260
+ </CommandItem>
261
+ ))}
262
+ </CommandGroup>
263
+ </Command>
264
+ </PopoverContent>
265
+ </Popover>
133
266
  </div>
134
267
  )}
135
268
  </div>