discourse/app/assets/javascripts/discourse-plugins/index.js
Martin Brennan ac7bf98ad1
DEV: Load client site settings YML into JS tests (#18413)
Our method of loading a subset of client settings into tests via
tests/helpers/site-settings.js can be improved upon. Currently we have a
hardcoded subset of the client settings, which may get out of date and not have
the correct defaults. As well as this plugins do not get their settings into the
tests, so whenever you need a setting from a plugin, even if it has a default,
you have to do needs.setting({ ... }) which is inconvenient.

This commit introduces an ember CLI build step to take the site_settings.yml and
all the plugin settings.yml files, pull out the client settings, and dump them
into a variable in a single JS file we can load in our tests, so we have the
correct selection of settings and default values in our JS tests. It also fixes
many, many tests that were operating under incorrect assumptions or old
settings.

Co-authored-by: Joffrey JAFFEUX <j.jaffeux@gmail.com>
2022-11-08 09:17:43 +10:00

221 lines
6.5 KiB
JavaScript

"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");
const DiscoursePluginColocatedTemplateProcessor = require("./colocated-template-compiler");
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}`;
},
});
}
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}`
);
}
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()) &&
!dirent.name.startsWith(".") &&
fs.existsSync(path.resolve(root, dirent.name, "plugin.rb"))
);
return pluginDirectories.map((directory) => {
const directoryName = directory.name;
const pluginName = parsePluginName(
path.resolve(root, directoryName, "plugin.rb")
);
const jsDirectory = path.resolve(
root,
directoryName,
"assets/javascripts"
);
const adminJsDirectory = path.resolve(
root,
directoryName,
"admin/assets/javascripts"
);
const testDirectory = path.resolve(
root,
directoryName,
"test/javascripts"
);
const configDirectory = path.resolve(root, directoryName, "config");
const hasJs = fs.existsSync(jsDirectory);
const hasAdminJs = fs.existsSync(adminJsDirectory);
const hasTests = fs.existsSync(testDirectory);
const hasConfig = fs.existsSync(configDirectory);
return {
pluginName,
directoryName,
jsDirectory,
adminJsDirectory,
testDirectory,
configDirectory,
hasJs,
hasAdminJs,
hasTests,
hasConfig,
};
});
},
generatePluginsTree() {
const appTree = this._generatePluginAppTree();
const testTree = this._generatePluginTestTree();
const adminTree = this._generatePluginAdminTree();
return mergeTrees([appTree, testTree, adminTree]);
},
_generatePluginAppTree() {
const trees = this.pluginInfos()
.filter((p) => p.hasJs)
.map(({ pluginName, directoryName, jsDirectory }) =>
this._buildAppTree({
directory: jsDirectory,
pluginName,
outputFile: `assets/plugins/${directoryName}.js`,
})
);
return mergeTrees(trees);
},
_generatePluginAdminTree() {
const trees = this.pluginInfos()
.filter((p) => p.hasAdminJs)
.map(({ pluginName, directoryName, adminJsDirectory }) =>
this._buildAppTree({
directory: adminJsDirectory,
pluginName,
outputFile: `assets/plugins/${directoryName}_admin.js`,
})
);
return mergeTrees(trees);
},
_buildAppTree({ directory, pluginName, outputFile }) {
let tree = new WatchedDir(directory);
tree = fixLegacyExtensions(tree);
tree = unColocateConnectors(tree);
tree = namespaceModules(tree, pluginName);
tree = RawHandlebarsCompiler(tree);
tree = new DiscoursePluginColocatedTemplateProcessor(tree, pluginName);
tree = this.compileTemplates(tree);
tree = this.processedAddonJsFiles(tree);
return concat(mergeTrees([tree]), {
inputFiles: ["**/*.js"],
outputFile,
allowNone: true,
});
},
_generatePluginTestTree() {
const trees = this.pluginInfos()
.filter((p) => p.hasTests)
.map(({ pluginName, directoryName, testDirectory }) => {
let tree = new WatchedDir(testDirectory);
tree = fixLegacyExtensions(tree);
tree = namespaceModules(tree, pluginName);
tree = this.processedAddonJsFiles(tree);
return concat(mergeTrees([tree]), {
inputFiles: ["**/*.js"],
outputFile: `assets/plugins/test/${directoryName}_tests.js`,
allowNone: true,
});
});
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;
},
};