From 6e8bef2749274f2d797b06f09c8ee96afe01be85 Mon Sep 17 00:00:00 2001 From: Atsuki Hasegawa <58581243+atsuki44@users.noreply.github.com> Date: Fri, 7 Mar 2025 20:20:29 +0900 Subject: [PATCH 01/39] =?UTF-8?q?feat:=20migrate=20.morph/frontend=20?= =?UTF-8?q?=E2=86=92=20@morph-data/frontend?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/morph/api/app.py | 22 ++-- core/morph/frontend/template/.eslintrc.cjs | 18 --- core/morph/frontend/template/.gitignore | 32 ----- core/morph/frontend/template/index.html | 12 -- core/morph/frontend/template/package.json | 41 ------ .../frontend/template/src/admin/AdminPage.tsx | 5 - .../src/admin/common/useResourcesQuery.ts | 37 ------ .../src/admin/common/useScheduledJobsQuery.ts | 32 ----- .../src/admin/common/utils/useQuery.ts | 36 ----- .../src/admin/datapipeline/DataPipeline.tsx | 32 ----- .../template/src/admin/datapipeline/Flow.tsx | 83 ------------ .../src/admin/datapipeline/ResourceDetail.tsx | 120 ----------------- .../src/admin/datapipeline/ResourceNode.tsx | 79 ----------- .../template/src/assets/icons/database.svg | 1 - .../template/src/assets/icons/python.svg | 1 - .../frontend/template/src/error-page.tsx | 44 ------- core/morph/frontend/template/src/main.tsx | 123 ------------------ .../frontend/template/src/mdx-componens.tsx | 105 --------------- core/morph/frontend/template/src/mdx.d.ts | 4 - .../frontend/template/src/page-skeleton.tsx | 102 --------------- .../morph/frontend/template/src/vite-env.d.ts | 1 - .../morph/frontend/template/tsconfig.app.json | 24 ---- .../frontend/template/tsconfig.node.json | 13 -- core/morph/frontend/template/vite.config.ts | 59 --------- .../include/starter_template/package.json | 20 ++- .../starter_template/src/pages/_app.tsx | 43 ++++++ .../starter_template/tsconfig.app.json | 7 + .../starter_template}/tsconfig.json | 2 +- .../starter_template/tsconfig.node.json | 7 + .../include/starter_template/vite.config.ts | 6 + core/morph/task/api.py | 14 +- core/morph/task/deploy.py | 8 +- core/morph/task/new.py | 6 - core/morph/task/utils/morph.py | 21 +-- 34 files changed, 95 insertions(+), 1065 deletions(-) delete mode 100644 core/morph/frontend/template/.eslintrc.cjs delete mode 100644 core/morph/frontend/template/.gitignore delete mode 100644 core/morph/frontend/template/index.html delete mode 100644 core/morph/frontend/template/package.json delete mode 100644 core/morph/frontend/template/src/admin/AdminPage.tsx delete mode 100644 core/morph/frontend/template/src/admin/common/useResourcesQuery.ts delete mode 100644 core/morph/frontend/template/src/admin/common/useScheduledJobsQuery.ts delete mode 100644 core/morph/frontend/template/src/admin/common/utils/useQuery.ts delete mode 100644 core/morph/frontend/template/src/admin/datapipeline/DataPipeline.tsx delete mode 100644 core/morph/frontend/template/src/admin/datapipeline/Flow.tsx delete mode 100644 core/morph/frontend/template/src/admin/datapipeline/ResourceDetail.tsx delete mode 100644 core/morph/frontend/template/src/admin/datapipeline/ResourceNode.tsx delete mode 100644 core/morph/frontend/template/src/assets/icons/database.svg delete mode 100644 core/morph/frontend/template/src/assets/icons/python.svg delete mode 100644 core/morph/frontend/template/src/error-page.tsx delete mode 100644 core/morph/frontend/template/src/main.tsx delete mode 100644 core/morph/frontend/template/src/mdx-componens.tsx delete mode 100644 core/morph/frontend/template/src/mdx.d.ts delete mode 100644 core/morph/frontend/template/src/page-skeleton.tsx delete mode 100644 core/morph/frontend/template/src/vite-env.d.ts delete mode 100644 core/morph/frontend/template/tsconfig.app.json delete mode 100644 core/morph/frontend/template/tsconfig.node.json delete mode 100644 core/morph/frontend/template/vite.config.ts create mode 100644 core/morph/include/starter_template/src/pages/_app.tsx create mode 100644 core/morph/include/starter_template/tsconfig.app.json rename core/morph/{frontend/template => include/starter_template}/tsconfig.json (96%) create mode 100644 core/morph/include/starter_template/tsconfig.node.json create mode 100644 core/morph/include/starter_template/vite.config.ts diff --git a/core/morph/api/app.py b/core/morph/api/app.py index 9ead037..54246d9 100644 --- a/core/morph/api/app.py +++ b/core/morph/api/app.py @@ -21,9 +21,6 @@ inertia_request_validation_exception_handler, inertia_version_conflict_exception_handler, ) -from starlette.middleware.cors import CORSMiddleware -from starlette.middleware.sessions import SessionMiddleware - from morph.api.error import ApiBaseError, InternalError, render_error_html from morph.api.handler import router from morph.api.plugin import plugin_app @@ -32,6 +29,8 @@ MorphFunctionMetaObjectCacheManager, MorphGlobalContext, ) +from starlette.middleware.cors import CORSMiddleware +from starlette.middleware.sessions import SessionMiddleware # configuration values @@ -42,10 +41,11 @@ # set true to MORPH_LOCAL_DEV_MODE to use local frontend server is_local_dev_mode = True if os.getenv("MORPH_LOCAL_DEV_MODE") == "true" else False +project_root = find_project_root_dir() + def custom_compile_logic(): logger.info("Compiling python and sql files...") - project_root = find_project_root_dir() context = MorphGlobalContext.get_instance() errors = context.load(project_root) if len(errors) > 0: @@ -129,8 +129,6 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: inertia_request_validation_exception_handler, ) -frontend_dir = os.path.join(os.getcwd(), ".morph", "frontend") - def get_inertia_config(): templates_dir = os.path.join(Path(__file__).resolve().parent, "templates") @@ -147,16 +145,16 @@ def get_inertia_config(): environment="development", use_flash_messages=True, use_flash_errors=True, - entrypoint_filename="main.tsx", - assets_prefix="/src", + entrypoint_filename=".morph/frontend/main.tsx", + root_directory=".", dev_url=frontend_url, ) return InertiaConfig( templates=Jinja2Templates(directory=templates_dir), - manifest_json_path=os.path.join(frontend_dir, "dist", "manifest.json"), + manifest_json_path=os.path.join(project_root, "dist", "manifest.json"), environment="production", - entrypoint_filename="main.tsx", + entrypoint_filename=".morph/frontend/main.tsx", ) @@ -167,13 +165,13 @@ def get_inertia_config(): if is_local_dev_mode: app.mount( "/src", - StaticFiles(directory=os.path.join(frontend_dir, "src")), + StaticFiles(directory=os.path.join(project_root, "src")), name="src", ) else: app.mount( "/assets", - StaticFiles(directory=os.path.join(frontend_dir, "dist", "assets")), + StaticFiles(directory=os.path.join(project_root, "dist", "assets")), name="assets", ) diff --git a/core/morph/frontend/template/.eslintrc.cjs b/core/morph/frontend/template/.eslintrc.cjs deleted file mode 100644 index d6c9537..0000000 --- a/core/morph/frontend/template/.eslintrc.cjs +++ /dev/null @@ -1,18 +0,0 @@ -module.exports = { - root: true, - env: { browser: true, es2020: true }, - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - 'plugin:react-hooks/recommended', - ], - ignorePatterns: ['dist', '.eslintrc.cjs'], - parser: '@typescript-eslint/parser', - plugins: ['react-refresh'], - rules: { - 'react-refresh/only-export-components': [ - 'warn', - { allowConstantExport: true }, - ], - }, -} diff --git a/core/morph/frontend/template/.gitignore b/core/morph/frontend/template/.gitignore deleted file mode 100644 index 1e47915..0000000 --- a/core/morph/frontend/template/.gitignore +++ /dev/null @@ -1,32 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? - -# development -local-test - -# project files -src/pages/* -!src/pages/.gitkeep -constants.js diff --git a/core/morph/frontend/template/index.html b/core/morph/frontend/template/index.html deleted file mode 100644 index 8467e8f..0000000 --- a/core/morph/frontend/template/index.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - Morph Project - - -
- - - diff --git a/core/morph/frontend/template/package.json b/core/morph/frontend/template/package.json deleted file mode 100644 index cf5af0e..0000000 --- a/core/morph/frontend/template/package.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "name": "morph-frontend", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite --port", - "build": "tsc -b && vite build", - "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0" - }, - "dependencies": { - "@dagrejs/dagre": "^1.1.4", - "@xyflow/react": "^12.4.1", - "tailwind-variants": "^0.3.0", - "react": "^18.3.1", - "react-dom": "^18.3.1" - }, - "devDependencies": { - "@inertiajs/react": "^1.2.0", - "@mdx-js/rollup": "^3.1.0", - "@stefanprobst/rehype-extract-toc": "^2.2.1", - "@types/mdx": "^2.0.13", - "@types/node": "^20.14.11", - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", - "@typescript-eslint/eslint-plugin": "^7.15.0", - "@typescript-eslint/parser": "^7.15.0", - "@morph-data/components": "0.2.0-beta.2", - "@vitejs/plugin-react-swc": "^3.5.0", - "class-variance-authority": "^0.7.1", - "eslint": "^8.57.0", - "eslint-plugin-react-hooks": "^4.6.2", - "eslint-plugin-react-refresh": "^0.4.7", - "react-error-boundary": "^4.1.2", - "rehype-slug": "^6.0.0", - "remark-gfm": "^4.0.0", - "typescript": "^5.2.2", - "vite": "^5.3.4", - "vite-plugin-restart": "^0.4.2" - } -} diff --git a/core/morph/frontend/template/src/admin/AdminPage.tsx b/core/morph/frontend/template/src/admin/AdminPage.tsx deleted file mode 100644 index f42580a..0000000 --- a/core/morph/frontend/template/src/admin/AdminPage.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { DataPipeline } from "./datapipeline/DataPipeline"; - -export const AdminPage = () => { - return ; -}; diff --git a/core/morph/frontend/template/src/admin/common/useResourcesQuery.ts b/core/morph/frontend/template/src/admin/common/useResourcesQuery.ts deleted file mode 100644 index f1581c8..0000000 --- a/core/morph/frontend/template/src/admin/common/useResourcesQuery.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { useQuery } from "./utils/useQuery"; - -type Resource = { - alias: string; - path: string; - connection?: string | null; - output_paths: string[]; - public?: boolean | null; - output_type?: string | null; - data_requirements?: string[] | null; -}; - -type GetResourcesResponse = { - resources: Resource[]; -}; - -const getResources = async () => { - const response = await fetch("/cli/resource"); - - if (!response.ok) { - throw await response.json(); - } - - const data = await response.json(); - - if (data.error) { - throw data.error; - } - - return data as GetResourcesResponse; -}; - -const useResourcesQuery = () => { - return useQuery(getResources); -}; - -export { type Resource, useResourcesQuery }; diff --git a/core/morph/frontend/template/src/admin/common/useScheduledJobsQuery.ts b/core/morph/frontend/template/src/admin/common/useScheduledJobsQuery.ts deleted file mode 100644 index 52eff98..0000000 --- a/core/morph/frontend/template/src/admin/common/useScheduledJobsQuery.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { useQuery } from "./utils/useQuery"; - -type ScheduledJob = { - cron: string; - is_enabled?: boolean; - timezone?: string; - variables?: Record; -}; - -type GetScheduledJobsResponse = Record; - -const getScheduledJobs = async () => { - const response = await fetch("/cli/morph-project/scheduled-jobs"); - - if (!response.ok) { - throw await response.json(); - } - - const data = await response.json(); - - if (data.error) { - throw data.error; - } - - return data as GetScheduledJobsResponse; -}; - -const useScheduledJobsQuery = () => { - return useQuery(getScheduledJobs); -}; - -export { type ScheduledJob, useScheduledJobsQuery }; diff --git a/core/morph/frontend/template/src/admin/common/utils/useQuery.ts b/core/morph/frontend/template/src/admin/common/utils/useQuery.ts deleted file mode 100644 index c2695cb..0000000 --- a/core/morph/frontend/template/src/admin/common/utils/useQuery.ts +++ /dev/null @@ -1,36 +0,0 @@ -import React from "react"; - -type QueryResult = - | { - status: "loading"; - } - | { - status: "success"; - data: T; - } - | { - status: "error"; - error: unknown; - }; - -export const useQuery = (fetcher: () => T): QueryResult> => { - const [result, setResult] = React.useState>>({ - status: "loading", - }); - - React.useEffect(() => { - const init = async () => { - try { - const data = await fetcher(); - - setResult({ status: "success", data }); - } catch (error) { - setResult({ status: "error", error }); - } - }; - - init(); - }, [fetcher]); - - return result; -}; diff --git a/core/morph/frontend/template/src/admin/datapipeline/DataPipeline.tsx b/core/morph/frontend/template/src/admin/datapipeline/DataPipeline.tsx deleted file mode 100644 index abc84a3..0000000 --- a/core/morph/frontend/template/src/admin/datapipeline/DataPipeline.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { ReactFlowProvider } from "@xyflow/react"; -import { useResourcesQuery } from "../common/useResourcesQuery"; - -import "@xyflow/react/dist/style.css"; -import { Flow } from "./Flow"; -import { ResourceDetail } from "./ResourceDetail"; - -export const DataPipeline = () => { - const resources = useResourcesQuery(); - - if (resources.status === "loading") { - return null; - } - - if (resources.status === "error") { - throw resources.error; - } - - return ( - - {/* TODO: Refactor height calculation, avoid hardcoded values */} -
-
- -
-
- -
-
-
- ); -}; diff --git a/core/morph/frontend/template/src/admin/datapipeline/Flow.tsx b/core/morph/frontend/template/src/admin/datapipeline/Flow.tsx deleted file mode 100644 index 6601869..0000000 --- a/core/morph/frontend/template/src/admin/datapipeline/Flow.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import { ReactFlow, Background, Node, Edge } from "@xyflow/react"; -import dagre from "@dagrejs/dagre"; -import { Resource } from "../common/useResourcesQuery"; - -import "@xyflow/react/dist/style.css"; -import { ResourceNode } from "./ResourceNode"; - -const nodeTypes = { - resource: ResourceNode, -}; - -export const Flow = ({ resources }: { resources: Resource[] }) => { - const nodes = convertResourcesToNodes(resources); - const edges = convertResourcesToEdges(resources); - - return ( - - - - ); -}; - -const convertResourcesToNodes = (resources: Resource[]): Node[] => { - const WIDTH = 200; - const HEIGHT = 40; - - const graph = new dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({})); - - graph.setGraph({ rankdir: "TB", ranksep: HEIGHT, nodesep: WIDTH / 2 }); - - resources.forEach((resource) => - graph.setNode(resource.alias, { - width: WIDTH, - height: HEIGHT, - }) - ); - - resources.forEach((resource) => { - resource.data_requirements?.forEach((parentAlias) => { - graph.setEdge(parentAlias, resource.alias); - }); - }); - - dagre.layout(graph); - - const nodes: ResourceNode[] = resources.map((resource) => { - return { - id: resource.alias, - type: "resource", - position: { - x: graph.node(resource.alias).x, - y: graph.node(resource.alias).y, - }, - width: WIDTH, - height: HEIGHT, - connectable: false, - draggable: false, - handles: [], - data: { resource }, - }; - }); - - return nodes; -}; - -const convertResourcesToEdges = (resources: Resource[]): Edge[] => { - return resources.reduce((edges, resource) => { - const addedEdges = - resource.data_requirements?.map((parentAlias) => ({ - id: `${parentAlias}-${resource.alias}`, - source: parentAlias, - target: resource.alias, - })) ?? []; - - return [...edges, ...addedEdges]; - }, [] as Edge[]); -}; diff --git a/core/morph/frontend/template/src/admin/datapipeline/ResourceDetail.tsx b/core/morph/frontend/template/src/admin/datapipeline/ResourceDetail.tsx deleted file mode 100644 index 6ec9077..0000000 --- a/core/morph/frontend/template/src/admin/datapipeline/ResourceDetail.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import { Node, useOnSelectionChange } from "@xyflow/react"; -import React from "react"; -import { useScheduledJobsQuery } from "../common/useScheduledJobsQuery"; -import { Resource } from "../common/useResourcesQuery"; - -export const ResourceDetail = ({ resources }: { resources: Resource[] }) => { - const [selectedAlias, setSelectedAlias] = React.useState(null); - - const onChange = React.useCallback(({ nodes }: { nodes: Node[] }) => { - if (nodes.length === 0) { - setSelectedAlias(null); - } else { - setSelectedAlias(nodes[0].id); - } - }, []); - - useOnSelectionChange({ - onChange, - }); - - if (!selectedAlias) { - return ( -
-

Select cell to view details

-
- ); - } - - return ( -
-

{selectedAlias}

- - {/* */} -
- ); -}; - -const BasicInfo = ({ - alias, - resources, -}: { - alias: string; - resources: Resource[]; -}) => { - const resource = resources.find((r) => r.alias === alias); - - if (!resource) { - return

Something went wrong

; - } - - return ( -
-

- Defined at {resource.path} -

-
- ); -}; - -const ScheduledJobs = ({ alias }: { alias: string }) => { - const scheduledJobs = useScheduledJobsQuery(); - - if (scheduledJobs.status === "loading") { - return null; - } - - if (scheduledJobs.status === "error") { - return

Something went wrong

; - } - - const scheduledJobsForAlias = scheduledJobs.data[alias]?.schedules ?? []; - - return ( -
-

Scheduled Jobs

- {scheduledJobsForAlias.length === 0 && ( -

No scheduled jobs

- )} - {scheduledJobsForAlias.map((job, i) => ( -
-

{`${job.cron} (${ - job.timezone ?? "UTC" - })`}

- - {job.variables ? ( -
- - Variables - - - - - - - - - - {Object.entries(job.variables).map(([key, value]) => ( - - - - - ))} - -
KeyValue
{key}{String(value)}
-
- ) : ( - - No variables - - )} - - {i < scheduledJobsForAlias.length - 1 && ( -
- )} -
- ))} -
- ); -}; diff --git a/core/morph/frontend/template/src/admin/datapipeline/ResourceNode.tsx b/core/morph/frontend/template/src/admin/datapipeline/ResourceNode.tsx deleted file mode 100644 index 048d6d3..0000000 --- a/core/morph/frontend/template/src/admin/datapipeline/ResourceNode.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { tv } from "tailwind-variants"; -import { Resource } from "../common/useResourcesQuery"; -import { - Handle, - Node, - NodeProps, - Position, - useOnSelectionChange, -} from "@xyflow/react"; -import React from "react"; -import databaseIcon from "../../assets/icons/database.svg"; -import pythonIcon from "../../assets/icons/python.svg"; - -export type ResourceNode = Node< - { - resource: Resource; - }, - "resource" ->; - -const useSelected = (id: string) => { - const [selected, setSelected] = React.useState(false); - - useOnSelectionChange({ - onChange: React.useCallback(({ nodes }) => { - setSelected(nodes.some((node) => node.id === id)); - }, []), - }); - - return selected; -}; - -const ResourceNode = ({ id, data }: NodeProps) => { - const { resource } = data; - - const selected = useSelected(id); - - return ( -
- - {resource.alias} - - -
- ); -}; - -const Icon = ({ resource }: { resource: Resource }) => { - if (resource.path.endsWith(".py")) { - return ; - } - - if (resource.path.endsWith(".sql")) { - return ; - } - - return null; -}; - -const card = tv({ - base: "bg-gray-200 p-2 rounded-md flex align-center justify-center gap-2", - variants: { - selected: { - true: "outline outline-gray-400", - }, - }, -}); - -export { ResourceNode }; diff --git a/core/morph/frontend/template/src/assets/icons/database.svg b/core/morph/frontend/template/src/assets/icons/database.svg deleted file mode 100644 index 0458bd5..0000000 --- a/core/morph/frontend/template/src/assets/icons/database.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/core/morph/frontend/template/src/assets/icons/python.svg b/core/morph/frontend/template/src/assets/icons/python.svg deleted file mode 100644 index e0e096a..0000000 --- a/core/morph/frontend/template/src/assets/icons/python.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/core/morph/frontend/template/src/error-page.tsx b/core/morph/frontend/template/src/error-page.tsx deleted file mode 100644 index 170d807..0000000 --- a/core/morph/frontend/template/src/error-page.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import React from "react"; - -type ErrorPageProps = React.PropsWithChildren<{ - routes: Array<{ path: string; title: string }>; -}>; - -export const ErrorPage: React.FC = () => { - return ( -
-

Page Not Found

-

Oops! The page you're looking for doesn't exist or has been moved.

-

Please select an exsiting page from below.

-
- {/* {props.routes.map((route) => ( - - - - ))} */} -
-

Trouble shoot

-
- Newly added files are not displayed. -
-

You need to re-start mdx dev server.

-
    -
  1. 1. Close preview window on code editor
  2. -
  3. - 2. Stop process with putting{" "} - - Ctrl + C - {" "} - to the terminal -
  4. -
  5. - 3. Open preview window on code editor again -
  6. -
-
-
-
- ); -}; - -export default ErrorPage; diff --git a/core/morph/frontend/template/src/main.tsx b/core/morph/frontend/template/src/main.tsx deleted file mode 100644 index 1eb916c..0000000 --- a/core/morph/frontend/template/src/main.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import "vite/modulepreload-polyfill"; -import { createRoot } from "react-dom/client"; -import { createInertiaApp, Head } from "@inertiajs/react"; -import React, { StrictMode, useMemo } from "react"; -import { PageSkeleton } from "./page-skeleton.tsx"; -import "@morph-data/components/css"; -import { MDXComponents } from "mdx/types"; -import type { Toc } from "@stefanprobst/rehype-extract-toc"; -import { AdminPage } from "./admin/AdminPage.tsx"; -import { ErrorPage } from "./error-page.tsx"; -import { useRefresh } from "@morph-data/components"; -import { mdxComponents } from "./mdx-componens.tsx"; - -type MDXProps = { - children?: React.ReactNode; - components?: MDXComponents; -}; - -export type MDXComponent = (props: MDXProps) => JSX.Element; - -type PageModule = { - default: MDXComponent; - title?: string; - tableOfContents?: Toc; -}; // types MDX default export -type Pages = Record; - -const pages: Pages = import.meta.glob( - "/../../src/pages/**/*.mdx", - { - eager: true, - } -); - -const normalizePath = (filePath: string) => { - // const relativePath = filePath.replace(/\.mdx$/, "").replace(/^\.\/pages/, ""); - const relativePath = filePath - .replace(/\.mdx$/, "") - .replace(/^\.\.\/\.\.\/src\/pages/, ""); - - return relativePath === "/index" ? "/" : relativePath; -}; - -const routes = Object.entries(pages).map(([filePath, module]) => { - // Extract the exported title from the MDX file - const title = (() => { - if (module.title) { - return String(module.title); - } - - if (module.tableOfContents && module.tableOfContents.length > 0) { - const firstHeading = module.tableOfContents[0]; - if (firstHeading) { - return firstHeading.value; - } - } - - return "Untitled"; - })(); - - return { - path: normalizePath(filePath), - title, - toc: module.tableOfContents, - }; -}); - -document.addEventListener("DOMContentLoaded", () => { - createInertiaApp<{ showAdminPage: boolean }>({ - resolve: (name) => { - const pageModule = pages[`../../src/pages/${name}.mdx`]; - - const WrappedComponent: React.FC<{ showAdminPage: boolean }> = ({ - showAdminPage, - }) => { - useRefresh(); - - const firstHeading = useMemo(() => { - if ( - pageModule?.tableOfContents && - pageModule.tableOfContents.length > 0 - ) { - return pageModule.tableOfContents[0]; - } - return null; - }, []); - - const title = pageModule?.title || firstHeading?.value || "Untitled"; - - return ( - <> - - - - - {name === "morph" ? ( - - ) : pageModule ? ( - - ) : ( - - )} - - - ); - }; - - return WrappedComponent; - }, - setup({ el, App, props }) { - createRoot(el).render( - - - - ); - }, - }).then(() => {}); -}); diff --git a/core/morph/frontend/template/src/mdx-componens.tsx b/core/morph/frontend/template/src/mdx-componens.tsx deleted file mode 100644 index ad89a77..0000000 --- a/core/morph/frontend/template/src/mdx-componens.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import { - Accordion, - Callout, - Card, - DataTable, - Embed, - Grid, - LLM, - Chat, - Metrics, - MetricsGrid, - Panel, - Input, - Select, - SelectGroup, - SelectGroupLabel, - SelectItem, - SelectSeparator, - SelectItems, - DatePicker, - DateRangePicker, - Pre, -} from "@morph-data/components"; -import { MDXComponents } from "mdx/types"; - -const builtinComponents = { - DataTable, - Embed, - Metrics, - MetricsGrid, - Input, - Select, - SelectGroup, - SelectGroupLabel, - SelectItem, - SelectSeparator, - SelectItems, - Card, - Grid, - Panel, - Accordion, - Callout, - LLM, - Chat, - DatePicker, - DateRangePicker, - pre: Pre, -} as const; - -type PluginModule = { - components?: Record; -}; - -type Plugins = { - [pluginName: string]: PluginModule; -}; - -const plugins: Plugins = Object.entries( - import.meta.glob( - "/../../src/plugin/**/react/index.ts", - { - eager: true, - } - ) -).reduce((acc, [reactEntryPath, module]) => { - // /path/to/plugin-name/react/index.ts -> plugin-name - const pluginName = reactEntryPath.match(/plugin\/(.+?)\//)?.[1] ?? ""; - return { - ...acc, - [pluginName]: module, - }; -}, {} as Plugins); - -const pluginsComponents = Object.entries(plugins).reduce( - (mdxComponents, [pluginName, module]) => { - if (!module.components) { - return mdxComponents; - } - - return Object.entries(module.components).reduce( - (mdxComponents, [componentName, component]) => { - const isComponentNameConflict = - Object.keys(mdxComponents).includes(componentName); - - if (isComponentNameConflict) { - console.warn( - `Component name conflict: ${componentName} in plugin ${pluginName}` - ); - } - - return { - ...mdxComponents, - [componentName]: component, - }; - }, - mdxComponents - ); - }, - {} as Record -); - -export const mdxComponents: MDXComponents = { - ...builtinComponents, - ...pluginsComponents, -}; diff --git a/core/morph/frontend/template/src/mdx.d.ts b/core/morph/frontend/template/src/mdx.d.ts deleted file mode 100644 index 838b4be..0000000 --- a/core/morph/frontend/template/src/mdx.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -declare module "*.mdx" { - let MDXComponent: (props) => JSX.Element; - export default MDXComponent; -} diff --git a/core/morph/frontend/template/src/page-skeleton.tsx b/core/morph/frontend/template/src/page-skeleton.tsx deleted file mode 100644 index f7668e3..0000000 --- a/core/morph/frontend/template/src/page-skeleton.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import React from "react"; -import { - DropdownMenu, - DropdownMenuTrigger, - DropdownMenuContent, - DropdownMenuItem, - Button, - DropdownMenuSeparator, - Toc, -} from "@morph-data/components"; -import { ErrorBoundary, FallbackProps } from "react-error-boundary"; -import { Link } from "@inertiajs/react"; -import { Toc as TocEntries } from "@stefanprobst/rehype-extract-toc"; - -function fallbackRender({ error }: FallbackProps) { - // Call resetErrorBoundary() to reset the error boundary and retry the render. - return ( -
-

Something went wrong:

-
{error.message}
-
- ); -} - -type PageSkeletonProps = React.PropsWithChildren<{ - routes: Array<{ path: string; title: string }>; - title: string; - showAdminPage: boolean; - toc?: TocEntries; -}>; - -export const PageSkeleton: React.FC = (props) => { - return ( - -
-
- - - - - - {props.routes.map((route) => ( - - - - {route.title} - - - - ))} - {props.showAdminPage && ( - <> - - - Admin Page - - - )} - - -
{props.title}
-
-
- Made with - - Morph - -
-
-
-
-
{props.children}
-
- -
-
-
-
-
- ); -}; diff --git a/core/morph/frontend/template/src/vite-env.d.ts b/core/morph/frontend/template/src/vite-env.d.ts deleted file mode 100644 index 11f02fe..0000000 --- a/core/morph/frontend/template/src/vite-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/core/morph/frontend/template/tsconfig.app.json b/core/morph/frontend/template/tsconfig.app.json deleted file mode 100644 index 0c89611..0000000 --- a/core/morph/frontend/template/tsconfig.app.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "compilerOptions": { - "composite": true, - "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", - "target": "ES2020", - "useDefineForClassFields": true, - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "module": "ESNext", - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "isolatedModules": true, - "moduleDetection": "force", - "noEmit": true, - "jsx": "react-jsx", - - /* Linting */ - "strict": true - }, - "include": ["src"] -} diff --git a/core/morph/frontend/template/tsconfig.node.json b/core/morph/frontend/template/tsconfig.node.json deleted file mode 100644 index 3afdd6e..0000000 --- a/core/morph/frontend/template/tsconfig.node.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "compilerOptions": { - "composite": true, - "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", - "skipLibCheck": true, - "module": "ESNext", - "moduleResolution": "bundler", - "allowSyntheticDefaultImports": true, - "strict": true, - "noEmit": true - }, - "include": ["vite.config.ts"] -} diff --git a/core/morph/frontend/template/vite.config.ts b/core/morph/frontend/template/vite.config.ts deleted file mode 100644 index 52f6140..0000000 --- a/core/morph/frontend/template/vite.config.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { defineConfig } from "vite"; -import react from "@vitejs/plugin-react-swc"; -import { resolve } from "path"; -import ViteRestart from "vite-plugin-restart"; -import mdx from "@mdx-js/rollup"; -import remarkGfm from "remark-gfm"; -import rehypeExtractToc from "@stefanprobst/rehype-extract-toc"; -import rehypeExtractTocMdxExport from "@stefanprobst/rehype-extract-toc/mdx"; -import rehypeSlug from "rehype-slug"; - -// https://vitejs.dev/config/ -export default defineConfig((env) => ({ - plugins: [ - react(), - { - enforce: "pre", - ...mdx({ - remarkPlugins: [remarkGfm], - rehypePlugins: [ - rehypeSlug, - rehypeExtractToc, - rehypeExtractTocMdxExport, - ], - }), - }, - ViteRestart({ - restart: ["../../src/pages/**/*"], - }), - ], - base: env.mode === "development" ? "" : "/_vite-static", - server: { - host: "0.0.0.0", - open: false, - watch: { - usePolling: true, - disableGlobbing: false, - }, - cors: true, - }, - resolve: { - alias: { - "@": resolve(__dirname, "./src"), - }, - }, - build: { - outDir: resolve("./dist"), - assetsDir: "assets", - target: "es2015", - manifest: "manifest.json", - rollupOptions: { - input: { - main: resolve("./src/main.tsx"), - }, - output: { - entryFileNames: `assets/bundle.js`, - }, - }, - }, -})); diff --git a/core/morph/include/starter_template/package.json b/core/morph/include/starter_template/package.json index 52ebc56..85e98fb 100644 --- a/core/morph/include/starter_template/package.json +++ b/core/morph/include/starter_template/package.json @@ -4,11 +4,19 @@ "version": "0.0.0", "type": "module", "scripts": { - "build": "npm run build -w .morph/frontend" + "dev": "morph-frontend dev --port", + "build": "morph-frontend build" }, - "dependencies": {}, - "devDependencies": {}, - "workspaces": [ - ".morph/frontend" - ] + "devDependencies": { + "@types/react": "^18.3.18", + "@types/react-dom": "^18.3.5", + "vite": "^6.2.1" + }, + "dependencies": { + "@morph-data/components": "^0.1.9", + "@morph-data/frontend": "0.3.0.beta-0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-error-boundary": "^5.0.0" + } } diff --git a/core/morph/include/starter_template/src/pages/_app.tsx b/core/morph/include/starter_template/src/pages/_app.tsx new file mode 100644 index 0000000..ba534c0 --- /dev/null +++ b/core/morph/include/starter_template/src/pages/_app.tsx @@ -0,0 +1,43 @@ +import { Head, Toc } from "@morph-data/components"; +import { RootErrorBoundary, Header } from "../lib"; +import { + usePageMeta, + MdxComponentsProvider, + Outlet, +} from "@morph-data/frontend/components"; + +export default function App() { + const pageMeta = usePageMeta(); + + return ( + + + {pageMeta?.title} + + + +
+ + + {pageMeta && } + + + +
+
+
+ +
+
+ +
+
+
+
+
+
+ ); +} diff --git a/core/morph/include/starter_template/tsconfig.app.json b/core/morph/include/starter_template/tsconfig.app.json new file mode 100644 index 0000000..8c62397 --- /dev/null +++ b/core/morph/include/starter_template/tsconfig.app.json @@ -0,0 +1,7 @@ +{ + "extends": "@morph-data/frontend/tsconfig.app.json", + "compilerOptions": { + "composite": true + }, + "include": ["src"] +} diff --git a/core/morph/frontend/template/tsconfig.json b/core/morph/include/starter_template/tsconfig.json similarity index 96% rename from core/morph/frontend/template/tsconfig.json rename to core/morph/include/starter_template/tsconfig.json index 2cb35ca..ea9d0cd 100644 --- a/core/morph/frontend/template/tsconfig.json +++ b/core/morph/include/starter_template/tsconfig.json @@ -7,5 +7,5 @@ { "path": "./tsconfig.node.json" } - ], + ] } diff --git a/core/morph/include/starter_template/tsconfig.node.json b/core/morph/include/starter_template/tsconfig.node.json new file mode 100644 index 0000000..ce779b4 --- /dev/null +++ b/core/morph/include/starter_template/tsconfig.node.json @@ -0,0 +1,7 @@ +{ + "extends": "@morph-data/frontend/tsconfig.node.json", + "compilerOptions": { + "composite": true + }, + "include": ["vite.config.ts"] +} diff --git a/core/morph/include/starter_template/vite.config.ts b/core/morph/include/starter_template/vite.config.ts new file mode 100644 index 0000000..4c7a434 --- /dev/null +++ b/core/morph/include/starter_template/vite.config.ts @@ -0,0 +1,6 @@ +import { morph } from "@morph-data/frontend/plugin"; +import { defineConfig } from "vite"; + +export default defineConfig({ + plugins: [morph()], +}); diff --git a/core/morph/task/api.py b/core/morph/task/api.py index 13d7eae..0973d74 100644 --- a/core/morph/task/api.py +++ b/core/morph/task/api.py @@ -9,10 +9,9 @@ import click from dotenv import dotenv_values, load_dotenv - from morph.cli.flags import Flags from morph.task.base import BaseTask -from morph.task.utils.morph import find_project_root_dir, initialize_frontend_dir +from morph.task.utils.morph import find_project_root_dir class ApiTask(BaseTask): @@ -43,15 +42,10 @@ def __init__(self, args: Flags): for e_key, e_val in env_vars.items(): os.environ[e_key] = str(e_val) - # Initialize the frontend directory - # Copy the frontend template to ~/.morph/frontend if it doesn't exist - self.frontend_dir = initialize_frontend_dir(self.project_root) - # for managing subprocesses self.processes: List[subprocess.Popen[str]] = [] def _find_available_port(self, start_port: int, max_port: int = 65535) -> int: - port = start_port while port <= max_port: @@ -119,7 +113,7 @@ def _run_frontend(self) -> None: try: subprocess.run( "npm install", - cwd=self.frontend_dir, + cwd=self.project_root, shell=True, check=True, ) @@ -130,8 +124,8 @@ def _run_frontend(self) -> None: exit(1) self._run_process( - ["npm", "run", "dev", "--port", f"{self.front_port}"], - cwd=self.frontend_dir, + ["npm", "run", "dev", "--", "--port", f"{self.front_port}"], + cwd=self.project_root, is_debug=False, ) diff --git a/core/morph/task/deploy.py b/core/morph/task/deploy.py index f2d81f6..564b0bb 100644 --- a/core/morph/task/deploy.py +++ b/core/morph/task/deploy.py @@ -8,15 +8,14 @@ import click import requests -from tqdm import tqdm - from morph.api.cloud.client import MorphApiKeyClientImpl from morph.api.cloud.types import EnvVarObject from morph.cli.flags import Flags from morph.config.project import load_project from morph.task.base import BaseTask from morph.task.utils.file_upload import FileWithProgress -from morph.task.utils.morph import find_project_root_dir, initialize_frontend_dir +from morph.task.utils.morph import find_project_root_dir +from tqdm import tqdm class DeployTask(BaseTask): @@ -83,9 +82,6 @@ def __init__(self, args: Flags): click.echo(click.style(f"Error: {str(e)}", fg="red")) sys.exit(1) - # Frontend and backend settings - self.frontend_dir = initialize_frontend_dir(self.project_root) - # Docker settings self.image_name = f"{os.path.basename(self.project_root)}:latest" self.output_tar = os.path.join( diff --git a/core/morph/task/new.py b/core/morph/task/new.py index 88f6bff..d3bf63a 100644 --- a/core/morph/task/new.py +++ b/core/morph/task/new.py @@ -8,12 +8,10 @@ from typing import Optional import click - from morph.cli.flags import Flags from morph.config.project import default_initial_project, load_project, save_project from morph.constants import MorphConstant from morph.task.base import BaseTask -from morph.task.utils.morph import initialize_frontend_dir from morph.task.utils.run_backend.state import MorphGlobalContext @@ -33,10 +31,6 @@ def __init__(self, args: Flags, project_directory: Optional[str]): os.makedirs(morph_dir) click.echo(f"Created directory at {morph_dir}") - # Initialize the frontend directory - # Copy the frontend template to ~/.morph/frontend if it doesn't exist - initialize_frontend_dir(self.project_root) - # Select the Python version for the project self.selected_python_version = self._select_python_version() diff --git a/core/morph/task/utils/morph.py b/core/morph/task/utils/morph.py index a8cf637..c5f573c 100644 --- a/core/morph/task/utils/morph.py +++ b/core/morph/task/utils/morph.py @@ -2,13 +2,11 @@ import logging import os import re -import shutil from pathlib import Path from typing import List, Optional, Union -from pydantic import BaseModel - from morph.constants import MorphConstant +from pydantic import BaseModel IGNORE_DIRS = ["/private/tmp", "/tmp"] @@ -41,23 +39,6 @@ def find_project_root_dir(abs_filepath: Optional[str] = None) -> str: ) -def initialize_frontend_dir(project_root: str) -> str: - """ - Initialize the frontend directory by copying the template frontend directory to the project directory. - Does nothing if the frontend directory already exists. - @param project_root: - @return: - """ - frontend_template_dir = os.path.join( - Path(__file__).resolve().parents[2], "frontend", "template" - ) - frontend_dir = MorphConstant.frontend_dir(project_root) - if not os.path.exists(frontend_dir): - os.makedirs(frontend_dir, exist_ok=True) - shutil.copytree(frontend_template_dir, frontend_dir, dirs_exist_ok=True) - return frontend_dir - - class Resource(BaseModel): alias: str path: str From aa1d44fddf754cf470fa6dc0237669c85a2eca48 Mon Sep 17 00:00:00 2001 From: Atsuki Hasegawa <58581243+atsuki44@users.noreply.github.com> Date: Fri, 7 Mar 2025 20:24:01 +0900 Subject: [PATCH 02/39] fix dev command in starter package.json --- core/morph/include/starter_template/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/morph/include/starter_template/package.json b/core/morph/include/starter_template/package.json index 85e98fb..f138a1c 100644 --- a/core/morph/include/starter_template/package.json +++ b/core/morph/include/starter_template/package.json @@ -4,7 +4,7 @@ "version": "0.0.0", "type": "module", "scripts": { - "dev": "morph-frontend dev --port", + "dev": "morph-frontend dev", "build": "morph-frontend build" }, "devDependencies": { From 6ec603c8caae46a806539823bb76714dec855f65 Mon Sep 17 00:00:00 2001 From: Atsuki Hasegawa <58581243+atsuki44@users.noreply.github.com> Date: Fri, 7 Mar 2025 20:25:46 +0900 Subject: [PATCH 03/39] fix package version --- core/morph/include/starter_template/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/morph/include/starter_template/package.json b/core/morph/include/starter_template/package.json index f138a1c..7e065d3 100644 --- a/core/morph/include/starter_template/package.json +++ b/core/morph/include/starter_template/package.json @@ -14,7 +14,7 @@ }, "dependencies": { "@morph-data/components": "^0.1.9", - "@morph-data/frontend": "0.3.0.beta-0", + "@morph-data/frontend": "0.3.0-beta.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-error-boundary": "^5.0.0" From 8ba16a6f1774d5957bbba44585c24c384cc883a9 Mon Sep 17 00:00:00 2001 From: Atsuki Hasegawa <58581243+atsuki44@users.noreply.github.com> Date: Tue, 11 Mar 2025 15:28:01 +0900 Subject: [PATCH 04/39] fix: root_directory for production --- core/morph/api/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/core/morph/api/app.py b/core/morph/api/app.py index 54246d9..ef31888 100644 --- a/core/morph/api/app.py +++ b/core/morph/api/app.py @@ -154,6 +154,7 @@ def get_inertia_config(): templates=Jinja2Templates(directory=templates_dir), manifest_json_path=os.path.join(project_root, "dist", "manifest.json"), environment="production", + root_directory=".", entrypoint_filename=".morph/frontend/main.tsx", ) From 1096da4abfc2c3035ba33debeb9cd0b65e71d200 Mon Sep 17 00:00:00 2001 From: Atsuki Hasegawa <58581243+atsuki44@users.noreply.github.com> Date: Tue, 11 Mar 2025 16:14:24 +0900 Subject: [PATCH 05/39] fix: root_directory --- core/morph/api/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/morph/api/app.py b/core/morph/api/app.py index ef31888..c084e13 100644 --- a/core/morph/api/app.py +++ b/core/morph/api/app.py @@ -146,7 +146,7 @@ def get_inertia_config(): use_flash_messages=True, use_flash_errors=True, entrypoint_filename=".morph/frontend/main.tsx", - root_directory=".", + root_directory="", dev_url=frontend_url, ) @@ -154,7 +154,7 @@ def get_inertia_config(): templates=Jinja2Templates(directory=templates_dir), manifest_json_path=os.path.join(project_root, "dist", "manifest.json"), environment="production", - root_directory=".", + root_directory="", entrypoint_filename=".morph/frontend/main.tsx", ) From ad108bc3e5319f258bb0e481389413b4231856f4 Mon Sep 17 00:00:00 2001 From: Atsuki Hasegawa <58581243+atsuki44@users.noreply.github.com> Date: Tue, 11 Mar 2025 16:31:58 +0900 Subject: [PATCH 06/39] fix: root_directory and entrypoint_filename --- core/morph/api/app.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/morph/api/app.py b/core/morph/api/app.py index c084e13..9f4fe99 100644 --- a/core/morph/api/app.py +++ b/core/morph/api/app.py @@ -145,8 +145,8 @@ def get_inertia_config(): environment="development", use_flash_messages=True, use_flash_errors=True, - entrypoint_filename=".morph/frontend/main.tsx", - root_directory="", + entrypoint_filename="main.tsx", + root_directory=".morph/frontend", dev_url=frontend_url, ) @@ -154,8 +154,8 @@ def get_inertia_config(): templates=Jinja2Templates(directory=templates_dir), manifest_json_path=os.path.join(project_root, "dist", "manifest.json"), environment="production", - root_directory="", - entrypoint_filename=".morph/frontend/main.tsx", + entrypoint_filename="main.tsx", + root_directory=".morph/frontend", ) From deee2d4b69a7d9d7783823b37507e7ab74a38cc7 Mon Sep 17 00:00:00 2001 From: tslcls Date: Wed, 12 Mar 2025 18:41:47 +0900 Subject: [PATCH 07/39] enhancement: deploy config to project yml --- core/morph/api/cloud/client.py | 10 ++++++++-- core/morph/config/project.py | 19 ++++++++++++++++++- core/morph/task/deploy.py | 9 +++++---- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/core/morph/api/cloud/client.py b/core/morph/api/cloud/client.py index 8d60a34..d0d197c 100644 --- a/core/morph/api/cloud/client.py +++ b/core/morph/api/cloud/client.py @@ -132,14 +132,20 @@ def verify_api_secret(self) -> MorphClientResponse: @validate_project_id def initiate_deployment( - self, project_id: str, image_build_log: str, image_checksum: str + self, + project_id: str, + image_build_log: str, + image_checksum: str, + config: Optional[dict[str, Any]] = None, ) -> MorphClientResponse: path = "deployment" - body = { + body: dict[str, Any] = { "projectId": project_id, "imageBuildLog": image_build_log, "imageChecksum": image_checksum, } + if config: + body["config"] = config return self.request(method="POST", path=path, data=body) diff --git a/core/morph/config/project.py b/core/morph/config/project.py index 9c42bc5..026fbac 100644 --- a/core/morph/config/project.py +++ b/core/morph/config/project.py @@ -1,5 +1,5 @@ import os -from typing import List, Optional +from typing import Dict, List, Optional import yaml from pydantic import BaseModel, Field @@ -13,6 +13,21 @@ from morph.task.utils.morph import find_project_root_dir +class BuildConfig(BaseModel): + use_custom_docker: bool = False + runtime: Optional[str] = None + framework: Optional[str] = "morph" + package_manager: Optional[str] = None + context: Optional[str] = None + build_args: Optional[Dict[str, str]] = None + + +class DeploymentConfig(BaseModel): + provider: Optional[str] = "aws" + aws: Optional[Dict[str, Optional[str]]] = None + gcp: Optional[Dict[str, Optional[str]]] = None + + class MorphProject(BaseModel): profile: Optional[str] = "default" source_paths: List[str] = Field(default_factory=lambda: ["src"]) @@ -21,6 +36,8 @@ class MorphProject(BaseModel): package_manager: str = Field( default="pip", description="Package manager to use, e.g., pip or poetry." ) + build: Optional[BuildConfig] = Field(default_factory=BuildConfig) + deployment: Optional[DeploymentConfig] = Field(default_factory=DeploymentConfig) class Config: arbitrary_types_allowed = True diff --git a/core/morph/task/deploy.py b/core/morph/task/deploy.py index f2d81f6..49c48d5 100644 --- a/core/morph/task/deploy.py +++ b/core/morph/task/deploy.py @@ -34,11 +34,11 @@ def __init__(self, args: Flags): sys.exit(1) # Load morph_project.yml or equivalent - project = load_project(self.project_root) - if not project: + self.project = load_project(self.project_root) + if not self.project: click.echo(click.style("Project configuration not found.", fg="red")) sys.exit(1) - elif project.project_id is None: + elif self.project.project_id is None: click.echo( click.style( "Error: No project id found. Please fill project_id in morph_project.yml.", @@ -46,7 +46,7 @@ def __init__(self, args: Flags): ) ) sys.exit(1) - self.package_manager = project.package_manager + self.package_manager = self.project.package_manager # Check Dockerfile existence self.dockerfile = os.path.join(self.project_root, "Dockerfile") @@ -129,6 +129,7 @@ def run(self): project_id=self.client.project_id, image_build_log=image_build_log, image_checksum=image_checksum, + config=self.project.model_dump() if self.project else None, ) except Exception as e: click.echo( From 06aba8265cefc8aeef25c839e28232ffe37f2bd3 Mon Sep 17 00:00:00 2001 From: Atsuki Hasegawa <58581243+atsuki44@users.noreply.github.com> Date: Thu, 13 Mar 2025 16:43:13 +0900 Subject: [PATCH 08/39] fix: remove tailwind import from cdn --- core/morph/api/templates/index.html | 7 ------- 1 file changed, 7 deletions(-) diff --git a/core/morph/api/templates/index.html b/core/morph/api/templates/index.html index 9132d42..8265186 100644 --- a/core/morph/api/templates/index.html +++ b/core/morph/api/templates/index.html @@ -16,13 +16,6 @@ window.__vite_plugin_react_preamble_installed__ = true; {% endif %} - - - {% inertia_body %} From 269d1ea029aad4b6b4392042a25f562f7454b0f6 Mon Sep 17 00:00:00 2001 From: tslcls Date: Thu, 13 Mar 2025 18:32:19 +0900 Subject: [PATCH 09/39] fix: rename project yml parameter --- core/morph/config/project.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/morph/config/project.py b/core/morph/config/project.py index 026fbac..f977565 100644 --- a/core/morph/config/project.py +++ b/core/morph/config/project.py @@ -14,7 +14,7 @@ class BuildConfig(BaseModel): - use_custom_docker: bool = False + use_custom_dockerfile: bool = False runtime: Optional[str] = None framework: Optional[str] = "morph" package_manager: Optional[str] = None From 2991d0ba71330758d27d73b5bb3f3070a1d62347 Mon Sep 17 00:00:00 2001 From: tslcls Date: Thu, 13 Mar 2025 18:40:03 +0900 Subject: [PATCH 10/39] fix: project yml initial values --- core/morph/task/new.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/core/morph/task/new.py b/core/morph/task/new.py index 88f6bff..e4de9ea 100644 --- a/core/morph/task/new.py +++ b/core/morph/task/new.py @@ -10,7 +10,12 @@ import click from morph.cli.flags import Flags -from morph.config.project import default_initial_project, load_project, save_project +from morph.config.project import ( + BuildConfig, + default_initial_project, + load_project, + save_project, +) from morph.constants import MorphConstant from morph.task.base import BaseTask from morph.task.utils.morph import initialize_frontend_dir @@ -111,6 +116,11 @@ def run(self): ) project.package_manager = "poetry" + if project.build is None: + project.build = BuildConfig() + project.build.package_manager = project.package_manager + project.build.runtime = self.selected_python_version + save_project(self.project_root, project) # Generate the Dockerfile template From ab47cb85f033c2ffaf745300f09cbba3082d7234 Mon Sep 17 00:00:00 2001 From: tslcls Date: Fri, 14 Mar 2025 10:24:12 +0900 Subject: [PATCH 11/39] fix: yml init at morph new --- core/morph/task/new.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/morph/task/new.py b/core/morph/task/new.py index e4de9ea..e81c58f 100644 --- a/core/morph/task/new.py +++ b/core/morph/task/new.py @@ -119,7 +119,7 @@ def run(self): if project.build is None: project.build = BuildConfig() project.build.package_manager = project.package_manager - project.build.runtime = self.selected_python_version + project.build.runtime = f"python{self.selected_python_version}" save_project(self.project_root, project) From 41cbef1008581e90131e051c7590c2a6b7439b1b Mon Sep 17 00:00:00 2001 From: nkennek Date: Fri, 14 Mar 2025 14:41:00 +0900 Subject: [PATCH 12/39] feat: change initial dockerfile content --- core/morph/include/Dockerfile | 16 ++++++++++++ core/morph/include/Dockerfile.poetry | 39 ++++++++++++++++++++++++++++ core/morph/include/Dockerfile.uv | 38 +++++++++++++++++++++++++++ core/morph/task/new.py | 8 +++++- 4 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 core/morph/include/Dockerfile.poetry create mode 100644 core/morph/include/Dockerfile.uv diff --git a/core/morph/include/Dockerfile b/core/morph/include/Dockerfile index 328e1cc..bb0f9c9 100644 --- a/core/morph/include/Dockerfile +++ b/core/morph/include/Dockerfile @@ -1,3 +1,13 @@ +FROM node:lts AS morph-frontend-build + +WORKDIR /var/build + +# Copy source code and dependencies +COPY . . + +RUN npm install +RUN npm run build + # Base image for Morph Cloud FROM public.ecr.aws/i1l4z0u0/morph-data:python${MORPH_PYTHON_VERSION} @@ -8,8 +18,14 @@ WORKDIR /var/task COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt --target "${MORPH_PACKAGE_ROOT}" +# Copy Morph template +COPY ${MORPH_PACKAGE_ROOT}/frontend/template .morph/frontend + # Copy source code and dependencies COPY . . +COPY --from=morph-frontend-build /var/build/.morph .morph +COPY --from=morph-frontend-build /var/build/dist dist + # Command to run the Lambda function CMD python "${MORPH_APP_FILE_PATH}" diff --git a/core/morph/include/Dockerfile.poetry b/core/morph/include/Dockerfile.poetry new file mode 100644 index 0000000..e545585 --- /dev/null +++ b/core/morph/include/Dockerfile.poetry @@ -0,0 +1,39 @@ +FROM node:lts AS morph-frontend-build + +WORKDIR /var/build + +# Copy source code and dependencies +COPY . . + +RUN npm install +RUN npm run build + +FROM python:${MORPH_PYTHON_VERSION}-slim AS python-build + +WORKDIR /var/build + +RUN pip install poetry poetry-plugin-export +COPY poetry.lock pyproject.toml ./ +RUN poetry export -f requirements.txt -o requirements.txt --without-hashes + +# Base image for Morph Cloud +FROM public.ecr.aws/i1l4z0u0/morph-data:python${MORPH_PYTHON_VERSION} + +# Set working directory +WORKDIR /var/task + +# Install Python dependencies +COPY --from=python-build /var/build/requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt --target "${MORPH_PACKAGE_ROOT}" + +# Copy Morph template +COPY ${MORPH_PACKAGE_ROOT}/frontend/template .morph/frontend + +# Copy source code and dependencies +COPY . . + +COPY --from=morph-frontend-build /var/build/.morph .morph +COPY --from=morph-frontend-build /var/build/dist dist + +# Command to run the Lambda function +CMD python "${MORPH_APP_FILE_PATH}" diff --git a/core/morph/include/Dockerfile.uv b/core/morph/include/Dockerfile.uv new file mode 100644 index 0000000..2e46d7b --- /dev/null +++ b/core/morph/include/Dockerfile.uv @@ -0,0 +1,38 @@ +FROM node:lts AS morph-frontend-build + +WORKDIR /var/build + +# Copy source code and dependencies +COPY . . + +RUN npm install +RUN npm run build + +FROM python:${MORPH_PYTHON_VERSION}-slim AS python-build + +WORKDIR /var/build + +RUN pip install uv +RUN uv pip compile pyproject.toml -o requirements.txt + +# Base image for Morph Cloud +FROM public.ecr.aws/i1l4z0u0/morph-data:python${MORPH_PYTHON_VERSION} + +# Set working directory +WORKDIR /var/task + +# Install Python dependencies +COPY --from=python-build /var/build/requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt --target "${MORPH_PACKAGE_ROOT}" + +# Copy Morph template +COPY ${MORPH_PACKAGE_ROOT}/frontend/template .morph/frontend + +# Copy source code and dependencies +COPY . . + +COPY --from=morph-frontend-build /var/build/.morph .morph +COPY --from=morph-frontend-build /var/build/dist dist + +# Command to run the Lambda function +CMD python "${MORPH_APP_FILE_PATH}" diff --git a/core/morph/task/new.py b/core/morph/task/new.py index 68d95e9..40be559 100644 --- a/core/morph/task/new.py +++ b/core/morph/task/new.py @@ -115,7 +115,13 @@ def run(self): # Generate the Dockerfile template template_dir = Path(__file__).parents[1].joinpath("include") - docker_template_file = template_dir.joinpath("Dockerfile") + template_dockerfile_name = "Dockerfile" + if project.package_manager == "poetry": + template_dockerfile_name = "Dockerfile.poetry" + elif project.package_manager == "uv": + template_dockerfile_name = "Dockerfile.uv" + + docker_template_file = template_dir.joinpath(template_dockerfile_name) if not docker_template_file.exists(): click.echo( click.style( From ab708419ff482effc3d07505da384d00e1596cf1 Mon Sep 17 00:00:00 2001 From: nkennek Date: Fri, 14 Mar 2025 14:54:58 +0900 Subject: [PATCH 13/39] feat: remove Dockerfile/dockerignore generation on morph new --- core/morph/include/Dockerfile | 31 -------------- core/morph/include/Dockerfile.poetry | 39 ------------------ core/morph/include/Dockerfile.uv | 38 ----------------- .../include/starter_template/.dockerignore | 24 ----------- core/morph/task/new.py | 41 ------------------- 5 files changed, 173 deletions(-) delete mode 100644 core/morph/include/Dockerfile delete mode 100644 core/morph/include/Dockerfile.poetry delete mode 100644 core/morph/include/Dockerfile.uv delete mode 100644 core/morph/include/starter_template/.dockerignore diff --git a/core/morph/include/Dockerfile b/core/morph/include/Dockerfile deleted file mode 100644 index bb0f9c9..0000000 --- a/core/morph/include/Dockerfile +++ /dev/null @@ -1,31 +0,0 @@ -FROM node:lts AS morph-frontend-build - -WORKDIR /var/build - -# Copy source code and dependencies -COPY . . - -RUN npm install -RUN npm run build - -# Base image for Morph Cloud -FROM public.ecr.aws/i1l4z0u0/morph-data:python${MORPH_PYTHON_VERSION} - -# Set working directory -WORKDIR /var/task - -# Install Python dependencies -COPY requirements.txt . -RUN pip install --no-cache-dir -r requirements.txt --target "${MORPH_PACKAGE_ROOT}" - -# Copy Morph template -COPY ${MORPH_PACKAGE_ROOT}/frontend/template .morph/frontend - -# Copy source code and dependencies -COPY . . - -COPY --from=morph-frontend-build /var/build/.morph .morph -COPY --from=morph-frontend-build /var/build/dist dist - -# Command to run the Lambda function -CMD python "${MORPH_APP_FILE_PATH}" diff --git a/core/morph/include/Dockerfile.poetry b/core/morph/include/Dockerfile.poetry deleted file mode 100644 index e545585..0000000 --- a/core/morph/include/Dockerfile.poetry +++ /dev/null @@ -1,39 +0,0 @@ -FROM node:lts AS morph-frontend-build - -WORKDIR /var/build - -# Copy source code and dependencies -COPY . . - -RUN npm install -RUN npm run build - -FROM python:${MORPH_PYTHON_VERSION}-slim AS python-build - -WORKDIR /var/build - -RUN pip install poetry poetry-plugin-export -COPY poetry.lock pyproject.toml ./ -RUN poetry export -f requirements.txt -o requirements.txt --without-hashes - -# Base image for Morph Cloud -FROM public.ecr.aws/i1l4z0u0/morph-data:python${MORPH_PYTHON_VERSION} - -# Set working directory -WORKDIR /var/task - -# Install Python dependencies -COPY --from=python-build /var/build/requirements.txt . -RUN pip install --no-cache-dir -r requirements.txt --target "${MORPH_PACKAGE_ROOT}" - -# Copy Morph template -COPY ${MORPH_PACKAGE_ROOT}/frontend/template .morph/frontend - -# Copy source code and dependencies -COPY . . - -COPY --from=morph-frontend-build /var/build/.morph .morph -COPY --from=morph-frontend-build /var/build/dist dist - -# Command to run the Lambda function -CMD python "${MORPH_APP_FILE_PATH}" diff --git a/core/morph/include/Dockerfile.uv b/core/morph/include/Dockerfile.uv deleted file mode 100644 index 2e46d7b..0000000 --- a/core/morph/include/Dockerfile.uv +++ /dev/null @@ -1,38 +0,0 @@ -FROM node:lts AS morph-frontend-build - -WORKDIR /var/build - -# Copy source code and dependencies -COPY . . - -RUN npm install -RUN npm run build - -FROM python:${MORPH_PYTHON_VERSION}-slim AS python-build - -WORKDIR /var/build - -RUN pip install uv -RUN uv pip compile pyproject.toml -o requirements.txt - -# Base image for Morph Cloud -FROM public.ecr.aws/i1l4z0u0/morph-data:python${MORPH_PYTHON_VERSION} - -# Set working directory -WORKDIR /var/task - -# Install Python dependencies -COPY --from=python-build /var/build/requirements.txt . -RUN pip install --no-cache-dir -r requirements.txt --target "${MORPH_PACKAGE_ROOT}" - -# Copy Morph template -COPY ${MORPH_PACKAGE_ROOT}/frontend/template .morph/frontend - -# Copy source code and dependencies -COPY . . - -COPY --from=morph-frontend-build /var/build/.morph .morph -COPY --from=morph-frontend-build /var/build/dist dist - -# Command to run the Lambda function -CMD python "${MORPH_APP_FILE_PATH}" diff --git a/core/morph/include/starter_template/.dockerignore b/core/morph/include/starter_template/.dockerignore deleted file mode 100644 index c6c3627..0000000 --- a/core/morph/include/starter_template/.dockerignore +++ /dev/null @@ -1,24 +0,0 @@ -# .morph -.morph/* -!.morph/frontend/dist -!.morph/core -!.morph/meta.json - -# node_modules -node_modules -package-lock.json -package.json - -.mock_user_context.json -README.md -.gitignore -.env - -# Python cache files -__pycache__/ -*.py[cod] -*$py.class -.pytest_cache/ -.coverage -.mypy_cache/ -.ruff_cache/ diff --git a/core/morph/task/new.py b/core/morph/task/new.py index 40be559..0179203 100644 --- a/core/morph/task/new.py +++ b/core/morph/task/new.py @@ -113,47 +113,6 @@ def run(self): save_project(self.project_root, project) - # Generate the Dockerfile template - template_dir = Path(__file__).parents[1].joinpath("include") - template_dockerfile_name = "Dockerfile" - if project.package_manager == "poetry": - template_dockerfile_name = "Dockerfile.poetry" - elif project.package_manager == "uv": - template_dockerfile_name = "Dockerfile.uv" - - docker_template_file = template_dir.joinpath(template_dockerfile_name) - if not docker_template_file.exists(): - click.echo( - click.style( - f"Template file not found: {docker_template_file}", fg="red" - ) - ) - click.echo() - sys.exit(1) - - # Generate the Dockerfile with the selected Python version - dockerfile_path = os.path.join(self.project_root, "Dockerfile") - try: - with docker_template_file.open("r", encoding="utf-8") as f: - dockerfile_content = f.read() - - # Replace the placeholder with the selected Python version - dockerfile_content = dockerfile_content.replace( - "${MORPH_PYTHON_VERSION}", self.selected_python_version - ) - - # Write the updated Dockerfile to the project directory - with open(dockerfile_path, "w") as output_file: - output_file.write(dockerfile_content) - except FileNotFoundError as e: - click.echo( - click.style(f"Error: Template Dockerfile not found: {e}", fg="red") - ) - sys.exit(1) - except IOError as e: - click.echo(click.style(f"Error: Unable to write Dockerfile: {e}", fg="red")) - sys.exit(1) - try: morph_data_version = importlib.metadata.version("morph-data") except importlib.metadata.PackageNotFoundError: From 4d20a7c051664a648d0cd282c3927d2747504f1a Mon Sep 17 00:00:00 2001 From: tslcls Date: Fri, 14 Mar 2025 18:22:34 +0900 Subject: [PATCH 14/39] fix: file upload cache path --- core/morph/api/service.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/morph/api/service.py b/core/morph/api/service.py index 744aeea..f12cb5f 100644 --- a/core/morph/api/service.py +++ b/core/morph/api/service.py @@ -284,11 +284,11 @@ async def file_upload_service(input: UploadFileService) -> Any: ) # Read the saved file path from the cache (always created as following path) - cache_file = Path(find_project_root_dir()).joinpath( - ".morph/cache/file_upload.md" - ) - with open(cache_file, "r") as f: - saved_filepath = f.read() + saved_filepath = "" + cache_file = Path(temp_dir).joinpath("file_upload.md") + if cache_file.exists(): + with open(cache_file, "r") as f: + saved_filepath = f.read() # Remove the temporary directory if os.path.exists(temp_dir): From 223f795ace2b6582b55c1d7a4ada07d7742692bd Mon Sep 17 00:00:00 2001 From: tslcls Date: Fri, 14 Mar 2025 18:36:12 +0900 Subject: [PATCH 15/39] fix: file upload cache path --- core/morph/api/service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/morph/api/service.py b/core/morph/api/service.py index f12cb5f..8de3426 100644 --- a/core/morph/api/service.py +++ b/core/morph/api/service.py @@ -285,7 +285,7 @@ async def file_upload_service(input: UploadFileService) -> Any: # Read the saved file path from the cache (always created as following path) saved_filepath = "" - cache_file = Path(temp_dir).joinpath("file_upload.md") + cache_file = Path(temp_dir).joinpath("file_upload.cache") if cache_file.exists(): with open(cache_file, "r") as f: saved_filepath = f.read() From 69e5e53f48b5a1e131a36862a4fb008410a7ecd7 Mon Sep 17 00:00:00 2001 From: Atsuki Hasegawa <58581243+atsuki44@users.noreply.github.com> Date: Mon, 17 Mar 2025 18:22:18 +0900 Subject: [PATCH 16/39] chore: update starter template to support new frontend architecture --- .../include/starter_template/components.json | 21 ++ .../include/starter_template/package.json | 10 +- .../starter_template/src/pages/404.tsx | 8 + .../starter_template/src/pages/_app.tsx | 31 ++- .../starter_template/src/pages/_lib/utils.ts | 6 + .../src/pages/_morph-data-lib/header.tsx | 86 +++++++ .../src/pages/_morph-data-lib/index.ts | 3 + .../_morph-data-lib/root-error-boundary.tsx | 18 ++ .../_morph-data-lib/table-of-contents.tsx | 65 ++++++ .../starter_template/src/pages/index.css | 214 ++++++++++++++++++ .../starter_template/src/pages/index.mdx | 14 -- .../starter_template/tsconfig.app.json | 6 +- .../include/starter_template/tsconfig.json | 8 +- .../include/starter_template/vite.config.ts | 9 +- core/morph/task/new.py | 18 ++ 15 files changed, 491 insertions(+), 26 deletions(-) create mode 100644 core/morph/include/starter_template/components.json create mode 100644 core/morph/include/starter_template/src/pages/404.tsx create mode 100644 core/morph/include/starter_template/src/pages/_lib/utils.ts create mode 100644 core/morph/include/starter_template/src/pages/_morph-data-lib/header.tsx create mode 100644 core/morph/include/starter_template/src/pages/_morph-data-lib/index.ts create mode 100644 core/morph/include/starter_template/src/pages/_morph-data-lib/root-error-boundary.tsx create mode 100644 core/morph/include/starter_template/src/pages/_morph-data-lib/table-of-contents.tsx create mode 100644 core/morph/include/starter_template/src/pages/index.css diff --git a/core/morph/include/starter_template/components.json b/core/morph/include/starter_template/components.json new file mode 100644 index 0000000..67b484d --- /dev/null +++ b/core/morph/include/starter_template/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/pages/index.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/pages/_components", + "utils": "@/pages/_lib/utils", + "ui": "@/pages/_components/ui", + "lib": "@/pages/_lib", + "hooks": "@/pages/_hooks" + }, + "iconLibrary": "lucide" +} diff --git a/core/morph/include/starter_template/package.json b/core/morph/include/starter_template/package.json index 7e065d3..52b3630 100644 --- a/core/morph/include/starter_template/package.json +++ b/core/morph/include/starter_template/package.json @@ -8,15 +8,19 @@ "build": "morph-frontend build" }, "devDependencies": { + "@tailwindcss/vite": "^4.0.14", "@types/react": "^18.3.18", "@types/react-dom": "^18.3.5", "vite": "^6.2.1" }, "dependencies": { - "@morph-data/components": "^0.1.9", - "@morph-data/frontend": "0.3.0-beta.0", + "@morph-data/frontend": "0.3.0-beta.7", + "class-variance-authority": "^0.7.1", "react": "^18.3.1", "react-dom": "^18.3.1", - "react-error-boundary": "^5.0.0" + "react-error-boundary": "^5.0.0", + "tailwind-merge": "^3.0.2", + "tailwindcss": "^4.0.14", + "tailwindcss-animate": "^1.0.7" } } diff --git a/core/morph/include/starter_template/src/pages/404.tsx b/core/morph/include/starter_template/src/pages/404.tsx new file mode 100644 index 0000000..6aa94d6 --- /dev/null +++ b/core/morph/include/starter_template/src/pages/404.tsx @@ -0,0 +1,8 @@ +export default function NotFound() { + return ( +
+

404

+

Page not found

+
+ ); +} diff --git a/core/morph/include/starter_template/src/pages/_app.tsx b/core/morph/include/starter_template/src/pages/_app.tsx index ba534c0..c77c0cf 100644 --- a/core/morph/include/starter_template/src/pages/_app.tsx +++ b/core/morph/include/starter_template/src/pages/_app.tsx @@ -1,21 +1,38 @@ -import { Head, Toc } from "@morph-data/components"; -import { RootErrorBoundary, Header } from "../lib"; +import { Head } from "@morph-data/frontend/components"; +import { RootErrorBoundary, Header, TableOfContents } from "./_morph-data-lib"; import { usePageMeta, MdxComponentsProvider, Outlet, + StateProvider, + extractComponents, } from "@morph-data/frontend/components"; +import "./index.css"; + +const uiComponents = extractComponents( + import.meta.glob("./_components/ui/**/*.tsx", { + eager: true, + }) +); + +const morphComponents = extractComponents( + import.meta.glob("./_components/*.tsx", { + eager: true, + }) +); export default function App() { const pageMeta = usePageMeta(); return ( - + {pageMeta?.title} - +
@@ -26,10 +43,12 @@ export default function App() {
- + + +
- diff --git a/core/morph/include/starter_template/src/pages/_lib/utils.ts b/core/morph/include/starter_template/src/pages/_lib/utils.ts new file mode 100644 index 0000000..a5ef193 --- /dev/null +++ b/core/morph/include/starter_template/src/pages/_lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/core/morph/include/starter_template/src/pages/_morph-data-lib/header.tsx b/core/morph/include/starter_template/src/pages/_morph-data-lib/header.tsx new file mode 100644 index 0000000..2be7531 --- /dev/null +++ b/core/morph/include/starter_template/src/pages/_morph-data-lib/header.tsx @@ -0,0 +1,86 @@ +import { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, +} from "@/pages/_components/ui/dropdown-menu"; +import { Button } from "@/pages/_components/ui/button"; +import { usePages, Link } from "@morph-data/frontend/components"; +import { PropsWithChildren } from "react"; + +const Root = ({ children }: PropsWithChildren) => { + return
{children}
; +}; + +const DropDownMenu = () => { + const pages = usePages(); + + return ( + + + + + + {pages.map((page) => ( + + + {page.title} + + + ))} + {/* {props.showAdminPage && ( + <> + + + Admin Page + + + )} */} + + + ); +}; + +const PageTitle = ({ title }: { title: string }) => { + return
{title}
; +}; + +const Spacer = () =>
; + +const MorphLogo = () => ( +
+ Made with + + Morph + +
+); + +export const Header = { + Root, + DropDownMenu, + PageTitle, + Spacer, + MorphLogo, +}; diff --git a/core/morph/include/starter_template/src/pages/_morph-data-lib/index.ts b/core/morph/include/starter_template/src/pages/_morph-data-lib/index.ts new file mode 100644 index 0000000..81471bf --- /dev/null +++ b/core/morph/include/starter_template/src/pages/_morph-data-lib/index.ts @@ -0,0 +1,3 @@ +export { RootErrorBoundary } from "./root-error-boundary"; +export { Header } from "./header"; +export { TableOfContents } from "./table-of-contents"; diff --git a/core/morph/include/starter_template/src/pages/_morph-data-lib/root-error-boundary.tsx b/core/morph/include/starter_template/src/pages/_morph-data-lib/root-error-boundary.tsx new file mode 100644 index 0000000..0344c00 --- /dev/null +++ b/core/morph/include/starter_template/src/pages/_morph-data-lib/root-error-boundary.tsx @@ -0,0 +1,18 @@ +import { PropsWithChildren } from "react"; +import { ErrorBoundary, FallbackProps } from "react-error-boundary"; + +function fallbackRender({ error }: FallbackProps) { + // Call resetErrorBoundary() to reset the error boundary and retry the render. + return ( +
+

Something went wrong:

+
{error.message}
+
+ ); +} + +export const RootErrorBoundary = ({ children }: PropsWithChildren) => { + return ( + {children} + ); +}; diff --git a/core/morph/include/starter_template/src/pages/_morph-data-lib/table-of-contents.tsx b/core/morph/include/starter_template/src/pages/_morph-data-lib/table-of-contents.tsx new file mode 100644 index 0000000..c51e666 --- /dev/null +++ b/core/morph/include/starter_template/src/pages/_morph-data-lib/table-of-contents.tsx @@ -0,0 +1,65 @@ +import { + HoverCard, + HoverCardTrigger, + HoverCardContent, +} from "@/pages/_components/ui/hover-card"; +import { Card } from "@/pages/_components/ui/card"; +import { Button } from "@/pages/_components/ui/button"; +import { LucideTableOfContents } from "lucide-react"; +import { cn } from "@/pages/_lib/utils"; +import { Toc } from "@morph-data/frontend/components"; + +export interface TocProps { + toc?: Toc; + className?: string; +} + +export const TableOfContents: React.FC = ({ toc, className }) => { + if (!toc) { + return null; + } + + return ( + <> + +
+ + + + + + +
+ {toc.map((entry) => ( + +
+ {entry.value} +
+
+ ))} +
+
+
+
+
+ + ); +}; diff --git a/core/morph/include/starter_template/src/pages/index.css b/core/morph/include/starter_template/src/pages/index.css new file mode 100644 index 0000000..f6a6be0 --- /dev/null +++ b/core/morph/include/starter_template/src/pages/index.css @@ -0,0 +1,214 @@ +@import "tailwindcss"; + +@plugin "tailwindcss-animate"; + +@custom-variant dark (&:is(.dark *)); + +:root { + --background: oklch(1 0 0); + --foreground: oklch(0.145 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); + --destructive-foreground: oklch(0.577 0.245 27.325); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --radius: 0.625rem; + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); +} + +.dark { + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.145 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.145 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.985 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.269 0 0); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.396 0.141 25.723); + --destructive-foreground: oklch(0.637 0.237 25.331); + --border: oklch(0.269 0 0); + --input: oklch(0.269 0 0); + --ring: oklch(0.439 0 0); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(0.269 0 0); + --sidebar-ring: oklch(0.439 0 0); +} + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-destructive-foreground: var(--destructive-foreground); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); + --animate-accordion-down: accordion-down 0.2s ease-out; + --animate-accordion-up: accordion-up 0.2s ease-out; + + @keyframes accordion-down { + from { + height: 0; + } + to { + height: var(--radix-accordion-content-height); + } + } + + @keyframes accordion-up { + from { + height: var(--radix-accordion-content-height); + } + to { + height: 0; + } + } +} + +@layer base { + * { + @apply border-border outline-ring/50; + + /* Pre-defined Styles for user generated contents */ + .morph-page { + h1 { + @apply mt-10 scroll-m-20 text-3xl font-bold tracking-tight lg:text-4xl; + } + h2 { + @apply mt-8 scroll-m-20 pb-2 text-2xl font-semibold tracking-tight transition-colors first:mt-0; + } + h3 { + @apply mt-6 scroll-m-20 text-xl font-semibold tracking-tight; + } + p { + @apply leading-relaxed [&:not(:first-child)]:mt-6; + } + a { + @apply font-medium underline underline-offset-4; + &.x-underline { + @apply no-underline; + } + } + blockquote { + @apply mt-6 border-l-2 pl-6 italic; + } + ul { + @apply my-3 ml-3 list-disc list-inside [&>li]:mt-2; + } + table { + @apply table-auto min-w-full text-sm text-left rtl:text-right py-4; + } + thead { + @apply [&_tr]:border-b; + } + th { + @apply py-2 px-2 text-left align-middle font-medium [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]; + } + tbody { + @apply [&_tr:last-child]:border-0; + } + td { + @apply p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]; + } + figure[data-rehype-pretty-code-figure] { + width: 100%; + max-width: 100%; + min-width: 100%; + } + pre { + @apply my-4; + width: 100%; + max-width: 100%; + min-width: 100%; + overflow-x: auto; + padding: 1rem 0; + border-radius: 0.5rem; + box-sizing: border-box; + + [data-line] { + padding: 0 1rem; + font-size: 0.8rem; + } + + > code { + display: block; + } + } + :not(pre) > code { + @apply font-mono text-sm rounded bg-gray-100 dark:bg-neutral-700 px-1.5 py-1; + } + } + } + body { + @apply bg-background text-foreground; + } +} diff --git a/core/morph/include/starter_template/src/pages/index.mdx b/core/morph/include/starter_template/src/pages/index.mdx index 363d53e..5465bcd 100644 --- a/core/morph/include/starter_template/src/pages/index.mdx +++ b/core/morph/include/starter_template/src/pages/index.mdx @@ -1,17 +1,3 @@ # Welcome to Morph Morph is a Python + Markdown framework for building internal AI apps. - -## 📚 Resources - -- [🚀 Deploy Now](https://app.morph-data.io) -- [📖 Documentation](https://docs.morph-data.io) -- [💻 GitHub](https://github.com/morph-data/morph) - -## 🛠️ Getting started - -### Tutorials - -- [🚀 Quickstart for AI App](https://docs.morph-data.io/docs/en/quickstart/building-app) -- [📊 Dashboard tutorial](https://docs.morph-data.io/docs/en/develop/tutorials/plotly) -- [📈 Pygwalker tutorial](https://docs.morph-data.io/docs/en/develop/tutorials/pygwalker) diff --git a/core/morph/include/starter_template/tsconfig.app.json b/core/morph/include/starter_template/tsconfig.app.json index 8c62397..98111af 100644 --- a/core/morph/include/starter_template/tsconfig.app.json +++ b/core/morph/include/starter_template/tsconfig.app.json @@ -1,7 +1,11 @@ { "extends": "@morph-data/frontend/tsconfig.app.json", "compilerOptions": { - "composite": true + "composite": true, + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } }, "include": ["src"] } diff --git a/core/morph/include/starter_template/tsconfig.json b/core/morph/include/starter_template/tsconfig.json index ea9d0cd..1e17393 100644 --- a/core/morph/include/starter_template/tsconfig.json +++ b/core/morph/include/starter_template/tsconfig.json @@ -7,5 +7,11 @@ { "path": "./tsconfig.node.json" } - ] + ], + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + } } diff --git a/core/morph/include/starter_template/vite.config.ts b/core/morph/include/starter_template/vite.config.ts index 4c7a434..e68cbc2 100644 --- a/core/morph/include/starter_template/vite.config.ts +++ b/core/morph/include/starter_template/vite.config.ts @@ -1,6 +1,13 @@ import { morph } from "@morph-data/frontend/plugin"; import { defineConfig } from "vite"; +import path from "path"; +import tailwindcss from "@tailwindcss/vite"; export default defineConfig({ - plugins: [morph()], + plugins: [morph(), tailwindcss()], + resolve: { + alias: { + "@": path.resolve(__dirname, "./src"), + }, + }, }); diff --git a/core/morph/task/new.py b/core/morph/task/new.py index d3bf63a..249231b 100644 --- a/core/morph/task/new.py +++ b/core/morph/task/new.py @@ -249,6 +249,24 @@ def run(self): ) sys.exit(1) + # Setup Frontend + subprocess.run( + [ + "npm", + "install", + ], + cwd=self.project_root, + ) + subprocess.run( + [ + "npx", + "shadcn@latest", + "add", + "https://morph-components.vercel.app/r/morph-components.json", + ], + cwd=self.project_root, + ) + click.echo() click.echo(click.style("Project setup completed successfully! 🎉", fg="green")) return True From e83122ccbefa900edf79207c8c0f8b9fade8a002 Mon Sep 17 00:00:00 2001 From: tslcls Date: Mon, 17 Mar 2025 19:14:20 +0900 Subject: [PATCH 17/39] fix: file upload cache path --- core/morph/api/service.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/morph/api/service.py b/core/morph/api/service.py index 8de3426..d5a350e 100644 --- a/core/morph/api/service.py +++ b/core/morph/api/service.py @@ -7,7 +7,6 @@ import time import uuid from contextlib import redirect_stdout -from pathlib import Path from typing import Any import click @@ -285,8 +284,8 @@ async def file_upload_service(input: UploadFileService) -> Any: # Read the saved file path from the cache (always created as following path) saved_filepath = "" - cache_file = Path(temp_dir).joinpath("file_upload.cache") - if cache_file.exists(): + cache_file = "/tmp/file_upload.cache" + if os.path.exists(cache_file): with open(cache_file, "r") as f: saved_filepath = f.read() From dac8114469a9f1f37059165bfb98746e89b8a1d5 Mon Sep 17 00:00:00 2001 From: Atsuki Hasegawa <58581243+atsuki44@users.noreply.github.com> Date: Wed, 19 Mar 2025 10:28:00 +0900 Subject: [PATCH 18/39] chore: update @morph-data/frontend --- core/morph/include/starter_template/package.json | 2 +- core/morph/include/starter_template/src/pages/_app.tsx | 8 ++++---- core/morph/task/api.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/core/morph/include/starter_template/package.json b/core/morph/include/starter_template/package.json index 52b3630..fbd4e4b 100644 --- a/core/morph/include/starter_template/package.json +++ b/core/morph/include/starter_template/package.json @@ -14,7 +14,7 @@ "vite": "^6.2.1" }, "dependencies": { - "@morph-data/frontend": "0.3.0-beta.7", + "@morph-data/frontend": "0.3.0-beta.8", "class-variance-authority": "^0.7.1", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/core/morph/include/starter_template/src/pages/_app.tsx b/core/morph/include/starter_template/src/pages/_app.tsx index c77c0cf..29c8966 100644 --- a/core/morph/include/starter_template/src/pages/_app.tsx +++ b/core/morph/include/starter_template/src/pages/_app.tsx @@ -4,7 +4,7 @@ import { usePageMeta, MdxComponentsProvider, Outlet, - StateProvider, + useRefresh, extractComponents, } from "@morph-data/frontend/components"; import "./index.css"; @@ -24,6 +24,8 @@ const morphComponents = extractComponents( export default function App() { const pageMeta = usePageMeta(); + useRefresh(); + return ( @@ -43,9 +45,7 @@ export default function App() {
- - - +
None: self._run_process( ["npm", "run", "dev", "--", "--port", f"{self.front_port}"], cwd=self.project_root, - is_debug=False, + is_debug=True, ) def _run_process( From f8b36439f527d51cd6e3b237e13f6b380f38ce6f Mon Sep 17 00:00:00 2001 From: shibatanaoto Date: Fri, 21 Mar 2025 01:22:24 +0900 Subject: [PATCH 19/39] get dockerfile by calling api --- core/morph/task/deploy.py | 54 +++++++++++------------- core/morph/task/utils/load_dockerfile.py | 39 +++++++++++++++++ 2 files changed, 63 insertions(+), 30 deletions(-) create mode 100644 core/morph/task/utils/load_dockerfile.py diff --git a/core/morph/task/deploy.py b/core/morph/task/deploy.py index 629c0e3..8884b3a 100644 --- a/core/morph/task/deploy.py +++ b/core/morph/task/deploy.py @@ -8,14 +8,16 @@ import click import requests +from tqdm import tqdm + from morph.api.cloud.client import MorphApiKeyClientImpl from morph.api.cloud.types import EnvVarObject from morph.cli.flags import Flags from morph.config.project import load_project from morph.task.base import BaseTask from morph.task.utils.file_upload import FileWithProgress +from morph.task.utils.load_dockerfile import get_dockerfile_from_api from morph.task.utils.morph import find_project_root_dir -from tqdm import tqdm class DeployTask(BaseTask): @@ -49,9 +51,24 @@ def __init__(self, args: Flags): # Check Dockerfile existence self.dockerfile = os.path.join(self.project_root, "Dockerfile") - if not os.path.exists(self.dockerfile): - click.echo(click.style(f"Error: {self.dockerfile} not found", fg="red")) - sys.exit(1) + if self.project.build is not None and self.project.build.use_custom_dockerfile: + if not os.path.exists(self.dockerfile): + click.echo(click.style(f"Error: {self.dockerfile} not found", fg="red")) + sys.exit(1) + else: + if self.project.build is None: + dockerfile, dockerignore = get_dockerfile_from_api("morph", None, None) + else: + dockerfile, dockerignore = get_dockerfile_from_api( + self.project.build.framework, + self.project.build.package_manager, + self.project.build.runtime, + ) + with open(self.dockerfile, "w") as f: + f.write(dockerfile) + dockerignore_path = os.path.join(self.project_root, ".dockerignore") + with open(dockerignore_path, "w") as f: + f.write(dockerignore) # Check Docker availability try: @@ -105,7 +122,7 @@ def run(self): click.echo(click.style("Initiating deployment sequence...", fg="blue")) # 1. Build the source code - self._copy_and_build_source() + self._build_source() # 2. Build the Docker image click.echo(click.style("Building Docker image...", fg="blue")) @@ -387,31 +404,8 @@ def _validate_api_key(self): ) sys.exit(1) - def _copy_and_build_source(self): - click.echo(click.style("Building frontend...", fg="blue")) - try: - # Run npm install and build - subprocess.run( - ["npm", "install"], - cwd=self.project_root, - check=True, - shell=True if sys.platform == "win32" else False, - ) - subprocess.run( - ["npm", "run", "build"], - cwd=self.project_root, - check=True, - shell=True if sys.platform == "win32" else False, - ) - - except subprocess.CalledProcessError as e: - click.echo(click.style(f"Error building frontend: {str(e)}", fg="red")) - sys.exit(1) - except Exception as e: - click.echo(click.style(f"Unexpected error: {str(e)}", fg="red")) - sys.exit(1) - - click.echo(click.style("Building backend...", fg="blue")) + def _build_source(self): + click.echo(click.style("Compiling morph project...", fg="blue")) try: # Compile the morph project subprocess.run( diff --git a/core/morph/task/utils/load_dockerfile.py b/core/morph/task/utils/load_dockerfile.py new file mode 100644 index 0000000..64c950f --- /dev/null +++ b/core/morph/task/utils/load_dockerfile.py @@ -0,0 +1,39 @@ +from typing import Any, Dict, Optional, Tuple + +import requests + + +def get_dockerfile_from_api( + framework: Optional[str] = "morph", + package_manager: Optional[str] = None, + runtime: Optional[str] = None, +) -> Tuple[str, str]: + """ + Fetch dockerfile and dockerignore from the Morph API. + + Args: + framework: The framework to get the dockerfile for + package_manager: Optional package manager to use + language_version: Optional language version to use + + Returns: + Tuple containing (dockerfile, dockerignore) + """ + url = f"https://backend-api-public.morph-cb9.workers.dev/dockerfile/{framework}" + + params: Dict[str, Any] = {} + if package_manager: + params["packageManager"] = package_manager + if runtime: + params["runtime"] = runtime + + response = requests.get(url, params=params) + + response.raise_for_status() + + data = response.json() + + if "error" in data: + raise ValueError(data["error"]) + + return data["dockerfile"], data["dockerignore"] From 5e699bf6db5de62aabb98385ec2c6c8678485190 Mon Sep 17 00:00:00 2001 From: shibatanaoto Date: Fri, 21 Mar 2025 10:36:12 +0900 Subject: [PATCH 20/39] update version as v0.3.0rc1 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ccca382..9c1bd08 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "morph-data" -version = "0.2.1" +version = "0.3.0rc1" description = "Morph is a python-centric full-stack framework for building and deploying data apps." authors = ["Morph "] packages = [ From 35d89ddac5eac76d9971b436b8468d45d3da82f4 Mon Sep 17 00:00:00 2001 From: Atsuki Hasegawa <58581243+atsuki44@users.noreply.github.com> Date: Fri, 21 Mar 2025 13:37:35 +0900 Subject: [PATCH 21/39] chore: update index.mdx --- .../include/starter_template/src/pages/404.mdx | 3 +++ .../include/starter_template/src/pages/404.tsx | 8 -------- .../include/starter_template/src/pages/index.mdx | 14 ++++++++++++++ 3 files changed, 17 insertions(+), 8 deletions(-) create mode 100644 core/morph/include/starter_template/src/pages/404.mdx delete mode 100644 core/morph/include/starter_template/src/pages/404.tsx diff --git a/core/morph/include/starter_template/src/pages/404.mdx b/core/morph/include/starter_template/src/pages/404.mdx new file mode 100644 index 0000000..04d6977 --- /dev/null +++ b/core/morph/include/starter_template/src/pages/404.mdx @@ -0,0 +1,3 @@ +# 404 + +Page not found diff --git a/core/morph/include/starter_template/src/pages/404.tsx b/core/morph/include/starter_template/src/pages/404.tsx deleted file mode 100644 index 6aa94d6..0000000 --- a/core/morph/include/starter_template/src/pages/404.tsx +++ /dev/null @@ -1,8 +0,0 @@ -export default function NotFound() { - return ( -
-

404

-

Page not found

-
- ); -} diff --git a/core/morph/include/starter_template/src/pages/index.mdx b/core/morph/include/starter_template/src/pages/index.mdx index 5465bcd..363d53e 100644 --- a/core/morph/include/starter_template/src/pages/index.mdx +++ b/core/morph/include/starter_template/src/pages/index.mdx @@ -1,3 +1,17 @@ # Welcome to Morph Morph is a Python + Markdown framework for building internal AI apps. + +## 📚 Resources + +- [🚀 Deploy Now](https://app.morph-data.io) +- [📖 Documentation](https://docs.morph-data.io) +- [💻 GitHub](https://github.com/morph-data/morph) + +## 🛠️ Getting started + +### Tutorials + +- [🚀 Quickstart for AI App](https://docs.morph-data.io/docs/en/quickstart/building-app) +- [📊 Dashboard tutorial](https://docs.morph-data.io/docs/en/develop/tutorials/plotly) +- [📈 Pygwalker tutorial](https://docs.morph-data.io/docs/en/develop/tutorials/pygwalker) From 5e4f70be0883c00bc847e57cdc893899d5a3b786 Mon Sep 17 00:00:00 2001 From: Atsuki Hasegawa <58581243+atsuki44@users.noreply.github.com> Date: Fri, 21 Mar 2025 13:39:49 +0900 Subject: [PATCH 22/39] chore: add dist to .gitignore --- core/morph/include/starter_template/.gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/morph/include/starter_template/.gitignore b/core/morph/include/starter_template/.gitignore index 7acf8d2..b4386bf 100644 --- a/core/morph/include/starter_template/.gitignore +++ b/core/morph/include/starter_template/.gitignore @@ -1,6 +1,9 @@ # Morph config files and directories .morph +# Morph build output +dist + # Node.js node_modules From 42ad3e8ec5846a55f374830051cdb0a6cefa2233 Mon Sep 17 00:00:00 2001 From: Atsuki Hasegawa <58581243+atsuki44@users.noreply.github.com> Date: Fri, 21 Mar 2025 13:40:24 +0900 Subject: [PATCH 23/39] chore: bundle tsconfig.json --- .../include/starter_template/tsconfig.app.json | 11 ----------- core/morph/include/starter_template/tsconfig.json | 14 ++++---------- .../include/starter_template/tsconfig.node.json | 7 ------- 3 files changed, 4 insertions(+), 28 deletions(-) delete mode 100644 core/morph/include/starter_template/tsconfig.app.json delete mode 100644 core/morph/include/starter_template/tsconfig.node.json diff --git a/core/morph/include/starter_template/tsconfig.app.json b/core/morph/include/starter_template/tsconfig.app.json deleted file mode 100644 index 98111af..0000000 --- a/core/morph/include/starter_template/tsconfig.app.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": "@morph-data/frontend/tsconfig.app.json", - "compilerOptions": { - "composite": true, - "baseUrl": ".", - "paths": { - "@/*": ["./src/*"] - } - }, - "include": ["src"] -} diff --git a/core/morph/include/starter_template/tsconfig.json b/core/morph/include/starter_template/tsconfig.json index 1e17393..98111af 100644 --- a/core/morph/include/starter_template/tsconfig.json +++ b/core/morph/include/starter_template/tsconfig.json @@ -1,17 +1,11 @@ { - "files": [], - "references": [ - { - "path": "./tsconfig.app.json" - }, - { - "path": "./tsconfig.node.json" - } - ], + "extends": "@morph-data/frontend/tsconfig.app.json", "compilerOptions": { + "composite": true, "baseUrl": ".", "paths": { "@/*": ["./src/*"] } - } + }, + "include": ["src"] } diff --git a/core/morph/include/starter_template/tsconfig.node.json b/core/morph/include/starter_template/tsconfig.node.json deleted file mode 100644 index ce779b4..0000000 --- a/core/morph/include/starter_template/tsconfig.node.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "@morph-data/frontend/tsconfig.node.json", - "compilerOptions": { - "composite": true - }, - "include": ["vite.config.ts"] -} From ba8db6c096a45ecfb4751d68ff7dff4819749a62 Mon Sep 17 00:00:00 2001 From: Atsuki Hasegawa <58581243+atsuki44@users.noreply.github.com> Date: Fri, 21 Mar 2025 13:51:02 +0900 Subject: [PATCH 24/39] chore: move layout components to _components --- .../starter_template/src/pages/_app.tsx | 7 +- .../src/pages/_components/header.tsx | 86 +++++++++++++++++++ .../pages/_components/table-of-contents.tsx | 65 ++++++++++++++ 3 files changed, 155 insertions(+), 3 deletions(-) create mode 100644 core/morph/include/starter_template/src/pages/_components/header.tsx create mode 100644 core/morph/include/starter_template/src/pages/_components/table-of-contents.tsx diff --git a/core/morph/include/starter_template/src/pages/_app.tsx b/core/morph/include/starter_template/src/pages/_app.tsx index 29c8966..b0339d0 100644 --- a/core/morph/include/starter_template/src/pages/_app.tsx +++ b/core/morph/include/starter_template/src/pages/_app.tsx @@ -1,5 +1,6 @@ import { Head } from "@morph-data/frontend/components"; -import { RootErrorBoundary, Header, TableOfContents } from "./_morph-data-lib"; +import { TableOfContents } from "./_components/table-of-contents"; +import { Header } from "./_components/header"; import { usePageMeta, MdxComponentsProvider, @@ -27,7 +28,7 @@ export default function App() { useRefresh(); return ( - + <> {pageMeta?.title} @@ -57,6 +58,6 @@ export default function App() {
- + ); } diff --git a/core/morph/include/starter_template/src/pages/_components/header.tsx b/core/morph/include/starter_template/src/pages/_components/header.tsx new file mode 100644 index 0000000..2be7531 --- /dev/null +++ b/core/morph/include/starter_template/src/pages/_components/header.tsx @@ -0,0 +1,86 @@ +import { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, +} from "@/pages/_components/ui/dropdown-menu"; +import { Button } from "@/pages/_components/ui/button"; +import { usePages, Link } from "@morph-data/frontend/components"; +import { PropsWithChildren } from "react"; + +const Root = ({ children }: PropsWithChildren) => { + return
{children}
; +}; + +const DropDownMenu = () => { + const pages = usePages(); + + return ( + + + + + + {pages.map((page) => ( + + + {page.title} + + + ))} + {/* {props.showAdminPage && ( + <> + + + Admin Page + + + )} */} + + + ); +}; + +const PageTitle = ({ title }: { title: string }) => { + return
{title}
; +}; + +const Spacer = () =>
; + +const MorphLogo = () => ( +
+ Made with + + Morph + +
+); + +export const Header = { + Root, + DropDownMenu, + PageTitle, + Spacer, + MorphLogo, +}; diff --git a/core/morph/include/starter_template/src/pages/_components/table-of-contents.tsx b/core/morph/include/starter_template/src/pages/_components/table-of-contents.tsx new file mode 100644 index 0000000..c51e666 --- /dev/null +++ b/core/morph/include/starter_template/src/pages/_components/table-of-contents.tsx @@ -0,0 +1,65 @@ +import { + HoverCard, + HoverCardTrigger, + HoverCardContent, +} from "@/pages/_components/ui/hover-card"; +import { Card } from "@/pages/_components/ui/card"; +import { Button } from "@/pages/_components/ui/button"; +import { LucideTableOfContents } from "lucide-react"; +import { cn } from "@/pages/_lib/utils"; +import { Toc } from "@morph-data/frontend/components"; + +export interface TocProps { + toc?: Toc; + className?: string; +} + +export const TableOfContents: React.FC = ({ toc, className }) => { + if (!toc) { + return null; + } + + return ( + <> + +
+ + + + + + +
+ {toc.map((entry) => ( + +
+ {entry.value} +
+
+ ))} +
+
+
+
+
+ + ); +}; From 838f7631595375d63daadb84b0d65337789b554d Mon Sep 17 00:00:00 2001 From: Atsuki Hasegawa <58581243+atsuki44@users.noreply.github.com> Date: Fri, 21 Mar 2025 13:58:43 +0900 Subject: [PATCH 25/39] chore: add error fallback --- .../starter_template/src/pages/_app.tsx | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/core/morph/include/starter_template/src/pages/_app.tsx b/core/morph/include/starter_template/src/pages/_app.tsx index b0339d0..57f0dc1 100644 --- a/core/morph/include/starter_template/src/pages/_app.tsx +++ b/core/morph/include/starter_template/src/pages/_app.tsx @@ -8,6 +8,9 @@ import { useRefresh, extractComponents, } from "@morph-data/frontend/components"; +import { ErrorBoundary } from "react-error-boundary"; +import { Callout } from "@/pages/_components/ui/callout"; + import "./index.css"; const uiComponents = extractComponents( @@ -46,7 +49,17 @@ export default function App() {
- + ( + + {typeof error.message === "string" + ? error.message + : "Something went wrong"} + + )} + > + +
); } + +export const Catch = () => ( + + Something went wrong + +); From 0639723ce7aaab805a7bb0103de95573ee8af575 Mon Sep 17 00:00:00 2001 From: Atsuki Hasegawa <58581243+atsuki44@users.noreply.github.com> Date: Fri, 21 Mar 2025 14:57:06 +0900 Subject: [PATCH 26/39] fix: toc style --- .../pages/_components/table-of-contents.tsx | 39 ++++++++----------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/core/morph/include/starter_template/src/pages/_components/table-of-contents.tsx b/core/morph/include/starter_template/src/pages/_components/table-of-contents.tsx index c51e666..4f31532 100644 --- a/core/morph/include/starter_template/src/pages/_components/table-of-contents.tsx +++ b/core/morph/include/starter_template/src/pages/_components/table-of-contents.tsx @@ -3,7 +3,6 @@ import { HoverCardTrigger, HoverCardContent, } from "@/pages/_components/ui/hover-card"; -import { Card } from "@/pages/_components/ui/card"; import { Button } from "@/pages/_components/ui/button"; import { LucideTableOfContents } from "lucide-react"; import { cn } from "@/pages/_lib/utils"; @@ -24,13 +23,12 @@ export const TableOfContents: React.FC = ({ toc, className }) => { From 81d6b61d7dc63588ff5b49c79ab1d3b8844e197d Mon Sep 17 00:00:00 2001 From: Atsuki Hasegawa <58581243+atsuki44@users.noreply.github.com> Date: Fri, 21 Mar 2025 14:58:54 +0900 Subject: [PATCH 27/39] chore: remove _morph-data-lib from template --- .../src/pages/_morph-data-lib/header.tsx | 86 ------------------- .../src/pages/_morph-data-lib/index.ts | 3 - .../_morph-data-lib/root-error-boundary.tsx | 18 ---- .../_morph-data-lib/table-of-contents.tsx | 65 -------------- 4 files changed, 172 deletions(-) delete mode 100644 core/morph/include/starter_template/src/pages/_morph-data-lib/header.tsx delete mode 100644 core/morph/include/starter_template/src/pages/_morph-data-lib/index.ts delete mode 100644 core/morph/include/starter_template/src/pages/_morph-data-lib/root-error-boundary.tsx delete mode 100644 core/morph/include/starter_template/src/pages/_morph-data-lib/table-of-contents.tsx diff --git a/core/morph/include/starter_template/src/pages/_morph-data-lib/header.tsx b/core/morph/include/starter_template/src/pages/_morph-data-lib/header.tsx deleted file mode 100644 index 2be7531..0000000 --- a/core/morph/include/starter_template/src/pages/_morph-data-lib/header.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import { - DropdownMenu, - DropdownMenuTrigger, - DropdownMenuContent, - DropdownMenuItem, -} from "@/pages/_components/ui/dropdown-menu"; -import { Button } from "@/pages/_components/ui/button"; -import { usePages, Link } from "@morph-data/frontend/components"; -import { PropsWithChildren } from "react"; - -const Root = ({ children }: PropsWithChildren) => { - return
{children}
; -}; - -const DropDownMenu = () => { - const pages = usePages(); - - return ( - - - - - - {pages.map((page) => ( - - - {page.title} - - - ))} - {/* {props.showAdminPage && ( - <> - - - Admin Page - - - )} */} - - - ); -}; - -const PageTitle = ({ title }: { title: string }) => { - return
{title}
; -}; - -const Spacer = () =>
; - -const MorphLogo = () => ( -
- Made with - - Morph - -
-); - -export const Header = { - Root, - DropDownMenu, - PageTitle, - Spacer, - MorphLogo, -}; diff --git a/core/morph/include/starter_template/src/pages/_morph-data-lib/index.ts b/core/morph/include/starter_template/src/pages/_morph-data-lib/index.ts deleted file mode 100644 index 81471bf..0000000 --- a/core/morph/include/starter_template/src/pages/_morph-data-lib/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { RootErrorBoundary } from "./root-error-boundary"; -export { Header } from "./header"; -export { TableOfContents } from "./table-of-contents"; diff --git a/core/morph/include/starter_template/src/pages/_morph-data-lib/root-error-boundary.tsx b/core/morph/include/starter_template/src/pages/_morph-data-lib/root-error-boundary.tsx deleted file mode 100644 index 0344c00..0000000 --- a/core/morph/include/starter_template/src/pages/_morph-data-lib/root-error-boundary.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { PropsWithChildren } from "react"; -import { ErrorBoundary, FallbackProps } from "react-error-boundary"; - -function fallbackRender({ error }: FallbackProps) { - // Call resetErrorBoundary() to reset the error boundary and retry the render. - return ( -
-

Something went wrong:

-
{error.message}
-
- ); -} - -export const RootErrorBoundary = ({ children }: PropsWithChildren) => { - return ( - {children} - ); -}; diff --git a/core/morph/include/starter_template/src/pages/_morph-data-lib/table-of-contents.tsx b/core/morph/include/starter_template/src/pages/_morph-data-lib/table-of-contents.tsx deleted file mode 100644 index c51e666..0000000 --- a/core/morph/include/starter_template/src/pages/_morph-data-lib/table-of-contents.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { - HoverCard, - HoverCardTrigger, - HoverCardContent, -} from "@/pages/_components/ui/hover-card"; -import { Card } from "@/pages/_components/ui/card"; -import { Button } from "@/pages/_components/ui/button"; -import { LucideTableOfContents } from "lucide-react"; -import { cn } from "@/pages/_lib/utils"; -import { Toc } from "@morph-data/frontend/components"; - -export interface TocProps { - toc?: Toc; - className?: string; -} - -export const TableOfContents: React.FC = ({ toc, className }) => { - if (!toc) { - return null; - } - - return ( - <> - -
- - - - - - -
- {toc.map((entry) => ( - -
- {entry.value} -
-
- ))} -
-
-
-
-
- - ); -}; From 6a0bba2acbe5f0344f2cc2b6fa08a61bc1f6739d Mon Sep 17 00:00:00 2001 From: Atsuki Hasegawa <58581243+atsuki44@users.noreply.github.com> Date: Fri, 21 Mar 2025 15:02:11 +0900 Subject: [PATCH 28/39] fix: enalbe -y to install initial morph components --- core/morph/task/new.py | 1 + 1 file changed, 1 insertion(+) diff --git a/core/morph/task/new.py b/core/morph/task/new.py index 77f43d8..711c466 100644 --- a/core/morph/task/new.py +++ b/core/morph/task/new.py @@ -245,6 +245,7 @@ def run(self): "npx", "shadcn@latest", "add", + "--yes", "https://morph-components.vercel.app/r/morph-components.json", ], cwd=self.project_root, From d35293962fd78488438290965a7ef78b6905b48a Mon Sep 17 00:00:00 2001 From: Atsuki Hasegawa <58581243+atsuki44@users.noreply.github.com> Date: Fri, 21 Mar 2025 15:35:47 +0900 Subject: [PATCH 29/39] fix: nested heading in toc --- .../pages/_components/table-of-contents.tsx | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/core/morph/include/starter_template/src/pages/_components/table-of-contents.tsx b/core/morph/include/starter_template/src/pages/_components/table-of-contents.tsx index 4f31532..25ba07c 100644 --- a/core/morph/include/starter_template/src/pages/_components/table-of-contents.tsx +++ b/core/morph/include/starter_template/src/pages/_components/table-of-contents.tsx @@ -23,13 +23,7 @@ export const TableOfContents: React.FC = ({ toc, className }) => { @@ -43,13 +37,7 @@ export const TableOfContents: React.FC = ({ toc, className }) => {
{toc.map((entry) => ( - - {entry.value} - + ))}
@@ -58,3 +46,19 @@ export const TableOfContents: React.FC = ({ toc, className }) => { ); }; + +const Heading = ({ entry }: { entry: Toc[number] }) => { + return ( + <> + + {entry.value} + + {entry.children?.map((child) => ( + + ))} + + ); +}; From b1117124063e72c123ebddf7e58298185c4de356 Mon Sep 17 00:00:00 2001 From: shibatanaoto Date: Fri, 21 Mar 2025 16:16:13 +0900 Subject: [PATCH 30/39] fix initial morph_project.yml --- core/morph/config/project.py | 120 ++++++++++++++++++++++++++++++++++- 1 file changed, 118 insertions(+), 2 deletions(-) diff --git a/core/morph/config/project.py b/core/morph/config/project.py index f977565..ec5bf8f 100644 --- a/core/morph/config/project.py +++ b/core/morph/config/project.py @@ -89,9 +89,125 @@ def save_project(project_root: str, project: MorphProject) -> None: old_config_path = os.path.join(project_root, "morph_project.yaml") if os.path.exists(old_config_path): with open(old_config_path, "w") as f: - yaml.safe_dump(project.model_dump(), f) + f.write(dump_project_yaml(project)) return config_path = os.path.join(project_root, "morph_project.yml") with open(config_path, "w") as f: - yaml.safe_dump(project.model_dump(), f) + f.write(dump_project_yaml(project)) + + +def dump_project_yaml(project: MorphProject) -> str: + source_paths = "\n- ".join([""] + project.source_paths) + + # Default values + build_use_custom_dockerfile = "false" + build_runtime = "" + build_framework = "" + build_package_manager = "" + build_context = "# context: ." + build_args_str = "\n # - ARG_NAME=value\n # - ANOTHER_ARG=value" + deployment_provider = "aws" + deployment_aws_region = "us-east-1" + deployment_aws_memory = "1024" + deployment_aws_timeout = "300" + deployment_aws_concurrency = "1" + deployment_gcp_region = "us-central1" + deployment_gcp_memory = "1Gi" + deployment_gcp_cpu = "1" + deployment_gcp_concurrency = "80" + deployment_gcp_timeout = "300" + + # Set values if build exists + if project.build: + if project.build.use_custom_dockerfile is not None: + build_use_custom_dockerfile = str(project.build.use_custom_dockerfile) + if project.build.runtime: + build_runtime = project.build.runtime or "" + if project.build.framework: + build_framework = project.build.framework or "" + if project.build.package_manager: + build_package_manager = project.build.package_manager or "" + if project.build.context: + build_context = project.build.context or "# context: ." + if project.build.build_args: + build_args_items = [] + for key, value in project.build.build_args.items(): + build_args_items.append(f"{key}={value}") + build_args_str = ( + "\n - ".join([""] + build_args_items) + if build_args_items + else "\n # - ARG_NAME=value\n # - ANOTHER_ARG=value" + ) + else: + # Use default BuildConfig + build_use_custom_dockerfile = "false" + build_runtime = "" + build_framework = "" + build_package_manager = "" + build_context = "# context: ." + build_args_str = "\n # - ARG_NAME=value\n # - ANOTHER_ARG=value" + + # Set values if deployment exists + if project.deployment: + if project.deployment.provider: + deployment_provider = project.deployment.provider or "aws" + if project.deployment.aws: + deployment_aws_region = project.deployment.aws.get("region") or "us-east-1" + deployment_aws_memory = project.deployment.aws.get("memory") or "1024" + deployment_aws_timeout = project.deployment.aws.get("timeout") or "300" + deployment_aws_concurrency = ( + project.deployment.aws.get("concurrency") or "1" + ) + if project.deployment.gcp: + deployment_gcp_region = ( + project.deployment.gcp.get("region") or "us-central1" + ) + deployment_gcp_memory = project.deployment.gcp.get("memory") or "1Gi" + deployment_gcp_cpu = project.deployment.gcp.get("cpu") or "1" + deployment_gcp_concurrency = ( + project.deployment.gcp.get("concurrency") or "80" + ) + deployment_gcp_timeout = project.deployment.gcp.get("timeout") or "300" + else: + # Use default DeploymentConfig + deployment_provider = "aws" + + return f""" +# Cloud Settings +profile: {project.profile} # Defined in the Profile Section in `~/.morph/credentials` +project_id: {project.project_id} + +# Framework Settings +default_connection: {project.default_connection} +source_paths:{source_paths} + +# Build Settings +build: + use_custom_dockerfile: {build_use_custom_dockerfile} + # These settings are required when use_custom_dockerfile is false + # They define the environment in which the project will be built + runtime: {build_runtime} # python3.9, python3.10, python3.11, python3.12 + framework: {build_framework} + package_manager: {build_package_manager} # pip, poetry, uv + # These settings are required when use_custom_dockerfile is true + # They define how the Docker image will be built + context: {build_context if build_context != "# context: ." else "# context: ."} + build_args:{build_args_str} + +# Deployment Settings +deployment: + provider: {deployment_provider} # aws or gcp (default is aws) + # These settings are used only when you want to customize the deployment settings + # aws: + # region: {deployment_aws_region} + # memory: {deployment_aws_memory} + # timeout: {deployment_aws_timeout} + # concurrency: {deployment_aws_concurrency} + # gcp: + # region: {deployment_gcp_region} + # memory: {deployment_gcp_memory} + # cpu: {deployment_gcp_cpu} + # concurrency: {deployment_gcp_concurrency} + # timeout: {deployment_gcp_timeout} +""" From 54bdda572627054972476720edd0c53377701d26 Mon Sep 17 00:00:00 2001 From: shibatanaoto Date: Fri, 21 Mar 2025 16:31:15 +0900 Subject: [PATCH 31/39] fix initial morph_project.yml --- core/morph/config/project.py | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/core/morph/config/project.py b/core/morph/config/project.py index ec5bf8f..b873853 100644 --- a/core/morph/config/project.py +++ b/core/morph/config/project.py @@ -105,7 +105,7 @@ def dump_project_yaml(project: MorphProject) -> str: build_runtime = "" build_framework = "" build_package_manager = "" - build_context = "# context: ." + build_context = "." build_args_str = "\n # - ARG_NAME=value\n # - ANOTHER_ARG=value" deployment_provider = "aws" deployment_aws_region = "us-east-1" @@ -121,7 +121,9 @@ def dump_project_yaml(project: MorphProject) -> str: # Set values if build exists if project.build: if project.build.use_custom_dockerfile is not None: - build_use_custom_dockerfile = str(project.build.use_custom_dockerfile) + build_use_custom_dockerfile = str( + project.build.use_custom_dockerfile + ).lower() if project.build.runtime: build_runtime = project.build.runtime or "" if project.build.framework: @@ -129,24 +131,16 @@ def dump_project_yaml(project: MorphProject) -> str: if project.build.package_manager: build_package_manager = project.build.package_manager or "" if project.build.context: - build_context = project.build.context or "# context: ." + build_context = f"{project.build.context}" or "." if project.build.build_args: build_args_items = [] for key, value in project.build.build_args.items(): build_args_items.append(f"{key}={value}") build_args_str = ( - "\n - ".join([""] + build_args_items) + "\n # - ".join([""] + build_args_items) if build_args_items else "\n # - ARG_NAME=value\n # - ANOTHER_ARG=value" ) - else: - # Use default BuildConfig - build_use_custom_dockerfile = "false" - build_runtime = "" - build_framework = "" - build_package_manager = "" - build_context = "# context: ." - build_args_str = "\n # - ARG_NAME=value\n # - ANOTHER_ARG=value" # Set values if deployment exists if project.deployment: @@ -176,7 +170,7 @@ def dump_project_yaml(project: MorphProject) -> str: return f""" # Cloud Settings profile: {project.profile} # Defined in the Profile Section in `~/.morph/credentials` -project_id: {project.project_id} +project_id: {project.project_id or ""} # Framework Settings default_connection: {project.default_connection} @@ -192,8 +186,8 @@ def dump_project_yaml(project: MorphProject) -> str: package_manager: {build_package_manager} # pip, poetry, uv # These settings are required when use_custom_dockerfile is true # They define how the Docker image will be built - context: {build_context if build_context != "# context: ." else "# context: ."} - build_args:{build_args_str} + # context: {build_context} + # build_args:{build_args_str} # Deployment Settings deployment: From 86622ec2e6f23a2a399220b97bd7b863b86dffb3 Mon Sep 17 00:00:00 2001 From: shibatanaoto Date: Fri, 21 Mar 2025 16:44:58 +0900 Subject: [PATCH 32/39] change dockerfile api call --- core/morph/config/project.py | 2 +- core/morph/task/deploy.py | 13 +++++++++++-- core/morph/task/utils/load_dockerfile.py | 10 +++++++--- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/core/morph/config/project.py b/core/morph/config/project.py index b873853..36d9e01 100644 --- a/core/morph/config/project.py +++ b/core/morph/config/project.py @@ -170,7 +170,7 @@ def dump_project_yaml(project: MorphProject) -> str: return f""" # Cloud Settings profile: {project.profile} # Defined in the Profile Section in `~/.morph/credentials` -project_id: {project.project_id or ""} +project_id: {project.project_id or "null"} # Framework Settings default_connection: {project.default_connection} diff --git a/core/morph/task/deploy.py b/core/morph/task/deploy.py index 8884b3a..8698db6 100644 --- a/core/morph/task/deploy.py +++ b/core/morph/task/deploy.py @@ -56,11 +56,20 @@ def __init__(self, args: Flags): click.echo(click.style(f"Error: {self.dockerfile} not found", fg="red")) sys.exit(1) else: + provider = "aws" + if ( + self.project.deployment is not None + and self.project.deployment.provider is not None + ): + provider = self.project.deployment.provider or "aws" if self.project.build is None: - dockerfile, dockerignore = get_dockerfile_from_api("morph", None, None) + dockerfile, dockerignore = get_dockerfile_from_api( + "morph", provider, None, None + ) else: dockerfile, dockerignore = get_dockerfile_from_api( - self.project.build.framework, + self.project.build.framework or "morph", + provider, self.project.build.package_manager, self.project.build.runtime, ) diff --git a/core/morph/task/utils/load_dockerfile.py b/core/morph/task/utils/load_dockerfile.py index 64c950f..77d8f1f 100644 --- a/core/morph/task/utils/load_dockerfile.py +++ b/core/morph/task/utils/load_dockerfile.py @@ -4,7 +4,8 @@ def get_dockerfile_from_api( - framework: Optional[str] = "morph", + framework: str, + provider: str, package_manager: Optional[str] = None, runtime: Optional[str] = None, ) -> Tuple[str, str]: @@ -13,15 +14,18 @@ def get_dockerfile_from_api( Args: framework: The framework to get the dockerfile for + provider: The provider to get the dockerfile for package_manager: Optional package manager to use - language_version: Optional language version to use + runtime: Optional runtime to use Returns: Tuple containing (dockerfile, dockerignore) """ url = f"https://backend-api-public.morph-cb9.workers.dev/dockerfile/{framework}" - params: Dict[str, Any] = {} + params: Dict[str, Any] = { + "provider": provider, + } if package_manager: params["packageManager"] = package_manager if runtime: From f446d2359fbe9341099b84c12da626db8beb9a30 Mon Sep 17 00:00:00 2001 From: Atsuki Hasegawa <58581243+atsuki44@users.noreply.github.com> Date: Fri, 21 Mar 2025 16:48:38 +0900 Subject: [PATCH 33/39] =?UTF-8?q?fix:=20404.mdx=20=E2=86=92=20404.tsx?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/morph/include/starter_template/src/pages/404.mdx | 3 --- core/morph/include/starter_template/src/pages/404.tsx | 8 ++++++++ 2 files changed, 8 insertions(+), 3 deletions(-) delete mode 100644 core/morph/include/starter_template/src/pages/404.mdx create mode 100644 core/morph/include/starter_template/src/pages/404.tsx diff --git a/core/morph/include/starter_template/src/pages/404.mdx b/core/morph/include/starter_template/src/pages/404.mdx deleted file mode 100644 index 04d6977..0000000 --- a/core/morph/include/starter_template/src/pages/404.mdx +++ /dev/null @@ -1,3 +0,0 @@ -# 404 - -Page not found diff --git a/core/morph/include/starter_template/src/pages/404.tsx b/core/morph/include/starter_template/src/pages/404.tsx new file mode 100644 index 0000000..4a669ab --- /dev/null +++ b/core/morph/include/starter_template/src/pages/404.tsx @@ -0,0 +1,8 @@ +export default function NotFound() { + return ( + <> +

404

+

Page not found

+ + ); +} From c3a49fe931f14e6a905dba54711a3cab321ee37a Mon Sep 17 00:00:00 2001 From: shibatanaoto Date: Fri, 21 Mar 2025 16:49:31 +0900 Subject: [PATCH 34/39] issue v0.3.0rc2 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9c1bd08..e5fa843 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "morph-data" -version = "0.3.0rc1" +version = "0.3.0rc2" description = "Morph is a python-centric full-stack framework for building and deploying data apps." authors = ["Morph "] packages = [ From dd7f1831bb9d0e5c605e4acc7b2542a761724cc5 Mon Sep 17 00:00:00 2001 From: shibatanaoto Date: Sat, 22 Mar 2025 14:59:49 +0900 Subject: [PATCH 35/39] change logic for checking use_custom_dockerfile --- core/morph/config/project.py | 11 ++--------- core/morph/task/deploy.py | 13 +++++-------- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/core/morph/config/project.py b/core/morph/config/project.py index 36d9e01..baffd98 100644 --- a/core/morph/config/project.py +++ b/core/morph/config/project.py @@ -14,7 +14,6 @@ class BuildConfig(BaseModel): - use_custom_dockerfile: bool = False runtime: Optional[str] = None framework: Optional[str] = "morph" package_manager: Optional[str] = None @@ -101,7 +100,6 @@ def dump_project_yaml(project: MorphProject) -> str: source_paths = "\n- ".join([""] + project.source_paths) # Default values - build_use_custom_dockerfile = "false" build_runtime = "" build_framework = "" build_package_manager = "" @@ -120,10 +118,6 @@ def dump_project_yaml(project: MorphProject) -> str: # Set values if build exists if project.build: - if project.build.use_custom_dockerfile is not None: - build_use_custom_dockerfile = str( - project.build.use_custom_dockerfile - ).lower() if project.build.runtime: build_runtime = project.build.runtime or "" if project.build.framework: @@ -178,13 +172,12 @@ def dump_project_yaml(project: MorphProject) -> str: # Build Settings build: - use_custom_dockerfile: {build_use_custom_dockerfile} - # These settings are required when use_custom_dockerfile is false + # These settings are required when there is no Dockerfile in the project root. # They define the environment in which the project will be built runtime: {build_runtime} # python3.9, python3.10, python3.11, python3.12 framework: {build_framework} package_manager: {build_package_manager} # pip, poetry, uv - # These settings are required when use_custom_dockerfile is true + # These settings are required when there is a Dockerfile in the project root. # They define how the Docker image will be built # context: {build_context} # build_args:{build_args_str} diff --git a/core/morph/task/deploy.py b/core/morph/task/deploy.py index 8698db6..3996ff2 100644 --- a/core/morph/task/deploy.py +++ b/core/morph/task/deploy.py @@ -50,12 +50,9 @@ def __init__(self, args: Flags): self.package_manager = self.project.package_manager # Check Dockerfile existence - self.dockerfile = os.path.join(self.project_root, "Dockerfile") - if self.project.build is not None and self.project.build.use_custom_dockerfile: - if not os.path.exists(self.dockerfile): - click.echo(click.style(f"Error: {self.dockerfile} not found", fg="red")) - sys.exit(1) - else: + self.dockerfile_path = os.path.join(self.project_root, "Dockerfile") + self.use_custom_dockerfile = os.path.exists(self.dockerfile_path) + if self.use_custom_dockerfile: provider = "aws" if ( self.project.deployment is not None @@ -73,7 +70,7 @@ def __init__(self, args: Flags): self.project.build.package_manager, self.project.build.runtime, ) - with open(self.dockerfile, "w") as f: + with open(self.dockerfile_path, "w") as f: f.write(dockerfile) dockerignore_path = os.path.join(self.project_root, ".dockerignore") with open(dockerignore_path, "w") as f: @@ -440,7 +437,7 @@ def _build_docker_image(self) -> str: "-t", self.image_name, "-f", - self.dockerfile, + self.dockerfile_path, self.project_root, ] if self.no_cache: From b508198c9b564691e5197cbaedadeb19daf52aa1 Mon Sep 17 00:00:00 2001 From: shibatanaoto Date: Sat, 22 Mar 2025 15:14:45 +0900 Subject: [PATCH 36/39] change initial morph_project.yml --- core/morph/config/project.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/core/morph/config/project.py b/core/morph/config/project.py index baffd98..f600294 100644 --- a/core/morph/config/project.py +++ b/core/morph/config/project.py @@ -162,14 +162,16 @@ def dump_project_yaml(project: MorphProject) -> str: deployment_provider = "aws" return f""" -# Cloud Settings -profile: {project.profile} # Defined in the Profile Section in `~/.morph/credentials` -project_id: {project.project_id or "null"} +version: 1 # Framework Settings default_connection: {project.default_connection} source_paths:{source_paths} +# Cloud Settings +# profile: {project.profile} # Defined in the Profile Section in `~/.morph/credentials` +# project_id: {project.project_id or "null"} + # Build Settings build: # These settings are required when there is no Dockerfile in the project root. From 0552e288a436ebeb40ebabf957e8c1100e3a0280 Mon Sep 17 00:00:00 2001 From: shibatanaoto Date: Sat, 22 Mar 2025 15:20:18 +0900 Subject: [PATCH 37/39] fix version value as string --- core/morph/config/project.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/morph/config/project.py b/core/morph/config/project.py index f600294..7e6b914 100644 --- a/core/morph/config/project.py +++ b/core/morph/config/project.py @@ -162,7 +162,7 @@ def dump_project_yaml(project: MorphProject) -> str: deployment_provider = "aws" return f""" -version: 1 +version: '1' # Framework Settings default_connection: {project.default_connection} From 521044bb1176020c41db9e8811fe7d3692d8dfe2 Mon Sep 17 00:00:00 2001 From: shibatanaoto Date: Sun, 23 Mar 2025 14:37:37 +0900 Subject: [PATCH 38/39] update version as v0.3.0rc3 --- core/morph/task/utils/load_dockerfile.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/morph/task/utils/load_dockerfile.py b/core/morph/task/utils/load_dockerfile.py index 77d8f1f..cfbbddb 100644 --- a/core/morph/task/utils/load_dockerfile.py +++ b/core/morph/task/utils/load_dockerfile.py @@ -21,7 +21,7 @@ def get_dockerfile_from_api( Returns: Tuple containing (dockerfile, dockerignore) """ - url = f"https://backend-api-public.morph-cb9.workers.dev/dockerfile/{framework}" + url = f"https://dockerfile-template.morph-cb9.workers.dev/dockerfile/{framework}" params: Dict[str, Any] = { "provider": provider, diff --git a/pyproject.toml b/pyproject.toml index e5fa843..7905855 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "morph-data" -version = "0.3.0rc2" +version = "0.3.0rc3" description = "Morph is a python-centric full-stack framework for building and deploying data apps." authors = ["Morph "] packages = [ From 52e1ceb103e8531a9e505d1044f24454345714d6 Mon Sep 17 00:00:00 2001 From: shibatanaoto Date: Mon, 24 Mar 2025 12:35:13 +0900 Subject: [PATCH 39/39] update version as v0.3.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7905855..a9a39d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "morph-data" -version = "0.3.0rc3" +version = "0.3.0" description = "Morph is a python-centric full-stack framework for building and deploying data apps." authors = ["Morph "] packages = [