Skip to content

Instantly share code, notes, and snippets.

@AngelMunoz
Created October 12, 2022 15:00
Show Gist options
  • Save AngelMunoz/bfb1531ffb8a98abe10c98b06bb77dea to your computer and use it in GitHub Desktop.
Save AngelMunoz/bfb1531ffb8a98abe10c98b06bb77dea to your computer and use it in GitHub Desktop.
module Commands =
open FSharp.SystemCommandLine
open FSharp.SystemCommandLine.Aliases
type RunConfiguration with
static member FromString(value: string) =
match value.ToLowerInvariant() with
| "production"
| "prod" -> Configuration.Production
| "development"
| "dev"
| _ -> Configuration.Development
type Init with
static member FromString(value: string) =
match value.ToLowerInvariant() with
| "full" -> Init.Full
| "simple"
| _ -> Init.Simple
[<AutoOpen>]
module Inputs =
open System.CommandLine
type Input with
static member OptionWithStrings
(
aliases: string seq,
values: string seq,
defaultValue: string,
?description
) =
Opt<string>(
aliases |> Array.ofSeq,
getDefaultValue = (fun _ -> defaultValue),
?description = description
)
.FromAmong(values |> Array.ofSeq)
|> HandlerInput.OfOption
static member ArgumentWithStrings
(
name: string,
defaultValue: string,
values: string seq,
?description
) =
Arg<string>(
name,
getDefaultValue = (fun _ -> defaultValue),
?description = description
)
.FromAmong(values |> Array.ofSeq)
|> HandlerInput.OfArgument
let modeArg =
Input.ArgumentWithStrings(
"mode",
"dev",
[ "development"; "dev"; "prod"; "production" ],
"Use Dev or Production dependencies when running, defaults to development"
)
let serveCmd =
let port =
Input.OptionMaybe<int>(
[ "--port"; "-p" ],
"Port where the application starts"
)
let host =
Input.OptionMaybe<string>(
[ "--host" ],
"network ip address where the application will run"
)
let ssl = Input.OptionMaybe<bool>([ "--ssl" ], "Run dev server with SSL")
let buildArgs
(
mode: string,
port: int option,
host: string option,
ssl: bool option
) : ServeOptions =
{ mode = Configuration.FromString mode
port = port |> Option.defaultValue 7331
host = host |> Option.defaultValue "127.0.0.1"
ssl = ssl |> Option.defaultValue false }
command "serve" {
description "Starts a development server for single page applications"
inputs (modeArg, port, host, ssl)
setHandler (buildArgs >> Handlers.handleServe)
}
let buildCmd =
let buildArgs (mode: string) : BuildOptions =
{ mode = Configuration.FromString mode }
command "build" {
description "Builds the SPA application for distribution"
inputs modeArg
setHandler (buildArgs >> Handlers.runBuild)
}
let initCmd =
let mode =
Input.ArgumentWithStrings(
"mode",
"simple",
[ "simple"; "full" ],
"Selects if we are initializing a project, or perla itself"
)
let path =
Input.ArgumentMaybe<System.IO.DirectoryInfo>(
"path",
"Choose what directory to initialize"
)
let yes = Input.ArgumentMaybe<bool>("yes", "Accept all of the prompts")
let fable =
Input.OptionMaybe<bool>(
[ "--with-fable"; "-wf" ],
"The project will use fable"
)
let buildArgs
(
mode: string,
path: System.IO.DirectoryInfo option,
yes: bool option,
fable: bool option
) : InitOptions =
{ mode = Init.FromString mode
path =
path
|> Option.map (fun d -> d.FullName)
|> Option.defaultWith (fun _ -> System.IO.Path.GetFullPath "./")
yes = yes |> Option.defaultValue false
useFable = fable |> Option.defaultValue false }
command "init" {
description "Initialized a given directory or perla itself"
inputs (mode, path, yes, fable)
setHandler (buildArgs >> Handlers.runInit)
}
let searchPackagesCmd =
let package =
Input.Argument(
"package",
"The package you want to search for in the skypack api"
)
let page =
Input.ArgumentMaybe(
"page",
"change the page number in the search results"
)
let buildArgs (package: string, page: int option) : SearchOptions =
{ package = package
page = page |> Option.defaultValue 1 }
command "search" {
description
"Search a pacakge name in the skypack api, this will bring potential results"
inputs (package, page)
setHandler (buildArgs >> Handlers.runSearch)
}
let showPackageCmd =
let package =
Input.Argument(
"package",
"The package you want to search for in the skypack api"
)
let buildArgs (package: string) : ShowPackageOptions = { package = package }
command "show" {
description
"Shows information about a package if the name matches an existing one"
inputs package
setHandler (buildArgs >> Handlers.runShow)
}
let removePackageCmd =
let package =
Input.Argument(
"package",
"The package you want to search for in the skypack api"
)
let alias =
Input.OptionMaybe(
[ "--alias"; "-a" ],
"the alias of the package if you added one"
)
let buildArgs
(
package: string,
alias: string option
) : RemovePackageOptions =
{ package = package; alias = alias }
command "remove" {
description "removes a package from the "
inputs (package, alias)
setHandler (buildArgs >> Handlers.runRemove)
}
let addPacakgeCmd =
let package =
Input.Argument(
"package",
"The package you want to search for in the skypack api"
)
let version =
Input.ArgumentMaybe(
"version",
"The version of the package you want to use, it defaults to latest"
)
let source =
Input.OptionWithStrings(
[ "--source"; "-s" ],
[ "jspm"; "skypack"; "unpkg"; "jsdelivr"; "jspm.system" ],
"jspm",
"CDN that will be used to fetch dependencies from"
)
let dev =
Input.OptionMaybe(
[ "--dev"; "--development"; "-d" ],
"Adds this dependency to the dev dependencies"
)
let alias =
Input.OptionMaybe(
[ "--alias"; "-a" ],
"the alias of the package if you added one"
)
let buildArgs
(
package: string,
version: string option,
source: string,
dev: bool option,
alias: string option
) : AddPackageOptions =
{ package = package
version = version |> Option.defaultValue "latest"
source = Provider.FromString source
mode =
dev
|> Option.map (fun dev ->
if dev then
Configuration.Development
else
Configuration.Production)
|> Option.defaultValue Configuration.Production
alias = alias }
command "add" {
description
"Shows information about a package if the name matches an existing one"
inputs (package, version, source, dev, alias)
setHandler (buildArgs >> Handlers.runAdd)
}
let listCmd =
let asNpm =
Input.OptionMaybe(
[ "--npm"; "--as-package-json"; "-j" ],
"Show the packages simlar to npm's package.json"
)
let buildArgs (asNpm: bool option) : ListPackagesOptions =
{ format =
asNpm
|> Option.map (fun asNpm ->
if asNpm then
ListFormat.PackageJson
else
ListFormat.HumanReadable)
|> Option.defaultValue ListFormat.HumanReadable }
command "list" {
description
"Lists the current dependencies in a table or an npm style json string"
inputs asNpm
setHandler (buildArgs >> Handlers.runList)
}
let restoreCmd =
let source =
Input.OptionWithStrings(
[ "--source"; "-s" ],
[ "jspm"; "skypack"; "unpkg"; "jsdelivr"; "jspm.system" ],
"jspm",
"CDN that will be used to fetch dependencies from"
)
let mode =
Input.OptionWithStrings(
[ "--mode"; "-m" ],
[ "dev"; "development"; "prod"; "production" ],
"production",
"Restore Dependencies based on the mode to run"
)
let buildArgs (source: string, mode: string) : RestoreOptions =
{ source = Provider.FromString source
mode = Configuration.FromString mode }
command "restore" {
description
"Restore the import map based on the selected mode, defaults to production"
inputs (source, mode)
setHandler (buildArgs >> Handlers.runRestore)
}
let addTemplateCmd, updateTemplateCmd =
let repoName =
Input.Argument(
"templateRepositoryName",
"The User/repository name combination"
)
let branch =
Input.Argument("banch", "Whch branch to pick the template from")
let yes = Input.OptionMaybe([ "--yes"; "--continue"; "-y" ], "skip prompts")
let buildArgs
(
name: string,
branch: string,
yes: bool option
) : TemplateRepositoryOptions =
{ fullRepositoryName = name
branch = branch
yes = yes |> Option.defaultValue false }
let add =
command "templates:add" {
description "Adds a new template from a particular repository"
inputs (repoName, branch, yes)
setHandler (buildArgs >> Handlers.runAddTemplate)
}
let update =
command "templates:update" {
description "Updates an existing template in the templates database"
inputs (repoName, branch, yes)
setHandler (buildArgs >> Handlers.runUpdateTemplate)
}
add, update
let listTemplatesCmd =
let display =
Input.ArgumentWithStrings("format", "table", [ "table"; "simple" ])
command "templates:list" {
inputs display
setHandler Handlers.runListTemplates
}
let removeTemplateCmd =
let repoName =
Input.Argument(
"templateRepositoryName",
"The User/repository name combination"
)
command "templates:delete" {
description "Removes a template from the templates database"
inputs (repoName)
setHandler Handlers.runRemoveTemplate
}
let newProjectCmd =
let name = Input.Argument("name", "Name of the new project")
let templateName =
Input.Argument(
"templateName",
"repository/directory combination of the template name, or the full name in case of name conflicts username/repository/directory"
)
let buildArgs (name: string, template: string) : ProjectOptions =
{ projectName = name
templateName = template }
command "new" {
description
"Creates a new project based on the selected template if it exists"
inputs (name, templateName)
setHandler (buildArgs >> Handlers.runNew)
}
let versionCmd =
let isSemver =
Input.OptionMaybe([ "--semver" ], "Gets the version of the application")
command "--version" {
description "Shows the full or semver version of the application"
inputs isSemver
setHandler (
(fun isSemver -> isSemver |> Option.defaultValue true)
>> Handlers.runVersion
)
}
// Learn more about F# at http://docs.microsoft.com/dotnet/fsharp
open System.Threading.Tasks
open System.CommandLine
open FSharp.SystemCommandLine
open Perla.Commands
[<EntryPoint>]
let main argv =
let isInteractive =
Input.OptionMaybe(
[ "-i"; "--interactive" ],
"Starts the server in interactive mode"
)
let rootHandler (isInteractive: bool option) = task { return 0 }
rootCommand argv {
description "The Perla Dev Server!"
inputs isInteractive
setHandler rootHandler
addCommands
[ serveCmd
buildCmd
initCmd
searchPackagesCmd
showPackageCmd
removePackageCmd
addPacakgeCmd
listCmd
restoreCmd
addTemplateCmd
updateTemplateCmd
listTemplatesCmd
removeTemplateCmd
newProjectCmd
versionCmd ]
}
|> Async.AwaitTask
|> Async.RunSynchronously
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment