ProjectBasedExtensionLoader.ts 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  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. /**
  24. * Resource extension that supports the web view typescript extension
  25. */
  26. export default class ProjectBasedExtensionLoader extends Atomic.ScriptObject implements Editor.HostExtensions.ProjectServicesEventListener {
  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(serviceLocator: Editor.HostExtensions.HostServiceLocator) {
  41. // Let's rewrite the mod search
  42. this.rewriteModSearch();
  43. // We care project events
  44. serviceLocator.projectServices.register(this);
  45. this.serviceRegistry = serviceLocator;
  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.FileMode.FILE_READ);
  68. try {
  69. // add a newline to handle situations where sourcemaps are used. Duktape
  70. // doesn't like not having a trailing newline and the sourcemap process doesn't
  71. // add one.
  72. return include.readText() + "\n";
  73. } finally {
  74. include.close();
  75. }
  76. } else {
  77. throw new Error(`Cannot find project module: ${path}`);
  78. }
  79. } else {
  80. throw new Error(`Extension at ${path} does not reside in the project directory ${system.project.projectPath}`);
  81. }
  82. } else {
  83. return origModSearch(id, require, exports, module);
  84. }
  85. };
  86. })(Duktape.modSearch);
  87. }
  88. /**
  89. * Called when the project is being loaded to allow the typescript language service to reset and
  90. * possibly compile
  91. */
  92. projectLoaded(ev: Editor.EditorLoadProjectEvent) {
  93. // got a load, we need to reset the language service
  94. console.log(`${this.name}: received a project loaded event for project at ${ev.path}`);
  95. let system = ToolCore.getToolSystem();
  96. if (system.project) {
  97. let fileSystem = Atomic.getFileSystem();
  98. let editorScriptsPath = Atomic.addTrailingSlash(system.project.resourcePath) + "EditorData/";
  99. if (fileSystem.dirExists(editorScriptsPath)) {
  100. let filenames = fileSystem.scanDir(editorScriptsPath, "*.js", Atomic.SCAN_FILES, true);
  101. filenames.forEach((filename) => {
  102. // Filtered search in Atomic doesn't due true wildcarding, only handles extension filters
  103. // in the future this may be better handled with some kind of manifest file
  104. if (filename.search(/\.plugin.js$/i) != -1) {
  105. var extensionPath = editorScriptsPath + filename;
  106. extensionPath = extensionPath.substring(0, extensionPath.length - 3);
  107. console.log(`Detected project extension at: ${extensionPath} `);
  108. const moduleName = ProjectBasedExtensionLoader.duktapeRequirePrefix + extensionPath;
  109. // Make sure that we delete the module from the module cache first so if there are any
  110. // changes, they get reflected
  111. delete Duktape.modLoaded[moduleName];
  112. let resourceServiceModule = require(moduleName);
  113. // Handle situation where the service is either exposed by a typescript default export
  114. // or as the module.export (depends on if it is being written in typescript, javascript, es6, etc.)
  115. let resourceService: Editor.HostExtensions.HostEditorService = null;
  116. if (resourceServiceModule.default) {
  117. resourceService = resourceServiceModule.default;
  118. } else {
  119. resourceService = resourceServiceModule;
  120. }
  121. this.serviceRegistry.loadService(resourceService);
  122. }
  123. });
  124. }
  125. }
  126. }
  127. }