@tscircuit/fake-snippets 0.0.88 → 0.0.89
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/api/generated-index.js +96 -14
- package/bun-tests/fake-snippets-api/routes/proxy.test.ts +42 -0
- package/bun.lock +187 -206
- package/dist/bundle.js +206 -100
- package/fake-snippets-api/routes/api/proxy.ts +128 -0
- package/package.json +56 -47
- package/renovate.json +2 -1
- package/src/App.tsx +22 -3
- package/src/ContextProviders.tsx +2 -0
- package/src/build-watcher.ts +52 -0
- package/src/components/CmdKMenu.tsx +533 -197
- package/src/components/DownloadButtonAndMenu.tsx +104 -26
- package/src/components/FileSidebar.tsx +11 -1
- package/src/components/Header2.tsx +7 -2
- package/src/components/PackageBuildsPage/LogContent.tsx +25 -22
- package/src/components/PackageBuildsPage/PackageBuildDetailsPage.tsx +6 -6
- package/src/components/PackageBuildsPage/build-preview-content.tsx +5 -5
- package/src/components/PackageBuildsPage/package-build-details-panel.tsx +15 -13
- package/src/components/PackageBuildsPage/package-build-header.tsx +17 -16
- package/src/components/PackageCard.tsx +66 -16
- package/src/components/SearchComponent.tsx +2 -2
- package/src/components/SuspenseRunFrame.tsx +14 -2
- package/src/components/ViewPackagePage/components/important-files-view.tsx +90 -17
- package/src/components/ViewPackagePage/components/main-content-header.tsx +26 -2
- package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +2 -2
- package/src/components/ViewPackagePage/components/repo-page-content.tsx +35 -30
- package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +2 -2
- package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +20 -12
- package/src/components/ViewPackagePage/components/tab-views/files-view.tsx +0 -7
- package/src/components/ViewPackagePage/utils/fuzz-search.ts +121 -0
- package/src/components/ViewPackagePage/utils/is-hidden-file.ts +4 -0
- package/src/components/dialogs/confirm-delete-package-dialog.tsx +1 -1
- package/src/components/dialogs/confirm-discard-changes-dialog.tsx +73 -0
- package/src/components/dialogs/edit-package-details-dialog.tsx +2 -2
- package/src/components/dialogs/view-ts-files-dialog.tsx +478 -42
- package/src/components/package-port/CodeAndPreview.tsx +16 -0
- package/src/components/package-port/CodeEditor.tsx +113 -11
- package/src/components/package-port/CodeEditorHeader.tsx +39 -4
- package/src/components/package-port/EditorNav.tsx +41 -15
- package/src/components/package-port/GlobalFindReplace.tsx +681 -0
- package/src/components/package-port/QuickOpen.tsx +241 -0
- package/src/components/ui/dialog.tsx +1 -1
- package/src/components/ui/tree-view.tsx +1 -1
- package/src/global.d.ts +3 -0
- package/src/hooks/use-ai-review.ts +31 -0
- package/src/hooks/use-current-package-release.ts +5 -1
- package/src/hooks/use-download-zip.ts +50 -0
- package/src/hooks/use-hotkey.ts +116 -0
- package/src/hooks/use-package-by-package-id.ts +1 -0
- package/src/hooks/use-package-by-package-name.ts +1 -0
- package/src/hooks/use-package-files.ts +3 -0
- package/src/hooks/use-package-release.ts +5 -1
- package/src/hooks/use-package.ts +1 -0
- package/src/hooks/use-request-ai-review-mutation.ts +14 -6
- package/src/hooks/use-snippet.ts +1 -0
- package/src/hooks/useFileManagement.ts +26 -8
- package/src/hooks/usePackageFilesLoader.ts +3 -1
- package/src/index.css +11 -0
- package/src/lib/decodeUrlHashToFsMap.ts +17 -0
- package/src/lib/download-fns/download-circuit-png.ts +88 -0
- package/src/lib/download-fns/download-png-utils.ts +31 -0
- package/src/lib/encodeFsMapToUrlHash.ts +13 -0
- package/src/lib/populate-query-cache-with-ssr-data.ts +7 -0
- package/src/lib/ts-lib-cache.ts +47 -0
- package/src/lib/types.ts +2 -0
- package/src/main.tsx +7 -0
- package/src/pages/dashboard.tsx +8 -5
- package/src/pages/view-package.tsx +15 -7
- package/vite.config.ts +100 -1
package/api/generated-index.js
CHANGED
|
@@ -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
|
|
12
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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:
|
|
149
|
-
imageUrl
|
|
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
|
-
|
|
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:
|
|
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
|
+
})
|