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
-
-
-
-
- | Key |
- Value |
-
-
-
- {Object.entries(job.variables).map(([key, value]) => (
-
- | {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.
-
-
Trouble shoot
-
- Newly added files are not displayed.
-
-
You need to re-start mdx dev server.
-
- - 1. Close preview window on code editor
- -
- 2. Stop process with putting{" "}
-
- Ctrl + C
- {" "}
- to the terminal
-
- -
- 3. Open preview window on code editor again
-
-
-
-
-
- );
-};
-
-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
-
-
-
-
-
-
-
-
{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 (
+
+ );
+}
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
+
+
+
+
+);
+
+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 (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
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 (
-
- );
-}
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
+
+
+
+
+);
+
+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 (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
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 }) => {
@@ -43,20 +41,17 @@ 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
-
-
-
-
-);
-
-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 (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
-
- >
- );
-};
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 }) => {
@@ -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 = [