@tscircuit/fake-snippets 0.0.108 → 0.0.110
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/.github/workflows/bun-formatcheck.yml +2 -2
- package/.github/workflows/bun-pver-release.yml +3 -3
- package/.github/workflows/bun-test.yml +1 -1
- package/.github/workflows/bun-typecheck.yml +2 -2
- package/.github/workflows/update-snapshots.yml +1 -1
- package/README.md +4 -0
- package/api/generated-index.js +37 -3
- package/biome.json +2 -1
- package/bun-tests/fake-snippets-api/fixtures/get-test-server.ts +31 -3
- package/bun-tests/fake-snippets-api/fixtures/preload.ts +18 -0
- package/bun-tests/fake-snippets-api/routes/orgs/add_member.test.ts +26 -0
- package/bun-tests/fake-snippets-api/routes/orgs/create.test.ts +37 -0
- package/bun-tests/fake-snippets-api/routes/orgs/get.test.ts +52 -0
- package/bun-tests/fake-snippets-api/routes/orgs/list.test.ts +17 -0
- package/bun-tests/fake-snippets-api/routes/orgs/list_members.test.ts +23 -0
- package/bun-tests/fake-snippets-api/routes/orgs/remove_member.test.ts +81 -0
- package/bun-tests/fake-snippets-api/routes/orgs/update.test.ts +99 -0
- package/bun-tests/fake-snippets-api/routes/package_builds/get.test.ts +1 -1
- package/bun-tests/fake-snippets-api/routes/package_files/create.test.ts +15 -13
- package/bun-tests/fake-snippets-api/routes/package_files/create_or_update.test.ts +26 -24
- package/bun-tests/fake-snippets-api/routes/package_files/delete.test.ts +9 -9
- package/bun-tests/fake-snippets-api/routes/package_files/download.test.ts +4 -4
- package/bun-tests/fake-snippets-api/routes/package_files/get.test.ts +38 -28
- package/bun-tests/fake-snippets-api/routes/package_files/list.test.ts +23 -15
- package/bun-tests/fake-snippets-api/routes/package_releases/create.test.ts +33 -0
- package/bun-tests/fake-snippets-api/routes/package_releases/get.test.ts +4 -4
- package/bun-tests/fake-snippets-api/routes/package_releases/get_image_generation_fields.test.ts +38 -0
- package/bun-tests/fake-snippets-api/routes/packages/create.test.ts +19 -0
- package/bun-tests/fake-snippets-api/routes/packages/fork.test.ts +3 -4
- package/bun-tests/fake-snippets-api/routes/packages/get.test.ts +30 -0
- package/bun-tests/fake-snippets-api/routes/packages/images.test.ts +4 -2
- package/bun-tests/fake-snippets-api/routes/packages/list-1.test.ts +34 -0
- package/bun.lock +389 -450
- package/bunfig.toml +2 -1
- package/dist/bundle.js +1255 -625
- package/dist/index.d.ts +296 -4
- package/dist/index.js +325 -24
- package/dist/schema.d.ts +282 -1
- package/dist/schema.js +54 -2
- package/fake-snippets-api/lib/db/autoload-dev-packages.ts +31 -20
- package/fake-snippets-api/lib/db/db-client.ts +214 -3
- package/fake-snippets-api/lib/db/schema.ts +62 -0
- package/fake-snippets-api/lib/db/seed.ts +100 -0
- package/fake-snippets-api/lib/middleware/with-session-auth.ts +1 -1
- package/fake-snippets-api/lib/package_file/get-package-file-id-from-file-descriptor.ts +2 -2
- package/fake-snippets-api/lib/public-mapping/public-map-org.ts +32 -0
- package/fake-snippets-api/lib/public-mapping/public-map-package-build.ts +10 -0
- package/fake-snippets-api/lib/public-mapping/public-map-package-release.ts +17 -0
- package/fake-snippets-api/routes/api/orgs/add_member.ts +52 -0
- package/fake-snippets-api/routes/api/orgs/create.ts +46 -0
- package/fake-snippets-api/routes/api/orgs/get.ts +39 -0
- package/fake-snippets-api/routes/api/orgs/list.ts +31 -0
- package/fake-snippets-api/routes/api/orgs/list_members.ts +67 -0
- package/fake-snippets-api/routes/api/orgs/remove_member.ts +46 -0
- package/fake-snippets-api/routes/api/orgs/update.ts +93 -0
- package/fake-snippets-api/routes/api/package_files/get.ts +3 -6
- package/fake-snippets-api/routes/api/package_files/list.ts +7 -4
- package/fake-snippets-api/routes/api/packages/create.ts +54 -10
- package/fake-snippets-api/routes/api/packages/get.ts +23 -0
- package/fake-snippets-api/routes/api/packages/images/[owner_github_username]/[unscoped_name]/[view_format].ts +13 -11
- package/fake-snippets-api/routes/api/packages/list.ts +29 -2
- package/fake-snippets-api/routes/api/packages/update_ai_description.ts +37 -0
- package/package.json +27 -24
- package/renovate.json +1 -1
- package/scripts/generate-sitemap.ts +1 -1
- package/src/App.tsx +29 -10
- package/src/ContextProviders.tsx +25 -2
- package/src/components/CircuitJsonImportDialog.tsx +1 -1
- package/src/components/CmdKMenu.tsx +281 -247
- package/src/components/DownloadButtonAndMenu.tsx +133 -36
- package/src/components/FileSidebar.tsx +41 -50
- package/src/components/Footer.tsx +8 -10
- package/src/components/Header.tsx +19 -32
- package/src/components/Header2.tsx +16 -32
- package/src/components/HeaderDropdown.tsx +13 -8
- package/src/components/HeaderLogin.tsx +44 -16
- package/src/components/HiddenFilesDropdown.tsx +0 -2
- package/src/components/NotFound.tsx +5 -5
- package/src/components/PackageBreadcrumb.tsx +6 -12
- package/src/components/PackageCard.tsx +0 -1
- package/src/components/PackageSearchResults.tsx +1 -1
- package/src/components/PrefetchPageLink.tsx +7 -1
- package/src/components/ProfileRouter.tsx +32 -0
- package/src/components/SearchComponent.tsx +12 -8
- package/src/components/UserCard.tsx +80 -0
- package/src/components/ViewPackagePage/components/ShikiCodeViewer.tsx +20 -11
- package/src/components/ViewPackagePage/components/build-status.tsx +1 -1
- package/src/components/ViewPackagePage/components/important-files-view.tsx +174 -87
- package/src/components/ViewPackagePage/components/main-content-header.tsx +8 -4
- package/src/components/ViewPackagePage/components/main-content-view-selector.tsx +1 -2
- package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +54 -20
- package/src/components/ViewPackagePage/components/package-header.tsx +26 -37
- package/src/components/ViewPackagePage/components/preview-image-squares.tsx +11 -19
- package/src/components/ViewPackagePage/components/repo-page-content.tsx +33 -25
- package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +16 -10
- package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +11 -11
- package/src/components/ViewPackagePage/components/sidebar.tsx +0 -2
- package/src/components/ViewPackagePage/components/tab-views/files-view.tsx +18 -17
- package/src/components/ViewPackagePage/components/tab-views/pcb-view.tsx +1 -2
- package/src/components/ViewPackagePage/components/tab-views/schematic-view.tsx +2 -1
- package/src/components/ViewPackagePage/components/theme-toggle.tsx +0 -2
- package/src/components/ViewPackagePage/hooks/use-toast.tsx +0 -1
- package/src/components/dialogs/GitHubRepositorySelector.tsx +56 -49
- package/src/components/dialogs/edit-package-details-dialog.tsx +5 -6
- package/src/components/dialogs/import-component-dialog.tsx +16 -9
- package/src/components/dialogs/import-package-dialog.tsx +3 -2
- package/src/components/dialogs/new-package-save-prompt-dialog.tsx +190 -0
- package/src/components/organization/OrganizationCard.tsx +204 -0
- package/src/components/organization/OrganizationCardSkeleton.tsx +55 -0
- package/src/components/organization/OrganizationHeader.tsx +154 -0
- package/src/components/organization/OrganizationMembers.tsx +146 -0
- package/src/components/package-port/CodeAndPreview.tsx +32 -46
- package/src/components/package-port/CodeEditor.tsx +28 -31
- package/src/components/package-port/CodeEditorHeader.tsx +128 -63
- package/src/components/package-port/EditorNav.tsx +32 -49
- package/src/components/preview/ConnectedPackagesList.tsx +8 -8
- package/src/components/preview/ConnectedRepoOverview.tsx +102 -2
- package/src/components/preview/PackageReleasesDashboard.tsx +53 -36
- package/src/components/ui/tree-view.tsx +6 -3
- package/src/hooks/use-add-org-member-mutation.ts +51 -0
- package/src/hooks/use-create-org-mutation.ts +38 -0
- package/src/hooks/use-create-package-mutation.ts +3 -0
- package/src/hooks/use-current-package-id.ts +5 -30
- package/src/hooks/use-current-package-info.ts +29 -5
- package/src/hooks/use-current-package-release.ts +4 -3
- package/src/hooks/use-download-zip.ts +2 -2
- package/src/hooks/use-global-store.ts +6 -4
- package/src/hooks/use-jlcpcb-component-import.tsx +164 -0
- package/src/hooks/use-list-org-members.ts +27 -0
- package/src/hooks/use-list-user-orgs.ts +25 -0
- package/src/hooks/use-org-by-github-handle.ts +26 -0
- package/src/hooks/use-org.ts +24 -0
- package/src/hooks/use-organization.ts +42 -0
- package/src/hooks/use-package-as-snippet.ts +4 -2
- package/src/hooks/use-package-builds.ts +6 -2
- package/src/hooks/use-package-files.ts +5 -3
- package/src/hooks/use-package-release-by-id-or-version.ts +29 -20
- package/src/hooks/use-package-release-images.ts +105 -0
- package/src/hooks/use-package-release.ts +2 -2
- package/src/hooks/use-package-stars.ts +80 -4
- package/src/hooks/use-preview-images.ts +6 -3
- package/src/hooks/use-remove-org-member-mutation.ts +32 -0
- package/src/hooks/use-update-ai-description-mutation.ts +42 -0
- package/src/hooks/use-update-org-mutation.ts +41 -0
- package/src/hooks/use-warn-user-on-page-change.ts +71 -4
- package/src/hooks/useFileManagement.ts +183 -35
- package/src/hooks/useOptimizedPackageFilesLoader.ts +136 -0
- package/src/hooks/usePackageFilesLoader.ts +2 -2
- package/src/hooks/useUpdatePackageFilesMutation.ts +15 -1
- package/src/lib/download-fns/download-circuit-png.ts +11 -3
- package/src/lib/download-fns/download-gltf-from-circuit-json.ts +44 -0
- package/src/lib/download-fns/download-kicad-files.ts +12 -11
- package/src/lib/normalize-svg-for-tile.ts +50 -0
- package/src/lib/posthog.ts +11 -9
- package/src/lib/react-query-api-failure-tracking.ts +148 -0
- package/src/lib/sentry.ts +14 -0
- package/src/lib/templates/blank-circuit-board-template.ts +0 -4
- package/src/lib/ts-lib-cache.ts +122 -7
- package/src/lib/utils/checkIfManualEditsImported.ts +4 -4
- package/src/lib/utils/findTargetFile.ts +45 -10
- package/src/lib/utils/isComponentExported.ts +10 -0
- package/src/main.tsx +2 -1
- package/src/pages/authorize.tsx +0 -2
- package/src/pages/create-organization.tsx +168 -0
- package/src/pages/dashboard.tsx +38 -6
- package/src/pages/datasheet.tsx +1 -1
- package/src/pages/datasheets.tsx +3 -3
- package/src/pages/editor.tsx +4 -6
- package/src/pages/landing.tsx +6 -7
- package/src/pages/latest.tsx +3 -0
- package/src/pages/organization-profile.tsx +199 -0
- package/src/pages/organization-settings.tsx +566 -0
- package/src/pages/package-editor.tsx +21 -21
- package/src/pages/preview-release.tsx +76 -136
- package/src/pages/quickstart.tsx +159 -123
- package/src/pages/release-detail.tsx +119 -31
- package/src/pages/search.tsx +192 -57
- package/src/pages/settings-redirect.tsx +44 -0
- package/src/pages/trending.tsx +29 -20
- package/src/pages/user-profile.tsx +58 -7
- package/src/pages/view-package.tsx +21 -26
- package/vite.config.ts +9 -0
- package/fake-snippets-api/routes/api/autocomplete/create_autocomplete.ts +0 -133
- package/src/components/Footer2.tsx +0 -100
- package/src/components/JLCPCBImportDialog.tsx +0 -280
- package/src/components/PackageBuildsPage/LogContent.tsx +0 -72
- package/src/components/PackageBuildsPage/PackageBuildDetailsPage.tsx +0 -115
- package/src/components/PackageBuildsPage/build-preview-content.tsx +0 -27
- package/src/components/PackageBuildsPage/collapsible-section.tsx +0 -63
- package/src/components/PackageBuildsPage/package-build-details-panel.tsx +0 -166
- package/src/components/PackageBuildsPage/package-build-header.tsx +0 -79
- package/src/components/PageSearchComponent.tsx +0 -148
- package/src/components/ShippingInformationForm.tsx +0 -423
- package/src/components/StaticViewSnippetHeader.tsx +0 -70
- package/src/components/ViewPackagePage/components/file-explorer.tsx +0 -67
- package/src/components/ViewPackagePage/components/readme-view.tsx +0 -58
- package/src/components/ViewPackagePage/components/repo-header-button.tsx +0 -36
- package/src/components/ViewPackagePage/components/repo-header.tsx +0 -4
- package/src/components/ViewPackagePage/components/sidebar-contributors-section.tsx +0 -31
- package/src/components/ViewSnippetHeader.tsx +0 -181
- package/src/components/ui/input-otp.tsx +0 -69
- package/src/pages/package-builds.tsx +0 -33
- package/src/pages/settings.tsx +0 -25
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export function normalizeSvgForSquareTile(svg: string): string {
|
|
2
|
+
try {
|
|
3
|
+
const openTagMatch = svg.match(/<svg[^>]*>/i)
|
|
4
|
+
if (!openTagMatch) return svg
|
|
5
|
+
|
|
6
|
+
const openTag = openTagMatch[0]
|
|
7
|
+
|
|
8
|
+
const hasViewBox = /viewBox="[^"]+"/i.test(openTag)
|
|
9
|
+
const widthMatch = openTag.match(/\swidth="([0-9.]+)"/i)
|
|
10
|
+
const heightMatch = openTag.match(/\sheight="([0-9.]+)"/i)
|
|
11
|
+
|
|
12
|
+
let newOpenTag = openTag
|
|
13
|
+
|
|
14
|
+
// Remove explicit width/height so CSS can control sizing
|
|
15
|
+
newOpenTag = newOpenTag.replace(/\swidth="[^"]*"/i, "")
|
|
16
|
+
newOpenTag = newOpenTag.replace(/\sheight="[^"]*"/i, "")
|
|
17
|
+
|
|
18
|
+
// Ensure viewBox is present for proper scaling
|
|
19
|
+
if (!hasViewBox && widthMatch && heightMatch) {
|
|
20
|
+
const w = widthMatch[1]
|
|
21
|
+
const h = heightMatch[1]
|
|
22
|
+
newOpenTag = newOpenTag.replace(/<svg/i, `<svg viewBox="0 0 ${w} ${h}"`)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Force preserveAspectRatio to fit within square without distortion
|
|
26
|
+
if (/preserveAspectRatio="[^"]+"/i.test(newOpenTag)) {
|
|
27
|
+
newOpenTag = newOpenTag.replace(
|
|
28
|
+
/preserveAspectRatio="[^"]+"/i,
|
|
29
|
+
'preserveAspectRatio="xMidYMid meet"',
|
|
30
|
+
)
|
|
31
|
+
} else {
|
|
32
|
+
newOpenTag = newOpenTag.replace(
|
|
33
|
+
/<svg(\s|>)/i,
|
|
34
|
+
(_m, p1) => `<svg preserveAspectRatio="xMidYMid meet"${p1}`,
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return svg.replace(openTag, newOpenTag)
|
|
39
|
+
} catch {
|
|
40
|
+
return svg
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function svgToDataUrl(svg: string): string {
|
|
45
|
+
try {
|
|
46
|
+
return `data:image/svg+xml,${encodeURIComponent(svg)}`
|
|
47
|
+
} catch {
|
|
48
|
+
return `data:image/svg+xml,${svg}`
|
|
49
|
+
}
|
|
50
|
+
}
|
package/src/lib/posthog.ts
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import posthog from "posthog-js"
|
|
2
2
|
|
|
3
|
-
if (
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
)
|
|
7
|
-
|
|
8
|
-
posthog.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
if (typeof window !== "undefined") {
|
|
4
|
+
if (
|
|
5
|
+
!window.location.hostname.includes("localhost") &&
|
|
6
|
+
!window.location.hostname.includes("127.0.0.1")
|
|
7
|
+
) {
|
|
8
|
+
if (!posthog.__loaded) {
|
|
9
|
+
posthog.init("phc_htd8AQjSfVEsFCLQMAiUooG4Q0DKBCjqYuQglc9V3Wo", {
|
|
10
|
+
api_host: "https://postpig.tscircuit.com",
|
|
11
|
+
person_profiles: "always",
|
|
12
|
+
})
|
|
13
|
+
}
|
|
12
14
|
}
|
|
13
15
|
}
|
|
14
16
|
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import type { QueryKey } from "react-query"
|
|
2
|
+
import { posthog } from "./posthog"
|
|
3
|
+
|
|
4
|
+
const TARGET_HOSTNAMES = new Set(["api.tscircuit.com"])
|
|
5
|
+
|
|
6
|
+
type FailureContext = {
|
|
7
|
+
operationType: "query" | "mutation"
|
|
8
|
+
queryKey?: QueryKey
|
|
9
|
+
mutationKey?: unknown
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type RedaxiosConfig = {
|
|
13
|
+
url?: string
|
|
14
|
+
baseURL?: string
|
|
15
|
+
method?: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type ResponseLike = {
|
|
19
|
+
url?: string
|
|
20
|
+
status?: number
|
|
21
|
+
statusText?: string
|
|
22
|
+
config?: RedaxiosConfig
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const isPosthogLoaded = () => Boolean((posthog as any)?.__loaded)
|
|
26
|
+
|
|
27
|
+
const toUpperCaseMethod = (method?: string) => method?.toUpperCase() ?? "GET"
|
|
28
|
+
|
|
29
|
+
const resolveAbsoluteUrl = (url?: string, baseURL?: string) => {
|
|
30
|
+
if (!url) return undefined
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
if (baseURL) {
|
|
34
|
+
return new URL(url, baseURL).toString()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (typeof window !== "undefined") {
|
|
38
|
+
return new URL(url, window.location.origin).toString()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return new URL(url).toString()
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.warn("Failed to resolve API failure URL", error)
|
|
44
|
+
return undefined
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const shouldTrackUrl = (resolvedUrl?: string) => {
|
|
49
|
+
if (!resolvedUrl) return false
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const { hostname } = new URL(resolvedUrl)
|
|
53
|
+
return TARGET_HOSTNAMES.has(hostname)
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.warn("Failed to parse URL for API failure tracking", error)
|
|
56
|
+
return false
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const serializeKey = (key?: unknown) => {
|
|
61
|
+
if (!key) return undefined
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
return JSON.stringify(key)
|
|
65
|
+
} catch {
|
|
66
|
+
return String(key)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const extractFromResponse = (error: unknown): ResponseLike | null => {
|
|
71
|
+
if (!error || typeof error !== "object") return null
|
|
72
|
+
|
|
73
|
+
const maybeResponse = error as Partial<ResponseLike>
|
|
74
|
+
if (typeof maybeResponse.status !== "number" || !("url" in maybeResponse)) {
|
|
75
|
+
return null
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
url: maybeResponse.url,
|
|
80
|
+
status: maybeResponse.status,
|
|
81
|
+
statusText: maybeResponse.statusText,
|
|
82
|
+
config: maybeResponse.config,
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const extractFromAxiosError = (error: unknown): ResponseLike | null => {
|
|
87
|
+
if (!error || typeof error !== "object") return null
|
|
88
|
+
|
|
89
|
+
const maybeAxiosError = error as {
|
|
90
|
+
response?: ResponseLike
|
|
91
|
+
config?: RedaxiosConfig
|
|
92
|
+
message?: string
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!maybeAxiosError.response && !maybeAxiosError.config) {
|
|
96
|
+
return null
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
url: maybeAxiosError.response?.url,
|
|
101
|
+
status: maybeAxiosError.response?.status,
|
|
102
|
+
statusText: maybeAxiosError.response?.statusText,
|
|
103
|
+
config: maybeAxiosError.response?.config ?? maybeAxiosError.config,
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const captureApiFailure = (
|
|
108
|
+
response: ResponseLike,
|
|
109
|
+
error: unknown,
|
|
110
|
+
context: FailureContext,
|
|
111
|
+
) => {
|
|
112
|
+
if (!isPosthogLoaded()) return
|
|
113
|
+
|
|
114
|
+
const resolvedUrl =
|
|
115
|
+
resolveAbsoluteUrl(response.url, response.config?.baseURL) ??
|
|
116
|
+
resolveAbsoluteUrl(response.config?.url, response.config?.baseURL)
|
|
117
|
+
if (!shouldTrackUrl(resolvedUrl)) return
|
|
118
|
+
|
|
119
|
+
const errorMessage =
|
|
120
|
+
error instanceof Error
|
|
121
|
+
? error.message
|
|
122
|
+
: typeof error === "string"
|
|
123
|
+
? error
|
|
124
|
+
: undefined
|
|
125
|
+
|
|
126
|
+
posthog.capture("api_request_failed", {
|
|
127
|
+
url: resolvedUrl,
|
|
128
|
+
method: toUpperCaseMethod(response.config?.method),
|
|
129
|
+
status: response.status,
|
|
130
|
+
statusText: response.statusText,
|
|
131
|
+
errorMessage,
|
|
132
|
+
operationType: context.operationType,
|
|
133
|
+
queryKey: serializeKey(context.queryKey),
|
|
134
|
+
mutationKey: serializeKey(context.mutationKey),
|
|
135
|
+
environment:
|
|
136
|
+
typeof window !== "undefined" ? window.location.hostname : undefined,
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export const trackReactQueryApiFailure = (
|
|
141
|
+
error: unknown,
|
|
142
|
+
context: FailureContext,
|
|
143
|
+
) => {
|
|
144
|
+
const response = extractFromResponse(error) ?? extractFromAxiosError(error)
|
|
145
|
+
if (!response) return
|
|
146
|
+
|
|
147
|
+
captureApiFailure(response, error, context)
|
|
148
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as Sentry from "@sentry/react"
|
|
2
|
+
|
|
3
|
+
if (
|
|
4
|
+
typeof window !== "undefined" &&
|
|
5
|
+
import.meta.env.VITE_SENTRY_DSN &&
|
|
6
|
+
!window.location.hostname.includes("localhost") &&
|
|
7
|
+
!window.location.hostname.includes("127.0.0.1")
|
|
8
|
+
) {
|
|
9
|
+
Sentry.init({
|
|
10
|
+
dsn: import.meta.env.VITE_SENTRY_DSN,
|
|
11
|
+
})
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export { Sentry }
|
|
@@ -7,15 +7,11 @@ export default () => (
|
|
|
7
7
|
resistance="1k"
|
|
8
8
|
footprint="0402"
|
|
9
9
|
name="R1"
|
|
10
|
-
schX={3}
|
|
11
|
-
pcbX={3}
|
|
12
10
|
/>
|
|
13
11
|
<capacitor
|
|
14
12
|
capacitance="1000pF"
|
|
15
13
|
footprint="0402"
|
|
16
14
|
name="C1"
|
|
17
|
-
schX={-3}
|
|
18
|
-
pcbX={-3}
|
|
19
15
|
/>
|
|
20
16
|
<trace from=".R1 > .pin1" to=".C1 > .pin1" />
|
|
21
17
|
</board>
|
package/src/lib/ts-lib-cache.ts
CHANGED
|
@@ -8,6 +8,7 @@ import ts from "typescript"
|
|
|
8
8
|
|
|
9
9
|
const TS_LIB_VERSION = "5.6.3"
|
|
10
10
|
const CACHE_PREFIX = `ts-lib-${TS_LIB_VERSION}-`
|
|
11
|
+
const CACHE_TTL = 7 * 24 * 60 * 60 * 1000 // 7 days
|
|
11
12
|
|
|
12
13
|
export async function loadDefaultLibMap(): Promise<Map<string, string>> {
|
|
13
14
|
const fsMap = new Map<string, string>()
|
|
@@ -18,9 +19,23 @@ export async function loadDefaultLibMap(): Promise<Map<string, string>> {
|
|
|
18
19
|
const missing: string[] = []
|
|
19
20
|
|
|
20
21
|
for (const lib of libs) {
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
const cacheKey = CACHE_PREFIX + lib
|
|
23
|
+
const cached = await get(cacheKey)
|
|
24
|
+
if (
|
|
25
|
+
cached &&
|
|
26
|
+
typeof cached === "object" &&
|
|
27
|
+
"compressedFileContent" in cached &&
|
|
28
|
+
"timestamp" in cached
|
|
29
|
+
) {
|
|
30
|
+
const { compressedFileContent, timestamp } = cached as {
|
|
31
|
+
compressedFileContent: string
|
|
32
|
+
timestamp: number
|
|
33
|
+
}
|
|
34
|
+
if (Date.now() - timestamp < CACHE_TTL) {
|
|
35
|
+
fsMap.set("/" + lib, decompressFromUTF16(compressedFileContent))
|
|
36
|
+
} else {
|
|
37
|
+
missing.push(lib)
|
|
38
|
+
}
|
|
24
39
|
} else {
|
|
25
40
|
missing.push(lib)
|
|
26
41
|
}
|
|
@@ -36,12 +51,112 @@ export async function loadDefaultLibMap(): Promise<Map<string, string>> {
|
|
|
36
51
|
)
|
|
37
52
|
for (const [filename, content] of fetched) {
|
|
38
53
|
fsMap.set(filename, content)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
54
|
+
const cacheKey = CACHE_PREFIX + filename.replace(/^\//, "")
|
|
55
|
+
const compressed = compressToUTF16(content)
|
|
56
|
+
await set(cacheKey, {
|
|
57
|
+
compressedFileContent: compressed,
|
|
58
|
+
timestamp: Date.now(),
|
|
59
|
+
}).catch(() => {})
|
|
43
60
|
}
|
|
44
61
|
}
|
|
45
62
|
|
|
46
63
|
return fsMap
|
|
47
64
|
}
|
|
65
|
+
|
|
66
|
+
export async function fetchWithPackageCaching(
|
|
67
|
+
input: RequestInfo | URL,
|
|
68
|
+
init?: RequestInit,
|
|
69
|
+
): Promise<Response> {
|
|
70
|
+
const url = typeof input === "string" ? input : input.toString()
|
|
71
|
+
|
|
72
|
+
// Only cache GET requests for packages
|
|
73
|
+
if (init?.method && init.method !== "GET") {
|
|
74
|
+
return fetch(input, init)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Check if this should be cached
|
|
78
|
+
const shouldCache =
|
|
79
|
+
url.includes("jsdelivr.net") ||
|
|
80
|
+
url.includes("unpkg.com") ||
|
|
81
|
+
url.includes("@types/") ||
|
|
82
|
+
url.includes("@tsci/")
|
|
83
|
+
|
|
84
|
+
if (!shouldCache) {
|
|
85
|
+
return fetch(input, init)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const cacheKey = `package-cache-${url}`
|
|
89
|
+
|
|
90
|
+
// Check cache
|
|
91
|
+
const cached = await get(cacheKey).catch(() => null)
|
|
92
|
+
if (
|
|
93
|
+
cached &&
|
|
94
|
+
typeof cached === "object" &&
|
|
95
|
+
"compressedFileContent" in cached &&
|
|
96
|
+
"timestamp" in cached
|
|
97
|
+
) {
|
|
98
|
+
const { compressedFileContent, timestamp } = cached as {
|
|
99
|
+
compressedFileContent: string
|
|
100
|
+
timestamp: number
|
|
101
|
+
}
|
|
102
|
+
if (Date.now() - timestamp < CACHE_TTL) {
|
|
103
|
+
return new Response(decompressFromUTF16(compressedFileContent), {
|
|
104
|
+
status: 200,
|
|
105
|
+
statusText: "OK",
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Handle @tsci packages
|
|
111
|
+
let fetchUrl = url
|
|
112
|
+
if (
|
|
113
|
+
url.includes("@tsci/") &&
|
|
114
|
+
(url.includes("jsdelivr.net") || url.includes("data.jsdelivr.com"))
|
|
115
|
+
) {
|
|
116
|
+
let packagePath = ""
|
|
117
|
+
if (url.includes("jsdelivr.net")) {
|
|
118
|
+
packagePath = url.replace("https://cdn.jsdelivr.net/npm/@tsci/", "")
|
|
119
|
+
} else if (url.includes("/v1/package/resolve/npm/@tsci/")) {
|
|
120
|
+
const resolveIndex = url.indexOf("/v1/package/resolve/npm/@tsci/")
|
|
121
|
+
packagePath = url.substring(
|
|
122
|
+
resolveIndex + "/v1/package/resolve/npm/@tsci/".length,
|
|
123
|
+
)
|
|
124
|
+
} else if (url.includes("/v1/package/npm/@tsci/")) {
|
|
125
|
+
const npmIndex = url.indexOf("/v1/package/npm/@tsci/")
|
|
126
|
+
packagePath = url.substring(npmIndex + "/v1/package/npm/@tsci/".length)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (packagePath) {
|
|
130
|
+
// Convert dots to slashes in the package name part (like original logic)
|
|
131
|
+
const parts = packagePath.split("/")
|
|
132
|
+
if (parts.length > 0) {
|
|
133
|
+
parts[0] = parts[0].replace(/\./, "/")
|
|
134
|
+
}
|
|
135
|
+
const transformedPackagePath = parts.join("/")
|
|
136
|
+
|
|
137
|
+
const apiUrl = import.meta.env.VITE_SNIPPETS_API_URL ?? "/api"
|
|
138
|
+
const isResolve = url.includes("/resolve/")
|
|
139
|
+
fetchUrl = `${apiUrl}/snippets/download?jsdelivr_resolve=${isResolve}&jsdelivr_path=${encodeURIComponent(
|
|
140
|
+
transformedPackagePath,
|
|
141
|
+
)}`
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Fetch and cache
|
|
146
|
+
const response = await fetch(fetchUrl, init)
|
|
147
|
+
if (response.ok) {
|
|
148
|
+
const text = await response.text()
|
|
149
|
+
const compressed = compressToUTF16(text)
|
|
150
|
+
await set(cacheKey, {
|
|
151
|
+
compressedFileContent: compressed,
|
|
152
|
+
timestamp: Date.now(),
|
|
153
|
+
}).catch(() => {})
|
|
154
|
+
return new Response(text, {
|
|
155
|
+
status: response.status,
|
|
156
|
+
statusText: response.statusText,
|
|
157
|
+
headers: response.headers,
|
|
158
|
+
})
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return response
|
|
162
|
+
}
|
|
@@ -5,10 +5,10 @@ export const checkIfManualEditsImported = (
|
|
|
5
5
|
file: string = "index.tsx",
|
|
6
6
|
) => {
|
|
7
7
|
if (!files[file]) return false
|
|
8
|
-
const targetFile = findTargetFile(
|
|
9
|
-
Object.keys(files).map((f) => ({ path: f, content: files[f] })),
|
|
10
|
-
null,
|
|
11
|
-
)
|
|
8
|
+
const targetFile = findTargetFile({
|
|
9
|
+
files: Object.keys(files).map((f) => ({ path: f, content: files[f] })),
|
|
10
|
+
filePathFromUrl: null,
|
|
11
|
+
})
|
|
12
12
|
if (targetFile && file !== targetFile.path) {
|
|
13
13
|
return false
|
|
14
14
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { isHiddenFile } from "@/components/ViewPackagePage/utils/is-hidden-file"
|
|
1
2
|
import { PackageFile } from "@/types/package"
|
|
3
|
+
import { isComponentExported } from "./isComponentExported"
|
|
2
4
|
|
|
3
5
|
export const findMainEntrypointFileFromTscircuitConfig = (
|
|
4
6
|
files: PackageFile[],
|
|
@@ -24,37 +26,70 @@ export const findMainEntrypointFileFromTscircuitConfig = (
|
|
|
24
26
|
return null
|
|
25
27
|
}
|
|
26
28
|
|
|
27
|
-
export const findTargetFile = (
|
|
28
|
-
files
|
|
29
|
-
filePathFromUrl
|
|
30
|
-
|
|
29
|
+
export const findTargetFile = ({
|
|
30
|
+
files,
|
|
31
|
+
filePathFromUrl,
|
|
32
|
+
fallbackToAnyFile = true,
|
|
33
|
+
}: {
|
|
34
|
+
files: PackageFile[]
|
|
35
|
+
filePathFromUrl: string | null
|
|
36
|
+
fallbackToAnyFile?: boolean
|
|
37
|
+
}): PackageFile | null => {
|
|
31
38
|
if (files.length === 0) {
|
|
32
39
|
return null
|
|
33
40
|
}
|
|
34
41
|
|
|
35
|
-
let targetFile: PackageFile | null = null
|
|
42
|
+
let targetFile: PackageFile | undefined | null = null
|
|
43
|
+
if (!filePathFromUrl) {
|
|
44
|
+
files = files.filter((x) => !isHiddenFile(x.path))
|
|
45
|
+
}
|
|
36
46
|
|
|
37
47
|
if (filePathFromUrl) {
|
|
38
|
-
|
|
48
|
+
const file = files.find((file) => file.path === filePathFromUrl)?.path
|
|
49
|
+
if (
|
|
50
|
+
file &&
|
|
51
|
+
!file.endsWith(".ts") &&
|
|
52
|
+
!file.endsWith(".tsx") &&
|
|
53
|
+
fallbackToAnyFile
|
|
54
|
+
) {
|
|
55
|
+
targetFile = files.find((file) => file.path === filePathFromUrl) ?? null
|
|
56
|
+
} else {
|
|
57
|
+
const _isComponentExported = isComponentExported(
|
|
58
|
+
files.find((file) => file.path === filePathFromUrl)?.content || "",
|
|
59
|
+
)
|
|
60
|
+
if (_isComponentExported) {
|
|
61
|
+
targetFile = files.find((file) => file.path === filePathFromUrl) ?? null
|
|
62
|
+
}
|
|
63
|
+
}
|
|
39
64
|
}
|
|
40
65
|
|
|
41
66
|
if (!targetFile) {
|
|
42
67
|
targetFile = findMainEntrypointFileFromTscircuitConfig(files)
|
|
43
68
|
}
|
|
69
|
+
if (!targetFile) {
|
|
70
|
+
targetFile =
|
|
71
|
+
files.find(
|
|
72
|
+
(file) => file.path === "index.tsx" || file.path === "index.ts",
|
|
73
|
+
) ?? null
|
|
74
|
+
}
|
|
44
75
|
|
|
45
76
|
if (!targetFile) {
|
|
46
|
-
targetFile =
|
|
77
|
+
targetFile =
|
|
78
|
+
files.find((file) => file.path.endsWith(".circuit.tsx")) ?? null
|
|
47
79
|
}
|
|
48
80
|
|
|
49
81
|
if (!targetFile) {
|
|
50
|
-
targetFile =
|
|
82
|
+
targetFile =
|
|
83
|
+
files.find(
|
|
84
|
+
(file) => file.path === "main.tsx" || file.path === "main.ts",
|
|
85
|
+
) ?? null
|
|
51
86
|
}
|
|
52
87
|
|
|
53
88
|
if (!targetFile) {
|
|
54
|
-
targetFile = files.find((file) => file.path
|
|
89
|
+
targetFile = files.find((file) => file.path.endsWith(".tsx")) ?? null
|
|
55
90
|
}
|
|
56
91
|
|
|
57
|
-
if (!targetFile && files[0]) {
|
|
92
|
+
if (!targetFile && files[0] && fallbackToAnyFile) {
|
|
58
93
|
targetFile = files[0]
|
|
59
94
|
}
|
|
60
95
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const isComponentExported = (code: string) => {
|
|
2
|
+
return (
|
|
3
|
+
/export function\s+\w+/.test(code) ||
|
|
4
|
+
/export const\s+\w+\s*=/.test(code) ||
|
|
5
|
+
/export default\s+\w+/.test(code) ||
|
|
6
|
+
/export default\s+function\s*(\w*)\s*\(/.test(code) ||
|
|
7
|
+
/export default\s*\(\s*\)\s*=>/.test(code) ||
|
|
8
|
+
/export default\s*\(.*?\)\s*=>/.test(code)
|
|
9
|
+
)
|
|
10
|
+
}
|
package/src/main.tsx
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { StrictMode } from "react"
|
|
2
2
|
import { createRoot } from "react-dom/client"
|
|
3
3
|
import App from "./App.tsx"
|
|
4
|
+
import "./lib/sentry"
|
|
5
|
+
import "./index.css"
|
|
4
6
|
|
|
5
7
|
if (typeof window !== "undefined" && !window.__APP_LOADED_AT) {
|
|
6
8
|
window.__APP_LOADED_AT = Date.now()
|
|
7
9
|
}
|
|
8
|
-
import "./index.css"
|
|
9
10
|
|
|
10
11
|
createRoot(document.getElementById("root")!).render(
|
|
11
12
|
<StrictMode>
|