ProjectBasedExtensionLoader.ts 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  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. import * as EditorEvents from "../../editor/EditorEvents";
  23. /**
  24. * Resource extension that supports the web view typescript extension
  25. */
  26. export default class ProjectBasedExtensionLoader implements Editor.HostExtensions.ProjectService {
  27. name: string = "ProjectBasedExtensionLoader";
  28. description: string = "This service supports loading extensions that reside in the project under {ProjectRoot}/Editor and named '*.Service.js'.";
  29. private serviceRegistry: Editor.HostExtensions.HostServiceLocator = null;
  30. private modSearchRewritten = false;
  31. /**
  32. * Prefix to use to detect "special" require paths
  33. * @type {String}
  34. */
  35. private static duktapeRequirePrefix = "project:";
  36. /**
  37. * Inject this language service into the registry
  38. * @return {[type]} True if successful
  39. */
  40. initialize(serviceRegistry: Editor.HostExtensions.HostServiceLocator) {
  41. // Let's rewrite the mod search
  42. this.rewriteModSearch();
  43. // We care project events
  44. serviceRegistry.projectServices.register(this);
  45. this.serviceRegistry = serviceRegistry;
  46. }
  47. /**
  48. * Rewrite the duktape modSearch routine so that we can intercept any
  49. * require calls with a "project:" prefix. Duktape will fail if it receives
  50. * a require call with a fully qualified path starting with a "/" (at least on OSX and Linux),
  51. * so we will need to detect any of these project level requires and allow Atomic to go to the
  52. * file system and manually pull these in to provide to duktape
  53. */
  54. private rewriteModSearch() {
  55. Duktape.modSearch = (function(origModSearch) {
  56. return function(id: string, require, exports, module) {
  57. let system = ToolCore.getToolSystem();
  58. if (id.indexOf(ProjectBasedExtensionLoader.duktapeRequirePrefix) == 0) {
  59. let path = id.substr(ProjectBasedExtensionLoader.duktapeRequirePrefix.length) + ".js";
  60. // For safety, only allow bringing modules in from the project directory. This could be
  61. // extended to look for some global extension directory to pull extensions from such as
  62. // ~/.atomicExtensions/...
  63. if (system.project && path.indexOf(system.project.projectPath) == 0) {
  64. console.log(`Searching for project based include: ${path}`);
  65. // we have a project based require
  66. if (Atomic.fileSystem.fileExists(path)) {
  67. let include = new Atomic.File(path, Atomic.FILE_READ);
  68. try {
  69. return include.readText();
  70. } finally {
  71. include.close();
  72. }
  73. } else {
  74. throw new Error(`Cannot find project module: ${path}`);
  75. }
  76. } else {
  77. throw new Error(`Extension at ${path} does not reside in the project directory ${system.project.projectPath}`);
  78. }
  79. } else {
  80. return origModSearch(id, require, exports, module);
  81. }
  82. };
  83. })(Duktape.modSearch);
  84. }
  85. /**
  86. * Called when the project is being loaded to allow the typscript language service to reset and
  87. * possibly compile
  88. */
  89. projectLoaded(ev: Editor.EditorEvents.LoadProjectEvent) {
  90. // got a load, we need to reset the language service
  91. console.log(`${this.name}: received a project loaded event for project at ${ev.path}`);
  92. let system = ToolCore.getToolSystem();
  93. if (system.project) {
  94. let fileSystem = Atomic.getFileSystem();
  95. let editorScriptsPath = Atomic.addTrailingSlash(system.project.projectPath) + "Editor/";
  96. if (fileSystem.dirExists(editorScriptsPath)) {
  97. let filenames = fileSystem.scanDir(editorScriptsPath, "*.js", Atomic.SCAN_FILES, true);
  98. filenames.forEach((filename) => {
  99. // Filtered search in Atomic doesn't due true wildcarding, only handles extension filters
  100. // in the future this may be better handled with some kind of manifest file
  101. if (filename.toLowerCase().lastIndexOf(".service.js") >= 0) {
  102. var extensionPath = editorScriptsPath + filename;
  103. extensionPath = extensionPath.substring(0, extensionPath.length - 3);
  104. console.log(`Detected project extension at: ${extensionPath} `);
  105. // Note: duktape does not yet support unloading modules,
  106. // but will return the same object when passed a path the second time.
  107. let resourceServiceModule = require(ProjectBasedExtensionLoader.duktapeRequirePrefix + extensionPath);
  108. // Handle situation where the service is either exposed by a typescript default export
  109. // or as the module.export (depends on if it is being written in typescript, javascript, es6, etc.)
  110. let resourceService: Editor.HostExtensions.HostEditorService = null;
  111. if (resourceServiceModule.default) {
  112. resourceService = resourceServiceModule.default;
  113. } else {
  114. resourceService = resourceServiceModule;
  115. }
  116. this.serviceRegistry.loadService(resourceService);
  117. }
  118. });
  119. }
  120. }
  121. }
  122. }