Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .ocamlformat
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
profile = default
let-binding-spacing=double-semicolon
break-infix-before-func=true
version = 0.26.1
version = 0.26.2
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,16 @@ be coming soon! In the mean time, please check in at
[The Caravan Discord](https://discord.gg/fNvVdsUWHE) for
help getting started working on `create-melange-app`.

<h3 id="run-locally">Run the CLI locally</h3>

```bash
npm install
opam install . --dps-only
dune build

node ./build/src/cli.mjs
```

<div align="center">
<a href="https://github.com/dmmulroy/create-melange-app/graphs/contributors">
<img src="https://contrib.rocks/image?repo=dmmulroy/create-melange-app" />
Expand Down
1 change: 1 addition & 0 deletions create_melange_app.opam
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ depends: [
"reason-react" {>= "0.15.0"}
"reason-react-ppx"
"ppx_deriving"
"ocamlformat" {= "0.26.2" & with-dev-setup}
"odoc" {with-doc}
]
build: [
Expand Down
6 changes: 5 additions & 1 deletion dune-project
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@
(reason-react
(>= 0.15.0))
reason-react-ppx
ppx_deriving)
ppx_deriving
(ocamlformat
(and
(= 0.26.2)
:with-dev-setup)))
(tags
(cli, react, reasonml, ocaml, melange)))
3 changes: 1 addition & 2 deletions src/core/app_module.ml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ let template (configuration : Configuration.t) =
in
let name =
match (configuration.syntax_preference, configuration.is_react_app) with
| `ReasonML, true
| `ReasonML, false -> "App.re.tmpl"
| `ReasonML, true | `ReasonML, false -> "App.re.tmpl"
| `OCaml, true -> "App.mlx.tmpl"
| `OCaml, false -> "App.ml.tmpl"
in
Expand Down
84 changes: 84 additions & 0 deletions src/core/backend_files.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
open Bindings

type backend_options = {
project_directory : string;
backend_framework : Configuration.backend_framework option;
project_name : string;
}

module Copy :
Process.S with type input = backend_options and type output = unit = struct
type input = backend_options
type output = unit

let name = "copy backend files"

let base_path =
Node.Path.join
[|
Nodejs.Util.__dirname [%mel.raw "import.meta.url"];
"..";
"templates";
"extensions";
"backend";
"dream";
|]
;;

let error_message =
{|
Failed to copy backend files to project directory

The scaffolding process failed while copying backend files. Please try
running `create-melange-app` again and choose to `Clear` the project
directory created by this run.

If the problem persists, please open an issue at
github.com/dmmulroy/create-melange-app/issues, and or join our discord for
help at https://discord.gg/fNvVdsUWHE.
|}
;;

let exec (input : input) =
match input.backend_framework with
| Some `Dream ->
let open Promise_result.Syntax.Let in
let backend_dest =
Node.Path.join [| input.project_directory; "backend" |]
in
let bin_dest = Node.Path.join [| backend_dest; "bin" |] in
let+ _ =
Fs_extra.ensureDir backend_dest |> Promise_result.of_js_promise
in
let+ _ = Fs_extra.ensureDir bin_dest |> Promise_result.of_js_promise in

let+ _ =
Fs_extra.copy
(Node.Path.join [| base_path; "bin"; "main.ml.tmpl" |])
(Node.Path.join [| bin_dest; "main.ml.tmpl" |])
|> Promise_result.of_js_promise
in
let+ _ =
Fs_extra.copy
(Node.Path.join [| base_path; "bin"; "dune.tmpl" |])
(Node.Path.join [| bin_dest; "dune.tmpl" |])
|> Promise_result.of_js_promise
in
let+ _ =
Fs_extra.copy
(Node.Path.join [| base_path; "dune.tmpl" |])
(Node.Path.join [| backend_dest; "dune.tmpl" |])
|> Promise_result.of_js_promise
in
let+ _ =
Fs_extra.copy
(Node.Path.join [| base_path; "README.md.tmpl" |])
(Node.Path.join [| backend_dest; "README.md.tmpl" |])
|> Promise_result.of_js_promise
in

Promise_result.resolve_ok ()
|> Promise_result.log_and_map_error (Fun.const error_message)
| None -> Promise_result.resolve_ok ()
;;
end
89 changes: 83 additions & 6 deletions src/core/configuration.ml
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,47 @@ let syntax_preference_of_string str =
| _ -> failwith "Invalid syntax preference"
;;

type project_type = [ `Frontend | `Fullstack ]

let project_type_to_string = function
| `Frontend -> "Frontend"
| `Fullstack -> "Fullstack"
;;

let project_type_of_string str =
str |> String.lowercase_ascii
|> function
| "frontend" -> `Frontend
| "fullstack" -> `Fullstack
| _ -> failwith "Invalid project type"
;;

type backend_framework = [ `Dream ]

let backend_framework_to_string = function `Dream -> "Dream"

let backend_framework_of_string str =
str |> String.lowercase_ascii
|> function "dream" -> `Dream | _ -> failwith "Invalid backend framework"
;;

type api_features = [ `REST ]

let api_features_to_string = function `REST -> "REST"

let api_features_of_string str =
str |> String.lowercase_ascii
|> function "rest" -> `REST | _ -> failwith "Invalid API features"
;;

type t = {
name : string;
directory : string;
node_package_manager : Nodejs.Process.npm_user_agent;
syntax_preference : syntax_preference;
project_type : project_type;
backend_framework : backend_framework option;
api_features : api_features option;
bundler : Bundler.t;
is_react_app : bool;
has_tests : bool;
Expand All @@ -36,13 +72,18 @@ type t = {
overwrite : overwrite_preference option;
}

let make ~name ~directory ~syntax_preference ~bundler ~is_react_app ~has_tests
~initialize_git ~initialize_npm ~initialize_ocaml_toolchain ~overwrite =
let make ~name ~directory ~syntax_preference ~project_type ~bundler
~is_react_app ~has_tests ~initialize_git ~initialize_npm
~initialize_ocaml_toolchain ~overwrite ?(backend_framework = None)
?(api_features = None) () =
{
name;
directory;
node_package_manager = Nodejs.Process.npm_config_user_agent;
syntax_preference;
project_type;
backend_framework;
api_features;
bundler;
is_react_app;
has_tests;
Expand All @@ -54,18 +95,35 @@ let make ~name ~directory ~syntax_preference ~bundler ~is_react_app ~has_tests
;;

let set_overwrite overwrite config = { config with overwrite = Some overwrite }
let is_fullstack config = config.project_type = `Fullstack
let is_frontend_only config = config.project_type = `Frontend

let to_string config =
let backend_info =
match config.project_type with
| `Frontend -> ""
| `Fullstack ->
Printf.sprintf "Backend framework: %s\nAPI features: %s\n"
(config.backend_framework
|> Option.map backend_framework_to_string
|> Option.value ~default:"None")
(config.api_features
|> Option.map api_features_to_string
|> Option.value ~default:"None")
in
Printf.sprintf
"Name: %s\n\
Directory: %s\n\
Syntax preference: %s\n\
Bunder: %s\n\
Project type: %s\n\
%sSyntax preference: %s\n\
Bundler: %s\n\
is_react_app: %b\n\
Initialize git: %b\n\
Initialize npm: %b\n\
Initialize OCaml toolchain: %b\n"
config.name config.directory
(project_type_to_string config.project_type)
backend_info
(syntax_preference_to_string config.syntax_preference)
(Bundler.to_string config.bundler)
config.is_react_app config.initialize_git config.initialize_npm
Expand All @@ -83,6 +141,18 @@ let to_json (configuration : t) =
Js.Dict.set dict "syntax_preference"
(Js.Json.string
(syntax_preference_to_string configuration.syntax_preference));
Js.Dict.set dict "project_type"
(Js.Json.string (project_type_to_string configuration.project_type));
(match configuration.backend_framework with
| Some framework ->
Js.Dict.set dict "backend_framework"
(Js.Json.string (backend_framework_to_string framework))
| None -> ());
(match configuration.api_features with
| Some features ->
Js.Dict.set dict "api_features"
(Js.Json.string (api_features_to_string features))
| None -> ());
Js.Dict.set dict "bundler"
(Js.Json.string
(Bundler.to_string configuration.bundler |> String.capitalize_ascii));
Expand All @@ -106,6 +176,9 @@ type partial = {
name : string option;
directory : string option;
syntax_preference : syntax_preference option;
project_type : project_type option;
backend_framework : backend_framework option;
api_features : api_features option;
bundler : Bundler.t option;
is_react_app : bool option;
has_tests : bool option;
Expand All @@ -114,12 +187,16 @@ type partial = {
initialize_ocaml_toolchain : bool option;
}

let make_partial ?name ?directory ?syntax_preference ?bundler ?is_react_app
?has_tests ?initialize_git ?initialize_npm ?initialize_ocaml_toolchain () =
let make_partial ?name ?directory ?syntax_preference ?project_type
?backend_framework ?api_features ?bundler ?is_react_app ?has_tests
?initialize_git ?initialize_npm ?initialize_ocaml_toolchain () =
{
name;
directory;
syntax_preference;
project_type;
backend_framework;
api_features;
bundler;
is_react_app;
has_tests;
Expand Down
21 changes: 19 additions & 2 deletions src/core/dune.ml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ module Dune_project = struct
let make ?version name = { name; version }
end

let backend_dependencies =
[ Dependency.make "dream"; Dependency.make "lwt"; Dependency.make "yojson" ]
;;

let default_dependencies =
[
Dependency.make ~version:">= 5.1.1" "ocaml";
Expand Down Expand Up @@ -149,10 +153,23 @@ module Dune_project = struct
Js.Json.object_ dict
;;

let template ~project_name ~project_directory ~is_mlx =
let make_with_backend_deps ~name ~is_fullstack =
let base_deps : Dependency.t String_map.t = default_dependencies in
let deps =
if is_fullstack then
List.fold_left
(fun acc (dep : Dependency.t) -> String_map.add dep.name dep acc)
base_deps backend_dependencies
else base_deps
in
{ name; depends = deps; is_mlx = false }
;;

let template ~project_name ~project_directory ~is_mlx:_
?(is_fullstack = false) () =
let template_directory = Node.Path.join [| project_directory; "./" |] in
Template.make ~name:"dune-project.tmpl"
~value:{ empty with name = project_name; is_mlx }
~value:(make_with_backend_deps ~name:project_name ~is_fullstack)
~dir:template_directory ~to_json
;;
end
Expand Down
42 changes: 42 additions & 0 deletions src/core/engine.ml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ let extend_dune_project_with_app_settings ~(is_react_app : bool)
(Dune.Dune_project.add_dependencies React.Dune_project.dependencies)
;;

let copy_backend_files ~backend_framework ~project_name project_directory =
let open Backend_files in
Copy.exec { project_directory; backend_framework; project_name }
;;

let copy_test_files ~syntax_preference ~is_react_app project_directory =
let open Test_files in
Copy.exec { project_directory; syntax_preference; is_react_app }
Expand All @@ -118,6 +123,43 @@ let extend_dune_project_with_tests ~(is_react_app : bool)
(if is_react_app then Fest.Dune_project.react_dependencies else []))
;;

let compile_backend_templates ~project_name ~project_directory =
let open Promise_result.Syntax.Let in
let backend_data = Js.Dict.empty () in
Js.Dict.set backend_data "name" (Js.Json.string project_name);
let backend_json = Js.Json.object_ backend_data in

let main_ml_template =
Template.make ~name:"main.ml.tmpl" ~value:backend_json
~dir:(Node.Path.join [| project_directory; "backend"; "bin" |])
~to_json:(fun x -> x)
in
let+ _ = Template.compile main_ml_template in

let bin_dune_template =
Template.make ~name:"dune.tmpl" ~value:backend_json
~dir:(Node.Path.join [| project_directory; "backend"; "bin" |])
~to_json:(fun x -> x)
in
let+ _ = Template.compile bin_dune_template in

let backend_dune_template =
Template.make ~name:"dune.tmpl" ~value:backend_json
~dir:(Node.Path.join [| project_directory; "backend" |])
~to_json:(fun x -> x)
in
let+ _ = Template.compile backend_dune_template in

let readme_template =
Template.make ~name:"README.md.tmpl" ~value:backend_json
~dir:(Node.Path.join [| project_directory; "backend" |])
~to_json:(fun x -> x)
in
let+ _ = Template.compile readme_template in

Promise_result.resolve_ok ()
;;

let compile = Template.compile
let node_pkg_manager_install = Npm.Install.exec
let copy_git_ignore = Git_scm.Copy_gitignore.exec
Expand Down
2 changes: 1 addition & 1 deletion src/core/react.ml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ module Dune_project = struct
Dependency.make "reason-react-ppx";
]
;;

let mlx_dependencies =
[
Dependency.make "mlx";
Expand Down
Loading