From 543260479a61c1a9627a87a11bb47170a93e45e9 Mon Sep 17 00:00:00 2001 From: Khairul Hidayat Date: Wed, 28 Feb 2024 22:32:31 +0000 Subject: [PATCH] fix: typescript support fix, add some params to editor, etc --- components/ui/code-editor.tsx | 9 +++- package.json | 2 + pages/project/@slug/+Page.tsx | 18 ++++--- .../project/@slug/components/api-manager.tsx | 1 + pages/project/@slug/components/editor.tsx | 29 +++++------ .../project/@slug/components/web-preview.tsx | 4 +- pages/project/@slug/context/project.tsx | 1 + pnpm-lock.yaml | 48 +++++++++++++++++++ server/api/index.ts | 3 +- server/api/preview/serve-js.ts | 5 +- server/index.ts | 13 +++-- server/lib/mime.ts | 2 +- server/lib/transform-js.ts | 6 ++- server/lib/unpack-project.ts | 1 + server/middlewares/nocache.ts | 12 +++++ server/routers/project.ts | 16 +++++++ 16 files changed, 136 insertions(+), 34 deletions(-) create mode 100644 server/middlewares/nocache.ts diff --git a/components/ui/code-editor.tsx b/components/ui/code-editor.tsx index 5065da6..2ee2059 100644 --- a/components/ui/code-editor.tsx +++ b/components/ui/code-editor.tsx @@ -130,13 +130,18 @@ function getLangMetadata(lang: string) { case "js": case "ts": case "tsx": + const isTypescript = ["tsx", "ts"].includes(lang); extensions = [ javascript({ jsx: ["jsx", "tsx"].includes(lang), - typescript: ["tsx", "ts"].includes(lang), + typescript: isTypescript, }), ]; - formatter = ["babel", prettierBabelPlugin, prettierPluginEstree]; + formatter = [ + isTypescript ? "babel-ts" : "babel", + prettierBabelPlugin, + prettierPluginEstree, + ]; break; } diff --git a/package.json b/package.json index 58f7b85..54b6748 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "@types/cookie-parser": "^1.4.6", "@types/express": "^4.17.21", "@types/jsonwebtoken": "^9.0.5", + "@types/morgan": "^1.9.9", "@types/node": "^20.11.19", "@types/nprogress": "^0.2.3", "@types/react": "^18.2.57", @@ -74,6 +75,7 @@ "jsonwebtoken": "^9.0.2", "lucide-react": "^0.331.0", "mime": "^4.0.1", + "morgan": "^1.10.0", "node-fetch": "^3.3.2", "nprogress": "^0.2.0", "postcss": "^8", diff --git a/pages/project/@slug/+Page.tsx b/pages/project/@slug/+Page.tsx index bdc5462..9e5d83e 100644 --- a/pages/project/@slug/+Page.tsx +++ b/pages/project/@slug/+Page.tsx @@ -15,20 +15,23 @@ import { Data } from "./+data"; const ViewProjectPage = () => { const { project } = useData(); const searchParams = useSearchParams(); - const isCompact = !!( - searchParams.get("compact") || searchParams.get("embed") - ); + const isCompact = Boolean(searchParams.get("compact")); + const isEmbed = Boolean(searchParams.get("embed")); + const hidePreview = searchParams.get("preview") === "0"; const previewUrl = getPreviewUrl(project, "index.html"); return ( - + { { const Actions = ({ stats }: any) => { const { project } = useProjectContext(); + const restart = useMutation({ mutationFn: () => { return api(`/sandbox/${project.slug}/restart`, { method: "POST" }); diff --git a/pages/project/@slug/components/editor.tsx b/pages/project/@slug/components/editor.tsx index c9f6b05..999ecc9 100644 --- a/pages/project/@slug/components/editor.tsx +++ b/pages/project/@slug/components/editor.tsx @@ -73,10 +73,10 @@ const Editor = () => { } }, [openedFilesData.data]); - useEffect(() => { - // start API sandbox - api(`/sandbox/${project.slug}/start`, { method: "POST" }).catch(() => {}); - }, [project]); + // useEffect(() => { + // // start API sandbox + // api(`/sandbox/${project.slug}/start`, { method: "POST" }).catch(() => {}); + // }, [project]); const onOpenFile = useCallback( (fileId: number, autoSwitchTab = true) => { @@ -154,17 +154,18 @@ const Editor = () => { }); } - tabs.push({ - title: "API", - icon: , - render: () => , - locked: true, - }); + // tabs.push({ + // title: "API", + // icon: , + // render: () => , + // locked: true, + // }); return tabs; }, [curOpenFiles, openedFiles, breakpoint]); - const PanelComponent = !projectCtx.isCompact ? Panel : "div"; + const PanelComponent = + !projectCtx.isCompact || !projectCtx.isEmbed ? Panel : "div"; return ( { className="flex-1 order-2 md:order-1" > { - + { refresh(); }, [refresh, togglePanel]); - const PanelComponent = !project.isCompact ? Panel : "div"; + const PanelComponent = !project.isCompact || !project.isEmbed ? Panel : "div"; return ( { id="web-preview" ref={frameRef} className="border-none w-full flex-1 overflow-hidden bg-white" - sandbox="allow-scripts" + sandbox="allow-scripts allow-forms" /> ) : null} diff --git a/pages/project/@slug/context/project.tsx b/pages/project/@slug/context/project.tsx index 110bfb2..5eae37f 100644 --- a/pages/project/@slug/context/project.tsx +++ b/pages/project/@slug/context/project.tsx @@ -4,6 +4,7 @@ import type { ProjectSchema } from "~/server/db/schema/project"; type TProjectContext = { project: ProjectSchema; isCompact?: boolean; + isEmbed?: boolean; }; const ProjectContext = createContext(null); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b540456..ab06543 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -110,6 +110,9 @@ dependencies: mime: specifier: ^4.0.1 version: 4.0.1 + morgan: + specifier: ^1.10.0 + version: 1.10.0 node-fetch: specifier: ^3.3.2 version: 3.3.2 @@ -178,6 +181,9 @@ devDependencies: '@types/jsonwebtoken': specifier: ^9.0.5 version: 9.0.5 + '@types/morgan': + specifier: ^1.9.9 + version: 1.9.9 '@types/node': specifier: ^20.11.19 version: 20.11.19 @@ -2338,6 +2344,12 @@ packages: resolution: {integrity: sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw==} dev: true + /@types/morgan@1.9.9: + resolution: {integrity: sha512-iRYSDKVaC6FkGSpEVVIvrRGw0DfJMiQzIn3qr2G5B3C//AWkulhXgaBd7tS9/J79GWSYMTHGs7PfI5b3Y8m+RQ==} + dependencies: + '@types/node': 20.11.19 + dev: true + /@types/node@20.11.19: resolution: {integrity: sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==} dependencies: @@ -2703,6 +2715,13 @@ packages: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} dev: false + /basic-auth@2.0.1: + resolution: {integrity: sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==} + engines: {node: '>= 0.8'} + dependencies: + safe-buffer: 5.1.2 + dev: false + /basic-ftp@5.0.4: resolution: {integrity: sha512-8PzkB0arJFV4jJWSGOYR+OEic6aeKMu/osRhBULN6RY0ykby6LKhbmuQ5ublvaas5BOwboah5D87nrHyuh8PPA==} engines: {node: '>=10.0.0'} @@ -4846,6 +4865,19 @@ packages: hasBin: true dev: false + /morgan@1.10.0: + resolution: {integrity: sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==} + engines: {node: '>= 0.8.0'} + dependencies: + basic-auth: 2.0.1 + debug: 2.6.9 + depd: 2.0.0 + on-finished: 2.3.0 + on-headers: 1.0.2 + transitivePeerDependencies: + - supports-color + dev: false + /mrmime@2.0.0: resolution: {integrity: sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==} engines: {node: '>=10'} @@ -5022,6 +5054,13 @@ packages: resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} dev: false + /on-finished@2.3.0: + resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} + engines: {node: '>= 0.8'} + dependencies: + ee-first: 1.1.1 + dev: false + /on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} @@ -5029,6 +5068,11 @@ packages: ee-first: 1.1.1 dev: false + /on-headers@1.0.2: + resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==} + engines: {node: '>= 0.8'} + dev: false + /once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} dependencies: @@ -5883,6 +5927,10 @@ packages: dependencies: queue-microtask: 1.2.3 + /safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + dev: false + /safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} diff --git a/server/api/index.ts b/server/api/index.ts index a4c7f98..affe173 100644 --- a/server/api/index.ts +++ b/server/api/index.ts @@ -3,11 +3,12 @@ import preview from "./preview"; import trpc from "./trpc/handler"; import { thumbnail } from "./thumbnail"; import sandbox from "./sandbox"; +import { nocache } from "../middlewares/nocache"; const api = Router(); api.use("/trpc", trpc); -api.use("/preview", preview); +api.use("/preview", nocache, preview); api.use("/sandbox", sandbox); api.get("/thumbnail/:slug", thumbnail); diff --git a/server/api/preview/serve-js.ts b/server/api/preview/serve-js.ts index 8235088..56bb7d1 100644 --- a/server/api/preview/serve-js.ts +++ b/server/api/preview/serve-js.ts @@ -1,3 +1,4 @@ +import { getFileExt } from "~/lib/utils"; import { FileSchema } from "~/server/db/schema/file"; import { ProjectSettingsSchema } from "~/server/db/schema/project"; import { transformJs } from "~/server/lib/transform-js"; @@ -10,7 +11,9 @@ export const serveJs = async ( // transpile to es5 if (cfg?.transpiler === "swc") { - content = await transformJs(content); + const ext = getFileExt(file.filename); + const typescript = ["ts", "tsx"].includes(ext); + content = await transformJs(content, typescript ? "ts" : "js"); } return content; diff --git a/server/index.ts b/server/index.ts index 3a05f1a..e3b957f 100644 --- a/server/index.ts +++ b/server/index.ts @@ -5,7 +5,6 @@ import { IS_DEV } from "./lib/consts"; import cookieParser from "cookie-parser"; import api from "./api"; import { authMiddleware } from "./middlewares/auth"; -import fetch from "node-fetch"; async function createServer() { const app = express(); @@ -24,8 +23,12 @@ async function createServer() { } else { // serve client assets app.use(express.static(root + "/dist/client")); + + const { default: morgan } = await import("morgan"); + app.use(morgan("combined")); } + app.set("etag", false); app.use(cookieParser()); app.use(authMiddleware); @@ -42,9 +45,11 @@ async function createServer() { } const { body, statusCode, headers, earlyHints } = httpResponse; - if (res.writeEarlyHints) { - res.writeEarlyHints({ link: earlyHints.map((e) => e.earlyHintLink) }); - } + + // FIXME: SSL cloudflare gak bisa digunakan kalo ada ini.. + // if (res.writeEarlyHints) { + // res.writeEarlyHints({ link: earlyHints.map((e) => e.earlyHintLink) }); + // } headers.forEach(([name, value]) => res.setHeader(name, value)); res.status(statusCode).send(body); diff --git a/server/lib/mime.ts b/server/lib/mime.ts index 21c1e50..3e16297 100644 --- a/server/lib/mime.ts +++ b/server/lib/mime.ts @@ -3,7 +3,7 @@ import standardTypes from "mime/types/standard.js"; import otherTypes from "mime/types/other.js"; const mime = new Mime(standardTypes, otherTypes); -mime.define({ "text/javascript": ["jsx", "tsx"] }, true); +mime.define({ "text/javascript": ["jsx", "tsx", "ts"] }, true); export const getMimeType = ( ext: string, diff --git a/server/lib/transform-js.ts b/server/lib/transform-js.ts index 90fd0c2..c2bbfdc 100644 --- a/server/lib/transform-js.ts +++ b/server/lib/transform-js.ts @@ -1,12 +1,13 @@ import * as swc from "@swc/core"; -export const transformJs = async (code: string) => { +export const transformJs = async (code: string, type: "js" | "ts" = "js") => { try { const result = await swc.transform(code, { jsc: { parser: { + syntax: type === "js" ? "ecmascript" : "typescript", jsx: true, - syntax: "ecmascript", + tsx: true, }, target: "es5", }, @@ -14,6 +15,7 @@ export const transformJs = async (code: string) => { return result.code; } catch (err) { + // console.log(err); return code; } }; diff --git a/server/lib/unpack-project.ts b/server/lib/unpack-project.ts index 6d86ce4..6fe6a8f 100644 --- a/server/lib/unpack-project.ts +++ b/server/lib/unpack-project.ts @@ -17,6 +17,7 @@ export const unpackProject = async ( ) => { const files = await db.query.file.findMany({ where: and( + eq(file.projectId, projectData.id), eq(file.isDirectory, false), eq(file.isFile, false), isNull(file.deletedAt) diff --git a/server/middlewares/nocache.ts b/server/middlewares/nocache.ts new file mode 100644 index 0000000..6992dfd --- /dev/null +++ b/server/middlewares/nocache.ts @@ -0,0 +1,12 @@ +import { NextFunction, Request, Response } from "express"; + +export const nocache = (_req: Request, res: Response, next: NextFunction) => { + res.setHeader("Surrogate-Control", "no-store"); + res.setHeader( + "Cache-Control", + "no-store, no-cache, must-revalidate, proxy-revalidate" + ); + res.setHeader("Expires", "0"); + + next(); +}; diff --git a/server/routers/project.ts b/server/routers/project.ts index 8114178..844deb0 100644 --- a/server/routers/project.ts +++ b/server/routers/project.ts @@ -116,6 +116,17 @@ const projectRouter = router({ const projectId = projectData.id; if (input.forkFromId) { + const forkProject = await tx.query.project.findFirst({ + where: and( + eq(project.id, input.forkFromId), + isNull(project.deletedAt) + ), + }); + + if (!forkProject) { + throw new Error("Fork Project not found!"); + } + const forkFiles = await tx.query.file.findMany({ where: and( eq(file.projectId, input.forkFromId), @@ -132,6 +143,11 @@ const projectRouter = router({ await tx .insert(file) .values(forkFiles.map((file) => ({ ...file, projectId }))); + + await tx + .update(project) + .set({ settings: forkProject.settings }) + .where(eq(project.id, projectData.id)); } else { await tx.insert(file).values([ {