package typescript import ( "errors" "os" "path/filepath" "strings" "github.com/kataras/iris" "github.com/kataras/iris/config" "github.com/kataras/iris/logger" "github.com/kataras/iris/npm" "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.IPluginContainer // 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.IPluginContainer) 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.Iris) { 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 } // //