iris/plugin/typescript/typescript.go

301 lines
8.1 KiB
Go

package typescript
import (
"errors"
"os"
"path/filepath"
"strings"
"github.com/iris-contrib/npm"
"github.com/kataras/iris"
"github.com/kataras/iris/config"
"github.com/kataras/iris/logger"
"github.com/kataras/iris/plugin/editor"
"github.com/kataras/iris/utils"
)
/* Notes
The editor is working when the typescript plugin finds a typescript project (tsconfig.json),
also working only if one typescript project found (normaly is one for client-side).
*/
// Name the name of the plugin, is "TypescriptPlugin"
const Name = "TypescriptPlugin"
var nodeModules = utils.PathSeparator + "node_modules" + utils.PathSeparator
type (
// Options the struct which holds the TypescriptPlugin options
// Has five (5) fields
//
// 1. Bin: string, the typescript installation directory/typescript/lib/tsc.js, if empty it will search inside global npm modules
// 2. Dir: string, Dir set the root, where to search for typescript files/project. Default "./"
// 3. Ignore: string, comma separated ignore typescript files/project from these directories. Default "" (node_modules are always ignored)
// 4. Tsconfig: &typescript.Tsconfig{}, here you can set all compilerOptions if no tsconfig.json exists inside the 'Dir'
// 5. Editor: typescript.Editor("username","password"), if setted then alm-tools browser-based typescript IDE will be available. Defailt is nil
Options struct {
Bin string
Dir string
Ignore string
Tsconfig *Tsconfig
Editor *editor.Plugin // the editor is just a plugin also
}
// Plugin the struct of the Typescript Plugin, holds all necessary fields & methods
Plugin struct {
options Options
// taken from Activate
pluginContainer iris.PluginContainer
// taken at the PreListen
logger *logger.Logger
}
)
// Editor is just a shortcut for github.com/kataras/iris/plugin/editor.New()
// returns a new (Editor)Plugin, it's exists here because the typescript plugin has direct interest with the EditorPlugin
func Editor(username, password string) *editor.Plugin {
editorCfg := config.DefaultEditor()
editorCfg.Username = username
editorCfg.Password = password
return editor.New(editorCfg)
}
// DefaultOptions returns the default Options of the Plugin
func DefaultOptions() Options {
root, err := os.Getwd()
if err != nil {
panic("Typescript Plugin: Cannot get the Current Working Directory !!! [os.getwd()]")
}
opt := Options{Dir: root + utils.PathSeparator, Ignore: nodeModules, Tsconfig: DefaultTsconfig()}
opt.Bin = npm.Abs("typescript/lib/tsc.js")
return opt
}
// Plugin
// New creates & returns a new instnace typescript plugin
func New(_opt ...Options) *Plugin {
var options = DefaultOptions()
if _opt != nil && len(_opt) > 0 { //not nil always but I like this way :)
opt := _opt[0]
if opt.Bin != "" {
options.Bin = opt.Bin
}
if opt.Dir != "" {
options.Dir = opt.Dir
}
if !strings.Contains(opt.Ignore, nodeModules) {
opt.Ignore += "," + nodeModules
}
if opt.Tsconfig != nil {
options.Tsconfig = opt.Tsconfig
}
options.Ignore = opt.Ignore
}
return &Plugin{options: options}
}
// implement the IPlugin & IPluginPreListen
// Activate ...
func (t *Plugin) Activate(container iris.PluginContainer) error {
t.pluginContainer = container
return nil
}
// GetName ...
func (t *Plugin) GetName() string {
return Name + "[" + utils.RandomString(10) + "]" // this allows the specific plugin to be registed more than one time
}
// GetDescription TypescriptPlugin scans and compile typescript files with ease
func (t *Plugin) GetDescription() string {
return Name + " scans and compile typescript files with ease. \n"
}
// PreListen ...
func (t *Plugin) PreListen(s *iris.Framework) {
t.logger = s.Logger
t.start()
}
//
// implementation
func (t *Plugin) start() {
defaultCompilerArgs := t.options.Tsconfig.CompilerArgs() //these will be used if no .tsconfig found.
if t.hasTypescriptFiles() {
//Can't check if permission denied returns always exists = true....
//typescriptModule := out + string(os.PathSeparator) + "typescript" + string(os.PathSeparator) + "bin"
if !npm.Exists(t.options.Bin) {
t.logger.Println("Installing typescript, please wait...")
res := npm.Install("typescript")
if res.Error != nil {
t.logger.Print(res.Error.Error())
return
}
t.logger.Print(res.Message)
}
projects := t.getTypescriptProjects()
if len(projects) > 0 {
watchedProjects := 0
//typescript project (.tsconfig) found
for _, project := range projects {
cmd := utils.CommandBuilder("node", t.options.Bin, "-p", project[0:strings.LastIndex(project, utils.PathSeparator)]) //remove the /tsconfig.json)
projectConfig := FromFile(project)
if projectConfig.CompilerOptions.Watch {
watchedProjects++
// if has watch : true then we have to wrap the command to a goroutine (I don't want to use the .Start here)
go func() {
_, err := cmd.Output()
if err != nil {
t.logger.Println(err.Error())
return
}
}()
} else {
_, err := cmd.Output()
if err != nil {
t.logger.Println(err.Error())
return
}
}
}
t.logger.Printf("%d Typescript project(s) compiled ( %d monitored by a background file watcher ) ", len(projects), watchedProjects)
} else {
//search for standalone typescript (.ts) files and compile them
files := t.getTypescriptFiles()
if len(files) > 0 {
watchedFiles := 0
if t.options.Tsconfig.CompilerOptions.Watch {
watchedFiles = len(files)
}
//it must be always > 0 if we came here, because of if hasTypescriptFiles == true.
for _, file := range files {
cmd := utils.CommandBuilder("node", t.options.Bin)
cmd.AppendArguments(defaultCompilerArgs...)
cmd.AppendArguments(file)
_, err := cmd.Output()
cmd.Args = cmd.Args[0 : len(cmd.Args)-1] //remove the last, which is the file
if err != nil {
t.logger.Println(err.Error())
return
}
}
t.logger.Printf("%d Typescript file(s) compiled ( %d monitored by a background file watcher )", len(files), watchedFiles)
}
}
//editor activation
if len(projects) == 1 && t.options.Editor != nil {
dir := projects[0][0:strings.LastIndex(projects[0], utils.PathSeparator)]
t.options.Editor.Dir(dir)
t.pluginContainer.Add(t.options.Editor)
}
}
}
func (t *Plugin) hasTypescriptFiles() bool {
root := t.options.Dir
ignoreFolders := strings.Split(t.options.Ignore, ",")
hasTs := false
filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
if fi.IsDir() {
return nil
}
for i := range ignoreFolders {
if strings.Contains(path, ignoreFolders[i]) {
return nil
}
}
if strings.HasSuffix(path, ".ts") {
hasTs = true
return errors.New("Typescript found, hope that will stop here")
}
return nil
})
return hasTs
}
func (t *Plugin) getTypescriptProjects() []string {
var projects []string
ignoreFolders := strings.Split(t.options.Ignore, ",")
root := t.options.Dir
//t.logger.Printf("\nSearching for typescript projects in %s", root)
filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
if fi.IsDir() {
return nil
}
for i := range ignoreFolders {
if strings.Contains(path, ignoreFolders[i]) {
//t.logger.Println(path + " ignored")
return nil
}
}
if strings.HasSuffix(path, utils.PathSeparator+"tsconfig.json") {
//t.logger.Printf("\nTypescript project found in %s", path)
projects = append(projects, path)
}
return nil
})
return projects
}
// this is being called if getTypescriptProjects return 0 len, then we are searching for files using that:
func (t *Plugin) getTypescriptFiles() []string {
var files []string
ignoreFolders := strings.Split(t.options.Ignore, ",")
root := t.options.Dir
//t.logger.Printf("\nSearching for typescript files in %s", root)
filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
if fi.IsDir() {
return nil
}
for i := range ignoreFolders {
if strings.Contains(path, ignoreFolders[i]) {
//t.logger.Println(path + " ignored")
return nil
}
}
if strings.HasSuffix(path, ".ts") {
//t.logger.Printf("\nTypescript file found in %s", path)
files = append(files, path)
}
return nil
})
return files
}
//
//