ProjectBasedExtensionLoader.ts 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. //
  2. // Copyright (c) 2014-2016 THUNDERBEAST GAMES LLC
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to deal
  6. // in the Software without restriction, including without limitation the rights
  7. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. // copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20. // THE SOFTWARE.
  21. //
  22. /// <reference path="../../../TypeScript/duktape.d.ts" />
  23. import * as EditorEvents from "../../editor/EditorEvents";
  24. /**
  25. * Resource extension that supports the web view typescript extension
  26. */
  27. export default class ProjectBasedExtensionLoader implements Editor.HostExtensions.ProjectServicesEventListener {
  28. name: string = "ProjectBasedExtensionLoader";
  29. description: string = "This service supports loading extensions that reside in the project under {ProjectRoot}/Editor and named '*.Service.js'.";
  30. private serviceRegistry: Editor.HostExtensions.HostServiceLocator = null;
  31. private modSearchRewritten = false;
  32. /**
  33. * Prefix to use to detect "special" require paths
  34. * @type {String}
  35. */
  36. private static duktapeRequirePrefix = "project:";
  37. /**
  38. * Inject this language service into the registry
  39. * @return {[type]} True if successful
  40. */
  41. initialize(serviceLocator: Editor.HostExtensions.HostServiceLocator) {
  42. // Let's rewrite the mod search
  43. this.rewriteModSearch();
  44. // We care project events
  45. serviceLocator.projectServices.register(this);
  46. this.serviceRegistry = serviceLocator;
  47. }
  48. /**
  49. * Rewrite the duktape modSearch routine so that we can intercept any
  50. * require calls with a "project:" prefix. Duktape will fail if it receives
  51. * a require call with a fully qualified path starting with a "/" (at least on OSX and Linux),
  52. * so we will need to detect any of these project level requires and allow Atomic to go to the
  53. * file system and manually pull these in to provide to duktape
  54. */
  55. private rewriteModSearch() {
  56. Duktape.modSearch = (function(origModSearch) {
  57. return function(id: string, require, exports, module) {
  58. let system = ToolCore.getToolSystem();
  59. if (id.indexOf(ProjectBasedExtensionLoader.duktapeRequirePrefix) == 0) {
  60. let path = id.substr(ProjectBasedExtensionLoader.duktapeRequirePrefix.length) + ".js";
  61. // For safety, only allow bringing modules in from the project directory. This could be
  62. // extended to look for some global extension directory to pull extensions from such as
  63. // ~/.atomicExtensions/...
  64. if (system.project && path.indexOf(system.project.projectPath) == 0) {
  65. console.log(`Searching for project based include: ${path}`);
  66. // we have a project based require
  67. if (Atomic.fileSystem.fileExists(path)) {
  68. let include = new Atomic.File(path, Atomic.FILE_READ);
  69. try {
  70. return include.readText();
  71. } finally {
  72. include.close();
  73. }
  74. } else {
  75. throw new Error(`Cannot find project module: ${path}`);
  76. }
  77. } else {
  78. throw new Error(`Extension at ${path} does not reside in the project directory ${system.project.projectPath}`);
  79. }
  80. } else {
  81. return origModSearch(id, require, exports, module);
  82. }
  83. };
  84. })(Duktape.modSearch);
  85. }
  86. /**
  87. * Called when the project is being loaded to allow the typscript language service to reset and
  88. * possibly compile
  89. */
  90. projectLoaded(ev: Editor.EditorEvents.LoadProjectEvent) {
  91. // got a load, we need to reset the language service
  92. console.log(`${this.name}: received a project loaded event for project at ${ev.path}`);
  93. let system = ToolCore.getToolSystem();
  94. if (system.project) {
  95. let fileSystem = Atomic.getFileSystem();
  96. let editorScriptsPath = Atomic.addTrailingSlash(system.project.resourcePath) + "EditorData/";
  97. if (fileSystem.dirExists(editorScriptsPath)) {
  98. let filenames = fileSystem.scanDir(editorScriptsPath, "*.js", Atomic.SCAN_FILES, true);
  99. filenames.forEach((filename) => {
  100. // Filtered search in Atomic doesn't due true wildcarding, only handles extension filters
  101. // in the future this may be better handled with some kind of manifest file
  102. if (filename.toLowerCase().lastIndexOf(".plugin.js") >= 0) {
  103. var extensionPath = editorScriptsPath + filename;
  104. extensionPath = extensionPath.substring(0, extensionPath.length - 3);
  105. console.log(`Detected project extension at: ${extensionPath} `);
  106. // Note: duktape does not yet support unloading modules,
  107. // but will return the same object when passed a path the second time.
  108. let resourceServiceModule = require(ProjectBasedExtensionLoader.duktapeRequirePrefix + extensionPath);
  109. // Handle situation where the service is either exposed by a typescript default export
  110. // or as the module.export (depends on if it is being written in typescript, javascript, es6, etc.)
  111. let resourceService: Editor.HostExtensions.HostEditorService = null;
  112. if (resourceServiceModule.default) {
  113. resourceService = resourceServiceModule.default;
  114. } else {
  115. resourceService = resourceServiceModule;
  116. }
  117. this.serviceRegistry.loadService(resourceService);
  118. }
  119. });
  120. }
  121. }
  122. }
  123. }