2022-08-22 16:56:39 +08:00
|
|
|
"use strict";
|
|
|
|
|
|
|
|
const path = require("path");
|
|
|
|
const WatchedDir = require("broccoli-source").WatchedDir;
|
|
|
|
const Funnel = require("broccoli-funnel");
|
|
|
|
const mergeTrees = require("broccoli-merge-trees");
|
|
|
|
const fs = require("fs");
|
|
|
|
const concat = require("broccoli-concat");
|
|
|
|
const RawHandlebarsCompiler = require("discourse-hbr/raw-handlebars-compiler");
|
2022-10-17 16:38:20 +08:00
|
|
|
const DiscoursePluginColocatedTemplateProcessor = require("./colocated-template-compiler");
|
2022-08-22 16:56:39 +08:00
|
|
|
|
|
|
|
function fixLegacyExtensions(tree) {
|
|
|
|
return new Funnel(tree, {
|
|
|
|
getDestinationPath: function (relativePath) {
|
|
|
|
if (relativePath.endsWith(".es6")) {
|
|
|
|
return relativePath.slice(0, -4);
|
|
|
|
} else if (relativePath.endsWith(".raw.hbs")) {
|
|
|
|
return relativePath.replace(".raw.hbs", ".hbr");
|
|
|
|
}
|
|
|
|
return relativePath;
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
const COLOCATED_CONNECTOR_REGEX =
|
|
|
|
/^(?<prefix>.*)\/connectors\/(?<outlet>[^\/]+)\/(?<name>[^\/\.]+)\.(?<extension>.+)$/;
|
|
|
|
|
|
|
|
// Having connector templates and js in the same directory causes a clash
|
|
|
|
// when outputting es6 modules. This shim separates colocated connectors
|
|
|
|
// into separate js / template locations.
|
|
|
|
function unColocateConnectors(tree) {
|
|
|
|
return new Funnel(tree, {
|
|
|
|
getDestinationPath: function (relativePath) {
|
|
|
|
const match = relativePath.match(COLOCATED_CONNECTOR_REGEX);
|
|
|
|
if (
|
|
|
|
match &&
|
|
|
|
match.groups.extension === "hbs" &&
|
|
|
|
!match.groups.prefix.endsWith("/templates")
|
|
|
|
) {
|
|
|
|
const { prefix, outlet, name } = match.groups;
|
|
|
|
return `${prefix}/templates/connectors/${outlet}/${name}.hbs`;
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
match &&
|
|
|
|
match.groups.extension === "js" &&
|
|
|
|
match.groups.prefix.endsWith("/templates")
|
|
|
|
) {
|
|
|
|
// Some plugins are colocating connector JS under `/templates`
|
|
|
|
const { prefix, outlet, name } = match.groups;
|
|
|
|
const newPrefix = prefix.slice(0, -"/templates".length);
|
|
|
|
return `${newPrefix}/connectors/${outlet}/${name}.js`;
|
|
|
|
}
|
|
|
|
return relativePath;
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function namespaceModules(tree, pluginDirectoryName) {
|
|
|
|
return new Funnel(tree, {
|
|
|
|
getDestinationPath: function (relativePath) {
|
|
|
|
return `discourse/plugins/${pluginDirectoryName}/${relativePath}`;
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-09-02 05:15:32 +08:00
|
|
|
function parsePluginName(pluginRbPath) {
|
|
|
|
const pluginRb = fs.readFileSync(pluginRbPath, "utf8");
|
|
|
|
// Match parsing logic in `lib/plugin/metadata.rb`
|
|
|
|
for (const line of pluginRb.split("\n")) {
|
|
|
|
if (line.startsWith("#")) {
|
|
|
|
const [attribute, value] = line.slice(1).split(":", 2);
|
|
|
|
if (attribute.trim() === "name") {
|
|
|
|
return value.trim();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
throw new Error(
|
|
|
|
`Unable to parse plugin name from metadata in ${pluginRbPath}`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-08-22 16:56:39 +08:00
|
|
|
module.exports = {
|
|
|
|
name: require("./package").name,
|
|
|
|
|
|
|
|
pluginInfos() {
|
|
|
|
const root = path.resolve("../../../../plugins");
|
|
|
|
const pluginDirectories = fs
|
|
|
|
.readdirSync(root, { withFileTypes: true })
|
|
|
|
.filter(
|
|
|
|
(dirent) =>
|
|
|
|
(dirent.isDirectory() || dirent.isSymbolicLink()) &&
|
2022-09-03 02:48:45 +08:00
|
|
|
!dirent.name.startsWith(".") &&
|
|
|
|
fs.existsSync(path.resolve(root, dirent.name, "plugin.rb"))
|
2022-08-22 16:56:39 +08:00
|
|
|
);
|
|
|
|
|
|
|
|
return pluginDirectories.map((directory) => {
|
2022-09-02 05:15:32 +08:00
|
|
|
const directoryName = directory.name;
|
|
|
|
const pluginName = parsePluginName(
|
|
|
|
path.resolve(root, directoryName, "plugin.rb")
|
|
|
|
);
|
|
|
|
const jsDirectory = path.resolve(
|
|
|
|
root,
|
|
|
|
directoryName,
|
|
|
|
"assets/javascripts"
|
|
|
|
);
|
2022-08-25 18:36:02 +08:00
|
|
|
const adminJsDirectory = path.resolve(
|
|
|
|
root,
|
2022-09-02 05:15:32 +08:00
|
|
|
directoryName,
|
2022-08-25 18:36:02 +08:00
|
|
|
"admin/assets/javascripts"
|
|
|
|
);
|
2022-09-02 05:15:32 +08:00
|
|
|
const testDirectory = path.resolve(
|
|
|
|
root,
|
|
|
|
directoryName,
|
|
|
|
"test/javascripts"
|
|
|
|
);
|
2022-08-22 16:56:39 +08:00
|
|
|
const hasJs = fs.existsSync(jsDirectory);
|
2022-08-25 18:36:02 +08:00
|
|
|
const hasAdminJs = fs.existsSync(adminJsDirectory);
|
2022-08-25 16:43:13 +08:00
|
|
|
const hasTests = fs.existsSync(testDirectory);
|
2022-08-25 18:36:02 +08:00
|
|
|
return {
|
2022-09-02 05:15:32 +08:00
|
|
|
pluginName,
|
|
|
|
directoryName,
|
2022-08-25 18:36:02 +08:00
|
|
|
jsDirectory,
|
|
|
|
adminJsDirectory,
|
|
|
|
testDirectory,
|
|
|
|
hasJs,
|
|
|
|
hasAdminJs,
|
|
|
|
hasTests,
|
|
|
|
};
|
2022-08-22 16:56:39 +08:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
generatePluginsTree() {
|
2022-08-25 16:43:13 +08:00
|
|
|
const appTree = this._generatePluginAppTree();
|
|
|
|
const testTree = this._generatePluginTestTree();
|
2022-08-25 18:36:02 +08:00
|
|
|
const adminTree = this._generatePluginAdminTree();
|
|
|
|
return mergeTrees([appTree, testTree, adminTree]);
|
2022-08-25 16:43:13 +08:00
|
|
|
},
|
|
|
|
|
|
|
|
_generatePluginAppTree() {
|
2022-08-22 16:56:39 +08:00
|
|
|
const trees = this.pluginInfos()
|
|
|
|
.filter((p) => p.hasJs)
|
2022-09-02 05:15:32 +08:00
|
|
|
.map(({ pluginName, directoryName, jsDirectory }) =>
|
2022-08-25 18:36:02 +08:00
|
|
|
this._buildAppTree({
|
|
|
|
directory: jsDirectory,
|
2022-09-02 05:15:32 +08:00
|
|
|
pluginName,
|
|
|
|
outputFile: `assets/plugins/${directoryName}.js`,
|
2022-08-25 18:36:02 +08:00
|
|
|
})
|
|
|
|
);
|
|
|
|
return mergeTrees(trees);
|
|
|
|
},
|
2022-08-22 16:56:39 +08:00
|
|
|
|
2022-08-25 18:36:02 +08:00
|
|
|
_generatePluginAdminTree() {
|
|
|
|
const trees = this.pluginInfos()
|
|
|
|
.filter((p) => p.hasAdminJs)
|
2022-09-02 05:15:32 +08:00
|
|
|
.map(({ pluginName, directoryName, adminJsDirectory }) =>
|
2022-08-25 18:36:02 +08:00
|
|
|
this._buildAppTree({
|
|
|
|
directory: adminJsDirectory,
|
2022-09-02 05:15:32 +08:00
|
|
|
pluginName,
|
|
|
|
outputFile: `assets/plugins/${directoryName}_admin.js`,
|
2022-08-25 18:36:02 +08:00
|
|
|
})
|
|
|
|
);
|
|
|
|
return mergeTrees(trees);
|
|
|
|
},
|
2022-08-22 16:56:39 +08:00
|
|
|
|
2022-08-25 18:36:02 +08:00
|
|
|
_buildAppTree({ directory, pluginName, outputFile }) {
|
|
|
|
let tree = new WatchedDir(directory);
|
2022-08-22 16:56:39 +08:00
|
|
|
|
2022-08-25 18:36:02 +08:00
|
|
|
tree = fixLegacyExtensions(tree);
|
|
|
|
tree = unColocateConnectors(tree);
|
|
|
|
tree = namespaceModules(tree, pluginName);
|
2022-08-22 16:56:39 +08:00
|
|
|
|
2022-08-25 18:36:02 +08:00
|
|
|
tree = RawHandlebarsCompiler(tree);
|
2022-10-17 16:38:20 +08:00
|
|
|
|
|
|
|
tree = new DiscoursePluginColocatedTemplateProcessor(tree);
|
2022-08-25 18:36:02 +08:00
|
|
|
tree = this.compileTemplates(tree);
|
|
|
|
|
|
|
|
tree = this.processedAddonJsFiles(tree);
|
|
|
|
|
|
|
|
return concat(mergeTrees([tree]), {
|
|
|
|
inputFiles: ["**/*.js"],
|
|
|
|
outputFile,
|
|
|
|
allowNone: true,
|
|
|
|
});
|
2022-08-25 16:43:13 +08:00
|
|
|
},
|
|
|
|
|
|
|
|
_generatePluginTestTree() {
|
|
|
|
const trees = this.pluginInfos()
|
|
|
|
.filter((p) => p.hasTests)
|
2022-09-02 05:15:32 +08:00
|
|
|
.map(({ pluginName, directoryName, testDirectory }) => {
|
2022-08-25 16:43:13 +08:00
|
|
|
let tree = new WatchedDir(testDirectory);
|
|
|
|
|
|
|
|
tree = fixLegacyExtensions(tree);
|
2022-09-02 05:15:32 +08:00
|
|
|
tree = namespaceModules(tree, pluginName);
|
2022-08-25 16:43:13 +08:00
|
|
|
tree = this.processedAddonJsFiles(tree);
|
|
|
|
|
|
|
|
return concat(mergeTrees([tree]), {
|
|
|
|
inputFiles: ["**/*.js"],
|
2022-09-02 05:15:32 +08:00
|
|
|
outputFile: `assets/plugins/test/${directoryName}_tests.js`,
|
2022-08-25 16:43:13 +08:00
|
|
|
allowNone: true,
|
2022-08-22 16:56:39 +08:00
|
|
|
});
|
|
|
|
});
|
|
|
|
return mergeTrees(trees);
|
|
|
|
},
|
|
|
|
|
|
|
|
shouldCompileTemplates() {
|
|
|
|
// The base Addon implementation checks for template
|
|
|
|
// files in the addon directories. We need to override that
|
|
|
|
// check so that the template compiler always runs.
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
|
|
|
|
treeFor() {
|
|
|
|
// This addon doesn't contribute any 'real' trees to the app
|
|
|
|
return;
|
|
|
|
},
|
|
|
|
};
|