From fd1a189e15cebcd641c3127b10114a366ce0dbca Mon Sep 17 00:00:00 2001 From: Jeongho Nam Date: Sun, 3 Nov 2024 04:04:35 +0900 Subject: [PATCH 01/16] `NestiaEditorModule` --- packages/editor/package.json | 14 ++- packages/editor/src/NestiaEditorModule.ts | 130 ++++++++++++++++++++++ packages/editor/test/express.ts | 22 ++++ packages/editor/test/fastify.ts | 25 +++++ packages/editor/tsconfig.app.tsbuildinfo | 2 +- packages/editor/tsconfig.test.json | 10 ++ website/package-lock.json | 32 ++++-- website/package.json | 2 +- website/pages/docs/editor.mdx | 55 ++++++++- 9 files changed, 272 insertions(+), 20 deletions(-) create mode 100644 packages/editor/src/NestiaEditorModule.ts create mode 100644 packages/editor/test/express.ts create mode 100644 packages/editor/test/fastify.ts create mode 100644 packages/editor/tsconfig.test.json diff --git a/packages/editor/package.json b/packages/editor/package.json index 7ce93fd3f..08138fd3c 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -1,12 +1,13 @@ { "name": "@nestia/editor", - "version": "0.6.2", + "version": "0.7.1", "typings": "lib/index.d.ts", "main": "lib/index.js", "module": "lib/index.mjs", "scripts": { - "build:lib": "tsc --project tsconfig.lib.json && rollup -c", - "build:static": "tsc -b && vite build", + "build": "npm run build:static && npm run build:lib", + "build:static": "rimraf dist && tsc -b && vite build", + "build:lib": "rimraf lib && tsc --project tsconfig.lib.json && rollup -c", "dev": "vite", "lint": "eslint .", "preview": "vite preview" @@ -42,9 +43,14 @@ }, "devDependencies": { "@eslint/js": "^9.13.0", + "@nestjs/common": "^10.4.6", + "@nestjs/core": "^10.4.6", + "@nestjs/platform-express": "^10.4.6", + "@nestjs/platform-fastify": "^10.4.6", "@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-typescript": "^12.1.1", "@types/js-yaml": "^4.0.9", + "@types/node": "^22.8.6", "@types/react": "^18.3.11", "@types/react-dom": "^18.3.1", "@vitejs/plugin-react": "^4.3.3", @@ -55,6 +61,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "rollup": "^4.24.2", + "ts-node": "^10.9.2", "typescript": "^5.6.2", "typescript-eslint": "^8.10.0", "vite": "^5.4.9" @@ -63,6 +70,7 @@ "README.md", "LICENSE", "package.json", + "dist", "lib", "src" ] diff --git a/packages/editor/src/NestiaEditorModule.ts b/packages/editor/src/NestiaEditorModule.ts new file mode 100644 index 000000000..c5571f48a --- /dev/null +++ b/packages/editor/src/NestiaEditorModule.ts @@ -0,0 +1,130 @@ +import type { OpenApiV3, OpenApiV3_1, SwaggerV2 } from "@samchon/openapi"; +import * as fs from "fs"; + +export namespace NestiaEditorModule { + export const setup = async (props: { + path: string; + application: INestApplication; + swagger: + | string + | SwaggerV2.IDocument + | OpenApiV3.IDocument + | OpenApiV3_1.IDocument; + package?: string; + simulate?: boolean; + e2e?: boolean; + }): Promise => { + const prefix: string = + "/" + + [getGlobalPrefix(props.application), props.path] + .join("/") + .split("/") + .filter((str) => str.length !== 0) + .join("/"); + const adaptor: INestHttpAdaptor = props.application.getHttpAdapter(); + const staticFiles: IStaticFile[] = [ + { + path: "/index.html", + type: "text/html", + content: await getIndex(props), + }, + { + path: "/swagger.json", + type: "application/json", + content: JSON.stringify( + typeof props.swagger === "string" + ? await getSwagger(props.swagger) + : props.swagger, + null, + 2, + ), + }, + await getJavaScript(), + ]; + for (const f of staticFiles) { + adaptor.get(prefix + f.path, (_: any, res: any) => { + res.type(f.type); + return res.send(f.content); + }); + } + for (const p of ["", "/"]) + adaptor.get(prefix + p, (_: any, res: any) => { + return res.redirect(prefix + "/index.html"); + }); + }; + + const getGlobalPrefix = (app: INestApplication): string => + typeof (app as any).config?.globalPrefix === "string" + ? (app as any).config.globalPrefix + : ""; +} + +interface INestApplication { + use(...args: any[]): this; + getUrl(): Promise; + getHttpAdapter(): INestHttpAdaptor; + setGlobalPrefix(prefix: string, options?: any): this; +} +interface INestHttpAdaptor { + getType(): string; + close(): any; + init?(): Promise; + get: Function; + post: Function; + put: Function; + patch: Function; + delete: Function; + head: Function; + all: Function; +} +interface IStaticFile { + path: string; + type: string; + content: string; +} + +const getIndex = async (props: { + package?: string; + simulate?: boolean; + e2e?: boolean; +}): Promise => { + const content: string = await fs.promises.readFile( + `${__dirname}/../dist/index.html`, + "utf8", + ); + return content + .replace( + `"@ORGANIZATION/PROJECT"`, + JSON.stringify(props.package ?? "@ORGANIZATION/PROJECT"), + ) + .replace("window.simulate = false", `window.simulate = ${!!props.simulate}`) + .replace("window.e2e = false", `window.e2e = ${!!props.e2e}`); +}; + +const getJavaScript = async (): Promise => { + const directory: string[] = await fs.promises.readdir( + `${__dirname}/../dist/assets`, + ); + const path: string | undefined = directory[0]; + if (path === undefined) + throw new Error("Unreachable code, no JS file exists."); + return { + path: `/assets/${path}`, + type: "application/javascript", + content: await fs.promises.readFile( + `${__dirname}/../dist/assets/${path}`, + "utf8", + ), + }; +}; + +const getSwagger = async ( + url: string, +): Promise< + SwaggerV2.IDocument | OpenApiV3.IDocument | OpenApiV3_1.IDocument +> => { + const response: Response = await fetch(url); + if (response.status !== 200) + throw new Error(`Failed to fetch Swagger document from ${url}`); + return response.json(); +}; diff --git a/packages/editor/test/express.ts b/packages/editor/test/express.ts new file mode 100644 index 000000000..6ca164f16 --- /dev/null +++ b/packages/editor/test/express.ts @@ -0,0 +1,22 @@ +import { Module } from "@nestjs/common"; +import { NestFactory } from "@nestjs/core"; + +import { NestiaEditorModule } from "../src/NestiaEditorModule"; + +@Module({}) +class MyModule {} + +const main = async (): Promise => { + const app = await NestFactory.create(MyModule, { logger: false }); + await NestiaEditorModule.setup({ + path: "editor", + application: app, + swagger: + "https://raw.githubusercontent.com/samchon/openapi/refs/heads/master/examples/v3.1/shopping.json", + }); + await app.listen(3_001); +}; +main().catch((exp) => { + console.error(exp); + process.exit(-1); +}); diff --git a/packages/editor/test/fastify.ts b/packages/editor/test/fastify.ts new file mode 100644 index 000000000..08d0f4ed9 --- /dev/null +++ b/packages/editor/test/fastify.ts @@ -0,0 +1,25 @@ +import { Module } from "@nestjs/common"; +import { NestFactory } from "@nestjs/core"; +import { FastifyAdapter } from "@nestjs/platform-fastify"; + +import { NestiaEditorModule } from "../src/NestiaEditorModule"; + +@Module({}) +class MyModule {} + +const main = async (): Promise => { + const app = await NestFactory.create(MyModule, new FastifyAdapter(), { + logger: false, + }); + await NestiaEditorModule.setup({ + path: "editor", + application: app, + swagger: + "https://raw.githubusercontent.com/samchon/openapi/refs/heads/master/examples/v3.1/shopping.json", + }); + await app.listen(3_001); +}; +main().catch((exp) => { + console.error(exp); + process.exit(-1); +}); diff --git a/packages/editor/tsconfig.app.tsbuildinfo b/packages/editor/tsconfig.app.tsbuildinfo index 06b0e2197..9ada0afca 100644 --- a/packages/editor/tsconfig.app.tsbuildinfo +++ b/packages/editor/tsconfig.app.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/nestiaeditorapplication.tsx","./src/nestiaeditoriframe.tsx","./src/nestiaeditoruploader.tsx","./src/index.ts","./src/main.tsx","./src/vite-env.d.ts","./src/internal/nestiaeditorcomposer.ts","./src/internal/nestiaeditorfileuploader.tsx"],"errors":true,"version":"5.6.3"} \ No newline at end of file +{"root":["./src/nestiaeditorapplication.tsx","./src/nestiaeditoriframe.tsx","./src/nestiaeditormodule.ts","./src/nestiaeditoruploader.tsx","./src/index.ts","./src/main.tsx","./src/vite-env.d.ts","./src/internal/nestiaeditorcomposer.ts","./src/internal/nestiaeditorfileuploader.tsx"],"version":"5.6.3"} \ No newline at end of file diff --git a/packages/editor/tsconfig.test.json b/packages/editor/tsconfig.test.json new file mode 100644 index 000000000..e6f067419 --- /dev/null +++ b/packages/editor/tsconfig.test.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.lib.json", + "compilerOptions": { + "target": "ES2015", + "module": "CommonJS", + "outDir": "bin", + "noEmit": true, + }, + "include": ["src", "test"] +} \ No newline at end of file diff --git a/website/package-lock.json b/website/package-lock.json index 9803a1e5c..af20eeee8 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -14,8 +14,7 @@ "@mui/icons-material": "5.15.6", "@mui/material": "5.15.6", "@mui/system": "5.15.6", - "@nestia/editor": "^0.6.1", - "jszip": "^3.10.1", + "@nestia/editor": "^0.7.1", "next": "14.2.13", "nextra": "^2.13.4", "nextra-theme-docs": "^2.13.4", @@ -26,6 +25,7 @@ "@types/node": "18.11.10", "@types/react": "18.0.35", "gh-pages": "^5.0.0", + "jszip": "^3.10.1", "next-sitemap": "^4.2.3", "rimraf": "^5.0.0", "ts-node": "^10.9.2", @@ -1069,9 +1069,9 @@ } }, "node_modules/@nestia/editor": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@nestia/editor/-/editor-0.6.1.tgz", - "integrity": "sha512-cPzTfi2V/ncKkcH12bga4TZo7am7nXGNypM5MG/9No8ps0NUYMeYXkxl+xlH8Q6VwHVuoe9bYfUCB/PqLWULsg==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@nestia/editor/-/editor-0.7.1.tgz", + "integrity": "sha512-gvIuUJ+MYHA9i2bwT7KwHxIRpLhOeqC1yf0d1VZ6fsNM4/QKPDxXE93CpFl5AKDKJ2i5v+CT/EnKnsXZTvscHA==", "dependencies": { "@mui/material": "^5.15.6", "@nestia/migrate": "^0.19.0", @@ -3778,7 +3778,8 @@ "node_modules/immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "dev": true }, "node_modules/import-fresh": { "version": "3.3.0", @@ -4049,7 +4050,8 @@ "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true }, "node_modules/isexe": { "version": "2.0.0", @@ -4121,6 +4123,7 @@ "version": "3.10.1", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "dev": true, "dependencies": { "lie": "~3.3.0", "pako": "~1.0.2", @@ -4132,6 +4135,7 @@ "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -4145,12 +4149,14 @@ "node_modules/jszip/node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true }, "node_modules/jszip/node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, "dependencies": { "safe-buffer": "~5.1.0" } @@ -4208,6 +4214,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dev": true, "dependencies": { "immediate": "~3.0.5" } @@ -6168,7 +6175,8 @@ "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true }, "node_modules/parent-module": { "version": "1.0.1", @@ -6423,7 +6431,8 @@ "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true }, "node_modules/prop-types": { "version": "15.8.1", @@ -7096,7 +7105,8 @@ "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true }, "node_modules/shebang-command": { "version": "1.2.0", diff --git a/website/package.json b/website/package.json index 634291366..b09755dbe 100644 --- a/website/package.json +++ b/website/package.json @@ -23,7 +23,7 @@ "@mui/icons-material": "5.15.6", "@mui/material": "5.15.6", "@mui/system": "5.15.6", - "@nestia/editor": "^0.6.2", + "@nestia/editor": "^0.7.1", "next": "14.2.13", "nextra": "^2.13.4", "nextra-theme-docs": "^2.13.4", diff --git a/website/pages/docs/editor.mdx b/website/pages/docs/editor.mdx index 67715eb05..c9e9ea466 100644 --- a/website/pages/docs/editor.mdx +++ b/website/pages/docs/editor.mdx @@ -40,7 +40,8 @@ Here are the some example projects generated by `@nestia/editor`. Traveling thos -## React Library +## Frontend Setup +### React Library ```typescript import { NestiaEditorIframe } from "@nestia/editor"; import { SwaggerV2, OpenApiV3, OpenApiV3_1 } from "@samchon/openapi"; @@ -64,7 +65,7 @@ If you've prepared the Swagger Document to serve, you can directly launch the cl -## Static Hosting +### Static Hosting ![Unzipped](/images/editor/unzipped.png) > [💾 https://nestia.io/downloads/editor.zip](/downloads/editor.zip) @@ -102,7 +103,7 @@ By the way, if you do not place the `swagger.json` (or `swagger.yaml`) file into -## ` ``` @@ -125,4 +126,50 @@ If you wanna see the example cases of the `