@tscircuit/fake-snippets 0.0.88 → 0.0.90

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/api/generated-index.js +96 -14
  2. package/bun-tests/fake-snippets-api/routes/proxy.test.ts +42 -0
  3. package/bun.lock +196 -215
  4. package/dist/bundle.js +596 -370
  5. package/fake-snippets-api/routes/api/autocomplete/create_autocomplete.ts +134 -0
  6. package/fake-snippets-api/routes/api/proxy.ts +128 -0
  7. package/package.json +59 -48
  8. package/renovate.json +2 -1
  9. package/src/App.tsx +67 -3
  10. package/src/ContextProviders.tsx +2 -0
  11. package/src/build-watcher.ts +52 -0
  12. package/src/components/CircuitJsonImportDialog.tsx +1 -1
  13. package/src/components/CmdKMenu.tsx +533 -197
  14. package/src/components/DownloadButtonAndMenu.tsx +104 -26
  15. package/src/components/FileSidebar.tsx +11 -1
  16. package/src/components/Header2.tsx +7 -2
  17. package/src/components/PackageBuildsPage/LogContent.tsx +25 -22
  18. package/src/components/PackageBuildsPage/PackageBuildDetailsPage.tsx +6 -6
  19. package/src/components/PackageBuildsPage/build-preview-content.tsx +5 -5
  20. package/src/components/PackageBuildsPage/package-build-details-panel.tsx +15 -13
  21. package/src/components/PackageBuildsPage/package-build-header.tsx +19 -28
  22. package/src/components/PackageCard.tsx +66 -16
  23. package/src/components/SearchComponent.tsx +2 -2
  24. package/src/components/SuspenseRunFrame.tsx +14 -2
  25. package/src/components/ViewPackagePage/components/important-files-view.tsx +90 -17
  26. package/src/components/ViewPackagePage/components/main-content-header.tsx +26 -2
  27. package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +2 -2
  28. package/src/components/ViewPackagePage/components/repo-page-content.tsx +35 -30
  29. package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +2 -2
  30. package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +20 -12
  31. package/src/components/ViewPackagePage/components/tab-views/files-view.tsx +0 -7
  32. package/src/components/ViewPackagePage/utils/fuzz-search.ts +121 -0
  33. package/src/components/ViewPackagePage/utils/is-hidden-file.ts +4 -0
  34. package/src/components/dialogs/confirm-delete-package-dialog.tsx +1 -1
  35. package/src/components/dialogs/confirm-discard-changes-dialog.tsx +73 -0
  36. package/src/components/dialogs/edit-package-details-dialog.tsx +2 -2
  37. package/src/components/dialogs/view-ts-files-dialog.tsx +478 -42
  38. package/src/components/package-port/CodeAndPreview.tsx +17 -16
  39. package/src/components/package-port/CodeEditor.tsx +138 -17
  40. package/src/components/package-port/CodeEditorHeader.tsx +44 -4
  41. package/src/components/package-port/EditorNav.tsx +42 -29
  42. package/src/components/package-port/GlobalFindReplace.tsx +681 -0
  43. package/src/components/package-port/QuickOpen.tsx +241 -0
  44. package/src/components/ui/dialog.tsx +1 -1
  45. package/src/components/ui/tree-view.tsx +1 -1
  46. package/src/global.d.ts +3 -0
  47. package/src/hooks/use-ai-review.ts +31 -0
  48. package/src/hooks/use-code-completion-ai-api.ts +3 -3
  49. package/src/hooks/use-current-package-release.ts +5 -1
  50. package/src/hooks/use-delete-package.ts +6 -2
  51. package/src/hooks/use-download-zip.ts +50 -0
  52. package/src/hooks/use-hotkey.ts +116 -0
  53. package/src/hooks/use-package-by-package-id.ts +1 -0
  54. package/src/hooks/use-package-by-package-name.ts +1 -0
  55. package/src/hooks/use-package-files.ts +3 -0
  56. package/src/hooks/use-package-release.ts +5 -1
  57. package/src/hooks/use-package.ts +1 -0
  58. package/src/hooks/use-request-ai-review-mutation.ts +14 -6
  59. package/src/hooks/use-snippet.ts +1 -0
  60. package/src/hooks/useFileManagement.ts +28 -10
  61. package/src/hooks/usePackageFilesLoader.ts +3 -1
  62. package/src/index.css +11 -0
  63. package/src/lib/decodeUrlHashToFsMap.ts +17 -0
  64. package/src/lib/download-fns/download-circuit-png.ts +88 -0
  65. package/src/lib/download-fns/download-png-utils.ts +31 -0
  66. package/src/lib/encodeFsMapToUrlHash.ts +13 -0
  67. package/src/lib/populate-query-cache-with-ssr-data.ts +7 -0
  68. package/src/lib/ts-lib-cache.ts +47 -0
  69. package/src/lib/types.ts +2 -0
  70. package/src/lib/utils/findTargetFile.ts +1 -1
  71. package/src/lib/utils/package-utils.ts +10 -0
  72. package/src/main.tsx +7 -0
  73. package/src/pages/dashboard.tsx +18 -5
  74. package/src/pages/view-package.tsx +15 -7
  75. package/src/types/package.ts +4 -0
  76. package/vite.config.ts +100 -1
@@ -4,14 +4,57 @@ import { readFileSync } from "fs"
4
4
  import { join, dirname } from "path"
5
5
  import { fileURLToPath } from "url"
6
6
  import he from "he"
7
-
8
7
  const __filename = fileURLToPath(import.meta.url)
9
8
  const __dirname = dirname(__filename)
10
9
 
11
- const normalIndexFile = join(__dirname, "../dist/index.html")
12
- const htmlContent = readFileSync(normalIndexFile, "utf-8")
10
+ const isDev =
11
+ process.env.TSC_DEV_SSR === "true" || process.env.NODE_ENV === "development"
12
+ const normalIndexFile = isDev
13
+ ? join(__dirname, "../index.html")
14
+ : join(__dirname, "../dist/index.html")
15
+ let htmlContent = readFileSync(normalIndexFile, "utf-8")
16
+
17
+ export function setHtmlContent(html) {
18
+ htmlContent = html
19
+ }
13
20
 
14
- const cacheControlHeader = "public, max-age=0, must-revalidate"
21
+ const BASE_URL = process.env.TSC_BASE_URL || "https://tscircuit.com"
22
+ const REGISTRY_URL = process.env.TSC_REGISTRY_API || "https://api.tscircuit.com"
23
+
24
+ export const cacheControlHeader = "public, max-age=0, must-revalidate"
25
+ const PREFETCHABLE_PAGES = new Set([
26
+ "landing",
27
+ "editor",
28
+ "search",
29
+ "trending",
30
+ "dashboard",
31
+ "latest",
32
+ "settings",
33
+ "quickstart",
34
+ ])
35
+
36
+ const pageDescriptions = {
37
+ landing:
38
+ "Build electronics with code, AI, and drag'n'drop tools. Render code into schematics, PCBs, 3D, fabrication files, and more. Open-source MIT licensed electronic design automation tool.",
39
+ dashboard:
40
+ "Your tscircuit dashboard - manage your electronic circuit packages, view trending and latest packages, and access your recent designs.",
41
+ search:
42
+ "Search and discover electronic circuit packages on tscircuit. Find components, circuits, and designs created by the community.",
43
+ editor:
44
+ "Design and edit electronic circuits online with tscircuit's powerful web-based editor. Create schematics, PCB layouts, and 3D models with code.",
45
+ trending:
46
+ "Discover the most popular and trending electronic circuit packages on tscircuit. Find top-rated components, keyboards, microcontrollers, connectors, and sensors.",
47
+ latest:
48
+ "Explore the newest electronic circuit packages on tscircuit. Discover fresh circuit designs, components, and innovative approaches to electronic design.",
49
+ quickstart:
50
+ "Get started quickly with tscircuit. Create new circuit packages, import components from JLCPCB, or start from templates to begin your electronic design journey.",
51
+ settings:
52
+ "Manage your tscircuit account settings, shipping information, and preferences for electronic design and PCB ordering.",
53
+ }
54
+
55
+ function getPageDescription(pageName) {
56
+ return pageDescriptions[pageName] || ""
57
+ }
15
58
 
16
59
  function getHtmlWithModifiedSeoTags({
17
60
  title,
@@ -86,6 +129,30 @@ function getHtmlWithModifiedSeoTags({
86
129
  return modifiedHtml
87
130
  }
88
131
 
132
+ export async function handleUserProfile(req, res) {
133
+ const username = req.url.split("/")[req.url.split("/").length - 1]
134
+
135
+ if (!username) {
136
+ throw new Error("Username not provided")
137
+ }
138
+
139
+ const description = he.encode(`Circuits created by ${username} on tscircuit`)
140
+
141
+ const title = he.encode(`${username} - tscircuit`)
142
+
143
+ const html = getHtmlWithModifiedSeoTags({
144
+ title,
145
+ description,
146
+ canonicalUrl: `${BASE_URL}/${he.encode(username)}`,
147
+ imageUrl: `https://github.com/${username}.png`,
148
+ })
149
+
150
+ res.setHeader("Content-Type", "text/html; charset=utf-8")
151
+ res.setHeader("Cache-Control", cacheControlHeader)
152
+ res.setHeader("Vary", "Accept-Encoding")
153
+ res.status(200).send(html)
154
+ }
155
+
89
156
  async function handleCustomPackageHtml(req, res) {
90
157
  // Get the author and package name
91
158
  const [_, author, unscopedPackageName] = req.url.split("?")[0].split("/")
@@ -94,7 +161,7 @@ async function handleCustomPackageHtml(req, res) {
94
161
  }
95
162
 
96
163
  const { package: packageInfo } = await ky
97
- .get(`https://registry-api.tscircuit.com/packages/get`, {
164
+ .get(`${REGISTRY_URL}/packages/get`, {
98
165
  searchParams: {
99
166
  name: `${author}/${unscopedPackageName}`,
100
167
  },
@@ -109,7 +176,7 @@ async function handleCustomPackageHtml(req, res) {
109
176
  let packageFiles = null
110
177
  try {
111
178
  const releaseResponse = await ky
112
- .post(`https://registry-api.tscircuit.com/package_releases/get`, {
179
+ .post(`${REGISTRY_URL}/package_releases/get`, {
113
180
  json: {
114
181
  package_id: packageInfo.package_id,
115
182
  is_latest: true,
@@ -122,7 +189,7 @@ async function handleCustomPackageHtml(req, res) {
122
189
  if (packageRelease?.package_release_id) {
123
190
  try {
124
191
  const filesResponse = await ky
125
- .post(`https://registry-api.tscircuit.com/package_files/list`, {
192
+ .post(`${REGISTRY_URL}/package_files/list`, {
126
193
  json: {
127
194
  package_release_id: packageRelease.package_release_id,
128
195
  },
@@ -142,11 +209,16 @@ async function handleCustomPackageHtml(req, res) {
142
209
  )
143
210
  const title = he.encode(`${packageInfo.name} - tscircuit`)
144
211
 
212
+ const allowedViews = ["schematic", "pcb", "assembly", "3d"]
213
+ const defaultView = packageInfo.default_view || "pcb"
214
+ const thumbnailView = allowedViews.includes(defaultView) ? defaultView : "pcb"
215
+ const imageUrl = `${REGISTRY_URL}/packages/images/${he.encode(author)}/${he.encode(unscopedPackageName)}/${thumbnailView}.png?fs_sha=${packageInfo.latest_package_release_fs_sha}`
216
+
145
217
  const html = getHtmlWithModifiedSeoTags({
146
218
  title,
147
219
  description,
148
- canonicalUrl: `https://tscircuit.com/${he.encode(author)}/${he.encode(unscopedPackageName)}`,
149
- imageUrl: `https://registry-api.tscircuit.com/snippets/images/${he.encode(author)}/${he.encode(unscopedPackageName)}/pcb.png`,
220
+ canonicalUrl: `${BASE_URL}/${he.encode(author)}/${he.encode(unscopedPackageName)}`,
221
+ imageUrl,
150
222
  ssrPackageData: { package: packageInfo, packageRelease, packageFiles },
151
223
  })
152
224
 
@@ -164,17 +236,20 @@ async function handleCustomPage(req, res) {
164
236
  throw new Error("Use landing.html content")
165
237
  }
166
238
 
167
- // TODO handle usernames
239
+ if (!PREFETCHABLE_PAGES.has(page)) {
240
+ throw new Error("Not a route that can be prefetched")
241
+ }
242
+
243
+ const pageDescription = getPageDescription(page)
168
244
 
169
245
  const html = getHtmlWithModifiedSeoTags({
170
- title: `${page} - tscircuit`,
171
- description: ``,
172
- canonicalUrl: `https://tscircuit.com/${page}`,
246
+ title: `${page.charAt(0).toUpperCase() + page.slice(1)} - tscircuit`,
247
+ description: pageDescription,
248
+ canonicalUrl: `${BASE_URL}/${page}`,
173
249
  })
174
250
 
175
251
  res.setHeader("Content-Type", "text/html; charset=utf-8")
176
252
  res.setHeader("Cache-Control", cacheControlHeader)
177
- // Add ETag support for better caching
178
253
  res.setHeader("Vary", "Accept-Encoding")
179
254
  res.status(200).send(html)
180
255
  }
@@ -194,6 +269,13 @@ export default async function handler(req, res) {
194
269
  console.warn(e)
195
270
  }
196
271
 
272
+ try {
273
+ await handleUserProfile(req, res)
274
+ return
275
+ } catch (e) {
276
+ console.warn(e)
277
+ }
278
+
197
279
  res.setHeader("Content-Type", "text/html; charset=utf-8")
198
280
  res.setHeader("Cache-Control", cacheControlHeader)
199
281
  // Add ETag support for better caching
@@ -0,0 +1,42 @@
1
+ import { test, expect } from "bun:test"
2
+ import { getTestServer } from "bun-tests/fake-snippets-api/fixtures/get-test-server"
3
+
4
+ test("should require X-Target-Url header", async () => {
5
+ const { axios } = await getTestServer()
6
+ try {
7
+ await axios.get("/api/proxy")
8
+ throw new Error("Should not reach here")
9
+ } catch (error: any) {
10
+ expect(error.status).toBe(400)
11
+ expect(error.data.error).toBe("X-Target-Url header is required")
12
+ }
13
+ })
14
+
15
+ test("should prevent recursive proxy calls when X-proxied header is present", async () => {
16
+ const { jane_axios: axios } = await getTestServer()
17
+ const port = axios.defaults?.baseURL?.split(":")[2]
18
+
19
+ try {
20
+ await axios.get("/api/proxy", {
21
+ headers: {
22
+ "X-Target-Url": `http://localhost:${port}/api/proxy`,
23
+ },
24
+ })
25
+ throw new Error("Should not reach here")
26
+ } catch (error: any) {
27
+ expect(error.status).toBe(403)
28
+ expect(error.data.error).toBe("Recursive proxy calls are not allowed")
29
+ }
30
+ })
31
+
32
+ test("should successfully proxy requests to allowed domains", async () => {
33
+ const { jane_axios: axios } = await getTestServer()
34
+ const port = axios.defaults?.baseURL?.split(":")[2]
35
+ const response = await axios.get("/api/proxy", {
36
+ headers: {
37
+ "X-Target-Url": `http://localhost:${port}/api/health`,
38
+ },
39
+ })
40
+
41
+ expect(response.status).toBe(200)
42
+ })