@tscircuit/fake-snippets 0.0.87 → 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 +207 -101
- package/fake-snippets-api/routes/api/package_releases/create.ts +1 -1
- package/fake-snippets-api/routes/api/proxy.ts +128 -0
- package/package.json +57 -50
- 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/Header.tsx +5 -1
- package/src/components/Header2.tsx +7 -2
- package/src/components/HeaderLogin.tsx +1 -1
- 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/PrefetchPageLink.tsx +66 -15
- package/src/components/SearchComponent.tsx +2 -2
- package/src/components/SuspenseRunFrame.tsx +14 -2
- package/src/components/ViewPackagePage/components/important-files-view.tsx +97 -22
- package/src/components/ViewPackagePage/components/main-content-header.tsx +27 -3
- package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +2 -2
- package/src/components/ViewPackagePage/components/repo-page-content.tsx +49 -34
- 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/ViewPackagePage/utils/is-package-file-important.ts +18 -5
- 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 +39 -38
- 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/user-profile.tsx +1 -1
- package/src/pages/view-package.tsx +15 -7
- package/vite.config.ts +100 -1
package/src/index.css
CHANGED
|
@@ -67,3 +67,14 @@
|
|
|
67
67
|
padding: 12px !important;
|
|
68
68
|
margin: 0 !important;
|
|
69
69
|
}
|
|
70
|
+
|
|
71
|
+
.markdown-content {
|
|
72
|
+
padding-top: 0.5rem;
|
|
73
|
+
max-width: none !important;
|
|
74
|
+
width: 100% !important;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.markdown-content .prose {
|
|
78
|
+
max-width: none !important;
|
|
79
|
+
width: 100% !important;
|
|
80
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { gunzipSync, strFromU8 } from "fflate"
|
|
2
|
+
import { base64ToBytes } from "./base64ToBytes"
|
|
3
|
+
|
|
4
|
+
export function decodeUrlHashToFsMap(
|
|
5
|
+
url: string,
|
|
6
|
+
): Record<string, string> | null {
|
|
7
|
+
const base64Data = url.split("#data:application/gzip;base64,")[1]
|
|
8
|
+
if (!base64Data) return null
|
|
9
|
+
try {
|
|
10
|
+
const compressedData = base64ToBytes(base64Data)
|
|
11
|
+
const decompressedData = gunzipSync(compressedData)
|
|
12
|
+
const text = strFromU8(decompressedData)
|
|
13
|
+
return JSON.parse(text)
|
|
14
|
+
} catch {
|
|
15
|
+
return null
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { AnyCircuitElement } from "circuit-json"
|
|
2
|
+
import {
|
|
3
|
+
convertCircuitJsonToAssemblySvg,
|
|
4
|
+
convertCircuitJsonToPcbSvg,
|
|
5
|
+
convertCircuitJsonToSchematicSvg,
|
|
6
|
+
} from "circuit-to-svg"
|
|
7
|
+
import { saveAs } from "file-saver"
|
|
8
|
+
|
|
9
|
+
export type ImageFormat = "schematic" | "pcb" | "assembly"
|
|
10
|
+
|
|
11
|
+
interface DownloadCircuitPngOptions {
|
|
12
|
+
format: ImageFormat
|
|
13
|
+
width?: number
|
|
14
|
+
height?: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const convertSvgToPng = async (svgString: string): Promise<Blob> => {
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
const canvas = document.createElement("canvas")
|
|
20
|
+
const ctx = canvas.getContext("2d")
|
|
21
|
+
if (!ctx) {
|
|
22
|
+
reject(new Error("Failed to get canvas context"))
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const img = new Image()
|
|
27
|
+
img.onload = () => {
|
|
28
|
+
canvas.width = img.width || 800
|
|
29
|
+
canvas.height = img.height || 600
|
|
30
|
+
ctx.fillStyle = "white"
|
|
31
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height)
|
|
32
|
+
ctx.drawImage(img, 0, 0)
|
|
33
|
+
|
|
34
|
+
canvas.toBlob((blob) => {
|
|
35
|
+
if (blob) {
|
|
36
|
+
resolve(blob)
|
|
37
|
+
} else {
|
|
38
|
+
reject(new Error("Failed to convert canvas to blob"))
|
|
39
|
+
}
|
|
40
|
+
}, "image/png")
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
img.onerror = () => {
|
|
44
|
+
reject(new Error("Failed to load SVG image"))
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const svgBlob = new Blob([svgString], { type: "image/svg+xml" })
|
|
48
|
+
const url = URL.createObjectURL(svgBlob)
|
|
49
|
+
img.src = url
|
|
50
|
+
|
|
51
|
+
setTimeout(() => URL.revokeObjectURL(url), 10000)
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const downloadCircuitPng = async (
|
|
56
|
+
circuitJson: AnyCircuitElement[],
|
|
57
|
+
fileName: string,
|
|
58
|
+
options: DownloadCircuitPngOptions = { format: "pcb" },
|
|
59
|
+
) => {
|
|
60
|
+
try {
|
|
61
|
+
let blob: Blob
|
|
62
|
+
let svg: string
|
|
63
|
+
|
|
64
|
+
const svgOptions: any = {}
|
|
65
|
+
if (options.width) svgOptions.width = options.width
|
|
66
|
+
if (options.height) svgOptions.height = options.height
|
|
67
|
+
|
|
68
|
+
switch (options.format) {
|
|
69
|
+
case "schematic":
|
|
70
|
+
svg = convertCircuitJsonToSchematicSvg(circuitJson, svgOptions)
|
|
71
|
+
break
|
|
72
|
+
case "pcb":
|
|
73
|
+
svg = convertCircuitJsonToPcbSvg(circuitJson, svgOptions)
|
|
74
|
+
break
|
|
75
|
+
case "assembly":
|
|
76
|
+
svg = convertCircuitJsonToAssemblySvg(circuitJson, svgOptions)
|
|
77
|
+
break
|
|
78
|
+
default:
|
|
79
|
+
throw new Error(`Unsupported format: ${options.format}`)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
blob = await convertSvgToPng(svg)
|
|
83
|
+
const downloadFileName = `${fileName}_${options.format}.png`
|
|
84
|
+
saveAs(blob, downloadFileName)
|
|
85
|
+
} catch (error) {
|
|
86
|
+
throw new Error(`Failed to download ${options.format} PNG: ${error}`)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { AnyCircuitElement } from "circuit-json"
|
|
2
|
+
import { downloadCircuitPng, ImageFormat } from "./download-circuit-png"
|
|
3
|
+
import { toast } from "@/hooks/use-toast"
|
|
4
|
+
|
|
5
|
+
interface DownloadPngOptions {
|
|
6
|
+
circuitJson?: AnyCircuitElement[] | null
|
|
7
|
+
unscopedName?: string
|
|
8
|
+
author?: string
|
|
9
|
+
format: ImageFormat
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const downloadPngImage = async ({
|
|
13
|
+
circuitJson,
|
|
14
|
+
unscopedName,
|
|
15
|
+
format,
|
|
16
|
+
}: DownloadPngOptions) => {
|
|
17
|
+
try {
|
|
18
|
+
if (!circuitJson) {
|
|
19
|
+
throw new Error("Circuit JSON not available")
|
|
20
|
+
}
|
|
21
|
+
await downloadCircuitPng(circuitJson, unscopedName || "circuit", {
|
|
22
|
+
format,
|
|
23
|
+
})
|
|
24
|
+
} catch (error: any) {
|
|
25
|
+
toast({
|
|
26
|
+
title: `Error Downloading ${format.charAt(0).toUpperCase() + format.slice(1)} PNG`,
|
|
27
|
+
description: error.toString(),
|
|
28
|
+
variant: "destructive",
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { gzipSync, strToU8 } from "fflate"
|
|
2
|
+
import { bytesToBase64 } from "./bytesToBase64"
|
|
3
|
+
|
|
4
|
+
export function encodeFsMapToUrlHash(
|
|
5
|
+
fsMap: Record<string, string>,
|
|
6
|
+
snippet_type?: string,
|
|
7
|
+
): string {
|
|
8
|
+
const text = JSON.stringify(fsMap)
|
|
9
|
+
const compressedData = gzipSync(strToU8(text))
|
|
10
|
+
const base64Data = bytesToBase64(compressedData)
|
|
11
|
+
const typeParam = snippet_type ? `&snippet_type=${snippet_type}` : ""
|
|
12
|
+
return `${window.location.origin}/editor?${typeParam}#data:application/gzip;base64,${base64Data}`
|
|
13
|
+
}
|
|
@@ -7,46 +7,47 @@ import type { QueryClient } from "react-query"
|
|
|
7
7
|
export function populateQueryCacheWithSSRData(queryClient: QueryClient) {
|
|
8
8
|
if (typeof window === "undefined") return
|
|
9
9
|
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
if (ssrPackage) {
|
|
15
|
-
// Cache package data with all possible query keys
|
|
16
|
-
queryClient.setQueryData(["package", ssrPackage.package_id], ssrPackage)
|
|
17
|
-
queryClient.setQueryData(["package", ssrPackage.name], ssrPackage)
|
|
18
|
-
queryClient.setQueryData(["packages", ssrPackage.package_id], ssrPackage)
|
|
19
|
-
}
|
|
10
|
+
const windowAny = window as any
|
|
11
|
+
const ssrPackage = windowAny.SSR_PACKAGE
|
|
12
|
+
const ssrPackageRelease = windowAny.SSR_PACKAGE_RELEASE
|
|
13
|
+
const ssrPackageFiles = windowAny.SSR_PACKAGE_FILES
|
|
20
14
|
|
|
21
|
-
if (
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
15
|
+
if (!ssrPackage || !ssrPackageRelease) return
|
|
16
|
+
|
|
17
|
+
// Cache lookups by package name and id
|
|
18
|
+
queryClient.setQueryData(["package", ssrPackage.name], ssrPackage)
|
|
19
|
+
queryClient.setQueryData(["package", ssrPackage.package_id], ssrPackage)
|
|
20
|
+
queryClient.setQueryData(["packages", ssrPackage.package_id], ssrPackage)
|
|
21
|
+
|
|
22
|
+
queryClient.setQueryData(
|
|
23
|
+
[
|
|
24
|
+
"packageRelease",
|
|
25
|
+
{
|
|
26
|
+
is_latest: true,
|
|
27
|
+
package_name: ssrPackage.name,
|
|
28
|
+
include_ai_review: true,
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
ssrPackageRelease,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
queryClient.setQueryData(
|
|
35
|
+
["packageRelease", { is_latest: true, package_id: ssrPackage.package_id }],
|
|
36
|
+
ssrPackageRelease,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
queryClient.setQueryData(
|
|
40
|
+
[
|
|
41
|
+
"packageRelease",
|
|
42
|
+
{ package_release_id: ssrPackageRelease.package_release_id },
|
|
43
|
+
],
|
|
44
|
+
ssrPackageRelease,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
if (ssrPackageFiles && ssrPackageRelease.package_release_id) {
|
|
30
48
|
queryClient.setQueryData(
|
|
31
|
-
["
|
|
32
|
-
|
|
49
|
+
["packageFiles", ssrPackageRelease.package_release_id],
|
|
50
|
+
ssrPackageFiles,
|
|
33
51
|
)
|
|
34
|
-
if (ssrPackageRelease.package_release_id) {
|
|
35
|
-
queryClient.setQueryData(
|
|
36
|
-
[
|
|
37
|
-
"packageRelease",
|
|
38
|
-
{ package_release_id: ssrPackageRelease.package_release_id },
|
|
39
|
-
],
|
|
40
|
-
ssrPackageRelease,
|
|
41
|
-
)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Cache package files if available
|
|
45
|
-
if (ssrPackageFiles && ssrPackageRelease.package_release_id) {
|
|
46
|
-
queryClient.setQueryData(
|
|
47
|
-
["packageFiles", ssrPackageRelease.package_release_id],
|
|
48
|
-
ssrPackageFiles,
|
|
49
|
-
)
|
|
50
|
-
}
|
|
51
52
|
}
|
|
52
53
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createDefaultMapFromCDN,
|
|
3
|
+
knownLibFilesForCompilerOptions,
|
|
4
|
+
} from "@typescript/vfs"
|
|
5
|
+
import { get, set } from "idb-keyval"
|
|
6
|
+
import { compressToUTF16, decompressFromUTF16 } from "lz-string"
|
|
7
|
+
import ts from "typescript"
|
|
8
|
+
|
|
9
|
+
const TS_LIB_VERSION = "5.6.3"
|
|
10
|
+
const CACHE_PREFIX = `ts-lib-${TS_LIB_VERSION}-`
|
|
11
|
+
|
|
12
|
+
export async function loadDefaultLibMap(): Promise<Map<string, string>> {
|
|
13
|
+
const fsMap = new Map<string, string>()
|
|
14
|
+
const libs = knownLibFilesForCompilerOptions(
|
|
15
|
+
{ target: ts.ScriptTarget.ES2022 },
|
|
16
|
+
ts,
|
|
17
|
+
)
|
|
18
|
+
const missing: string[] = []
|
|
19
|
+
|
|
20
|
+
for (const lib of libs) {
|
|
21
|
+
const cached = await get(CACHE_PREFIX + lib)
|
|
22
|
+
if (cached) {
|
|
23
|
+
fsMap.set("/" + lib, decompressFromUTF16(cached as string))
|
|
24
|
+
} else {
|
|
25
|
+
missing.push(lib)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (missing.length > 0) {
|
|
30
|
+
const fetched = await createDefaultMapFromCDN(
|
|
31
|
+
{ target: ts.ScriptTarget.ES2022 },
|
|
32
|
+
TS_LIB_VERSION,
|
|
33
|
+
true,
|
|
34
|
+
ts,
|
|
35
|
+
{ compressToUTF16, decompressFromUTF16 } as any,
|
|
36
|
+
)
|
|
37
|
+
for (const [filename, content] of fetched) {
|
|
38
|
+
fsMap.set(filename, content)
|
|
39
|
+
await set(
|
|
40
|
+
CACHE_PREFIX + filename.replace(/^\//, ""),
|
|
41
|
+
compressToUTF16(content),
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return fsMap
|
|
47
|
+
}
|
package/src/lib/types.ts
CHANGED
package/src/main.tsx
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { StrictMode } from "react"
|
|
2
2
|
import { createRoot } from "react-dom/client"
|
|
3
3
|
import App from "./App.tsx"
|
|
4
|
+
|
|
5
|
+
if (typeof window !== "undefined" && !window.__APP_LOADED_AT) {
|
|
6
|
+
window.__APP_LOADED_AT = Date.now()
|
|
7
|
+
}
|
|
4
8
|
import "./index.css"
|
|
9
|
+
import { setupBuildWatcher } from "./build-watcher"
|
|
10
|
+
|
|
11
|
+
setupBuildWatcher()
|
|
5
12
|
|
|
6
13
|
createRoot(document.getElementById("root")!).render(
|
|
7
14
|
<StrictMode>
|
package/src/pages/dashboard.tsx
CHANGED
|
@@ -41,14 +41,17 @@ export const DashboardPage = () => {
|
|
|
41
41
|
const response = await axios.post(`/packages/list`, {
|
|
42
42
|
owner_github_username: currentUser,
|
|
43
43
|
})
|
|
44
|
-
return response.data.packages
|
|
45
|
-
(a: any, b: any) =>
|
|
46
|
-
new Date(b.updated_at || b.created_at).getTime() -
|
|
47
|
-
new Date(a.updated_at || a.created_at).getTime(),
|
|
48
|
-
)
|
|
44
|
+
return response.data.packages
|
|
49
45
|
},
|
|
50
46
|
{
|
|
51
47
|
enabled: isLoggedIn,
|
|
48
|
+
select: (data: Package[]) => {
|
|
49
|
+
return [...data].sort(
|
|
50
|
+
(a: Package, b: Package) =>
|
|
51
|
+
new Date(b.updated_at || b.created_at).getTime() -
|
|
52
|
+
new Date(a.updated_at || a.created_at).getTime(),
|
|
53
|
+
)
|
|
54
|
+
},
|
|
52
55
|
},
|
|
53
56
|
)
|
|
54
57
|
|
|
@@ -55,7 +55,7 @@ export const UserProfilePage = () => {
|
|
|
55
55
|
)
|
|
56
56
|
|
|
57
57
|
// use the username stored in the database so the correct case is displayed
|
|
58
|
-
const githubUsername = account?.account
|
|
58
|
+
const githubUsername = account?.account?.github_username || username
|
|
59
59
|
const isCurrentUserProfile = githubUsername === session?.github_username
|
|
60
60
|
|
|
61
61
|
const { Dialog: DeleteDialog, openDialog: openDeleteDialog } =
|
|
@@ -21,11 +21,17 @@ export const ViewPackagePage = () => {
|
|
|
21
21
|
data: packageRelease,
|
|
22
22
|
error: packageReleaseError,
|
|
23
23
|
isLoading: isLoadingPackageRelease,
|
|
24
|
-
} = usePackageRelease(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
} = usePackageRelease(
|
|
25
|
+
{
|
|
26
|
+
is_latest: true,
|
|
27
|
+
package_name: `${author}/${packageName}`,
|
|
28
|
+
include_ai_review: true,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
refetchInterval: (data) =>
|
|
32
|
+
data?.ai_review_requested && !data.ai_review_text ? 2000 : false,
|
|
33
|
+
},
|
|
34
|
+
)
|
|
29
35
|
|
|
30
36
|
const { data: packageFiles } = usePackageFiles(
|
|
31
37
|
packageRelease?.package_release_id,
|
|
@@ -46,15 +52,17 @@ export const ViewPackagePage = () => {
|
|
|
46
52
|
</Helmet>
|
|
47
53
|
<RepoPageContent
|
|
48
54
|
packageFiles={packageFiles as any}
|
|
49
|
-
packageInfo={packageInfo
|
|
50
|
-
packageRelease={packageRelease
|
|
55
|
+
packageInfo={packageInfo}
|
|
56
|
+
packageRelease={packageRelease}
|
|
51
57
|
importantFilePaths={["README.md", "LICENSE", "package.json"]}
|
|
52
58
|
onFileClicked={(file) => {
|
|
59
|
+
if (!packageInfo?.package_id) return
|
|
53
60
|
setLocation(
|
|
54
61
|
`/editor?package_id=${packageInfo?.package_id}&file_path=${file.file_path}`,
|
|
55
62
|
)
|
|
56
63
|
}}
|
|
57
64
|
onEditClicked={(file_path?: string) => {
|
|
65
|
+
if (!packageInfo?.package_id) return
|
|
58
66
|
setLocation(
|
|
59
67
|
`/editor?package_id=${packageInfo?.package_id}${
|
|
60
68
|
file_path ? `&file_path=${file_path}` : ""
|
package/vite.config.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { createDatabase } from "./fake-snippets-api/lib/db/db-client"
|
|
2
2
|
import { defineConfig, Plugin, UserConfig } from "vite"
|
|
3
3
|
import type { PluginOption } from "vite"
|
|
4
|
-
import path from "path"
|
|
4
|
+
import path, { extname } from "path"
|
|
5
|
+
import { readFileSync } from "fs"
|
|
6
|
+
import { execSync } from "child_process"
|
|
5
7
|
import react from "@vitejs/plugin-react"
|
|
6
8
|
import { ViteImageOptimizer } from "vite-plugin-image-optimizer"
|
|
7
9
|
import { getNodeHandler } from "winterspec/adapters/node"
|
|
8
10
|
import vercel from "vite-plugin-vercel"
|
|
11
|
+
import type { IncomingMessage, ServerResponse } from "http"
|
|
9
12
|
|
|
10
13
|
// @ts-ignore
|
|
11
14
|
import winterspecBundle from "./dist/bundle.js"
|
|
@@ -39,6 +42,100 @@ function apiFakePlugin(): Plugin {
|
|
|
39
42
|
}
|
|
40
43
|
}
|
|
41
44
|
|
|
45
|
+
function vercelSsrDevPlugin(): Plugin {
|
|
46
|
+
return {
|
|
47
|
+
name: "vercel-ssr-dev",
|
|
48
|
+
apply: "serve",
|
|
49
|
+
async configureServer(server) {
|
|
50
|
+
const port = server.config.server.port || 5173
|
|
51
|
+
process.env.TSC_DEV_SSR = "true"
|
|
52
|
+
process.env.TSC_BASE_URL = `http://127.0.0.1:${port}`
|
|
53
|
+
process.env.TSC_REGISTRY_API = `http://127.0.0.1:${port}/api`
|
|
54
|
+
|
|
55
|
+
const { default: ssrHandler, setHtmlContent } = await import(
|
|
56
|
+
"./api/generated-index.js"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
server.middlewares.use(async (req, res, next) => {
|
|
60
|
+
const url = req.url?.split("?")[0] || ""
|
|
61
|
+
const accept = req.headers.accept || ""
|
|
62
|
+
|
|
63
|
+
if (url === "/" || url === "/landing.html") {
|
|
64
|
+
return next()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (url.startsWith("/api/")) {
|
|
68
|
+
return next()
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!accept.includes("text/html")) {
|
|
72
|
+
return next()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (extname(url)) {
|
|
76
|
+
return next()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const patchedRes = res as ServerResponse & {
|
|
80
|
+
status?: (code: number) => ServerResponse
|
|
81
|
+
send?: (body: any) => void
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!patchedRes.status) {
|
|
85
|
+
patchedRes.status = function (code: number) {
|
|
86
|
+
patchedRes.statusCode = code
|
|
87
|
+
return patchedRes
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!patchedRes.send) {
|
|
92
|
+
patchedRes.send = function (body: any) {
|
|
93
|
+
if (Buffer.isBuffer(body) || typeof body === "string") {
|
|
94
|
+
patchedRes.end(body)
|
|
95
|
+
} else {
|
|
96
|
+
patchedRes.end(JSON.stringify(body))
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
let rawHtml = readFileSync(
|
|
103
|
+
path.resolve(__dirname, "index.html"),
|
|
104
|
+
"utf-8",
|
|
105
|
+
)
|
|
106
|
+
rawHtml = rawHtml.replace(
|
|
107
|
+
/src="\.\/src\/main\.tsx"/,
|
|
108
|
+
'src="/src/main.tsx"',
|
|
109
|
+
)
|
|
110
|
+
const transformed = await server.transformIndexHtml(url, rawHtml)
|
|
111
|
+
setHtmlContent(transformed)
|
|
112
|
+
await ssrHandler(req as IncomingMessage, patchedRes)
|
|
113
|
+
} catch (err) {
|
|
114
|
+
console.error("SSR handler error", err)
|
|
115
|
+
next()
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
},
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function buildIdPlugin(): Plugin {
|
|
123
|
+
return {
|
|
124
|
+
name: "build-id",
|
|
125
|
+
transformIndexHtml(html) {
|
|
126
|
+
try {
|
|
127
|
+
const hash = execSync("git rev-parse --short HEAD").toString().trim()
|
|
128
|
+
return html.replace(
|
|
129
|
+
/<\/head>/i,
|
|
130
|
+
` <meta name="tscircuit-build" content="${hash}"></head>`,
|
|
131
|
+
)
|
|
132
|
+
} catch {
|
|
133
|
+
return html
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
42
139
|
export default defineConfig(async (): Promise<UserConfig> => {
|
|
43
140
|
let proxyConfig: Record<string, any> | undefined
|
|
44
141
|
|
|
@@ -71,6 +168,8 @@ export default defineConfig(async (): Promise<UserConfig> => {
|
|
|
71
168
|
effort: 6,
|
|
72
169
|
},
|
|
73
170
|
}),
|
|
171
|
+
vercelSsrDevPlugin(),
|
|
172
|
+
buildIdPlugin(),
|
|
74
173
|
]
|
|
75
174
|
|
|
76
175
|
if (process.env.VITE_BUNDLE_ANALYZE === "true" || 1) {
|