Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 103 additions & 82 deletions build.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,57 @@
const { cwd } = require("process");
const { cwd } = require("node:process");
const ts = require("typescript");
const {
cp,
readdirSync,
exists,
existsSync,
readFileSync,
writeFile,
writeFileSync,
mkdirSync,
realpathSync,
copyFileSync,
} = require("node:fs");
const sourcePath = cwd() + "/src";
const targetPath = cwd() + "/dist";

// directory containing the sources for nodes; defaults to `src/nodes` within the current working directory
const nodesSourceDir = cwd() + "/src/nodes";
// directory the generated files should be saved to; defaults to `dist/nodes` within the current working directory
const nodesTargetDir = cwd() + "/dist/nodes";
// path to the typescript project configuration for building the nodes
const buildTsConfig = JSON.parse(readFileSync("build.tsconfig.json").toString());

/**
* @param node String
* @param source String
* @param target String
* Creates a directory if it does not exist
* @param {string} dir directory path to create
*/
function makeDir(dir) {
// bypass everything in case the directory already exists
if (!existsSync(dir)) {
const parentDir = realpathSync(`${dir}../`);

// check whether parent directory exists and create it recursively if not
if (!existsSync(parentDir)) {
makeDir(parentDir);
}

// finally create the directory
mkdirSync(dir);
}
}

/**
* Builds the form for editing a node using the Node-RED Editor.
*
* @param {string} node name of the node the form belongs to
* @param {string} sourcePath path of the directory containing the node's source files
* @param {string} targetPath path of the directory the node's generated files should be saved to
*/
function buildNodeForm(node, source, target) {
function buildForm(node, sourcePath, targetPath) {
/**
* @type {string[]}
*/
const html = [];
const form = readFileSync(`${source}/form.html`).toString();
const docs = existsSync(`${source}/docs.html`) ? readFileSync(`${source}/docs.html`).toString() : undefined;
const form = readFileSync(`${sourcePath}/form.html`).toString();
const docs = existsSync(`${sourcePath}/docs.html`) ? readFileSync(`${sourcePath}/docs.html`).toString() : undefined;

// parse `form.html` and wrap it to the corresponding script-tag
const formLines = form.split("\n");
html.push(`<script type="text/html" data-template-name="${node}">`);
formLines.forEach((line) => {
Expand All @@ -37,6 +61,7 @@ function buildNodeForm(node, source, target) {
});
html.push("</script>");

// parse `docs.html` and wrap it to the corresponding script-tag
if (docs) {
const docsLines = docs.split("\n");
html.push(`<script type="text/html" data-help-name="${node}">`);
Expand All @@ -48,12 +73,14 @@ function buildNodeForm(node, source, target) {
html.push("</script>");
}

const initTS = readFileSync(`${source}/init.ts`).toString();
// read and transpile the node's TypeScript
const initTS = readFileSync(`${sourcePath}/init.ts`).toString();
let initJS = ts
.transpileModule(initTS, buildTsConfig)
.outputText.replace(/export \{(?:[^\}]+)?\};/gim, "")
.replace(/RED\.nodes\.registerType\("([^\"]+)"/i, `RED.nodes.registerType("${node}"`);

// inject the node's init logic into the node's HTML
const initJSLines = initJS.split("\n");
html.push('<script type="text/javascript">');
initJSLines.forEach((line) => {
Expand All @@ -65,40 +92,47 @@ function buildNodeForm(node, source, target) {

html.push("");

mkdirSync(`${target}/`, {
// create node's target directory
mkdirSync(`${targetPath}/`, {
recursive: true,
});

writeFileSync(`${target}/${node}.html`, html.join("\n"));
// save the node's editor formular
writeFileSync(`${targetPath}/${node}.html`, html.join("\n"));
}

/**
* @param node String
* @param source String
* @param target String
* Copys the locale files of a node.
*
* @param {string} node name of the node the form belongs to
* @param {string} sourcePath path of the directory containing the node's source files
* @param {string} targetPath path of the directory the node's generated files should be saved to
*/
function copyNodeLocales(node, source, target) {
if (!existsSync(`${source}/locales`)) {
function copyLocales(node, sourcePath, targetPath) {
// skip in case directory `locales` does not exist within the nodes directory
if (!existsSync(`${sourcePath}/locales`)) {
return;
}

readdirSync(`${source}/locales`, {
readdirSync(`${sourcePath}/locales`, {
recursive: false,
})
.filter((language) => {
// filter for files named as valid language codes
return language.match(/^[a-z]{2,2}(?:-[A-Z]{2,2})?\.json$/);
})
.forEach((language) => {
// read the language code and the JSON
const languageCode = language.match(/^([a-z]{2,2}(?:-[A-Z]{2,2})?)\.json$/i)[1];
const content = readFileSync(`${source}/locales/${language}`).toString();
const content = readFileSync(`${sourcePath}/locales/${language}`).toString();

/**
* @type {string[]}
*/
const json = [];
const json = ["{"];

// wrap locales content within a tag names like the node
const contentLines = content.split("\n");
const lastElement = json.push("{");
json.push(` "${node}": ${contentLines[0]}`);
contentLines.splice(1).forEach((line) => {
if (line.length > 0) {
Expand All @@ -107,75 +141,62 @@ function copyNodeLocales(node, source, target) {
});
json.push("}");

mkdirSync(`${target}/locales/${languageCode}`, {
// create node locales target directory
mkdirSync(`${targetPath}/locales/${languageCode}`, {
recursive: true,
});

writeFileSync(`${target}/locales/${languageCode}/${node}.json`, json.join("\n"));
// save the node's locales
writeFileSync(`${targetPath}/locales/${languageCode}/${node}.json`, json.join("\n"));
});
}

function buildNodes() {
readdirSync(`${sourcePath}/nodes`, {
recursive: false,
})
.filter((node) => {
const path = `${sourcePath}/nodes/${node}`;

return existsSync(`${path}/form.html`) && existsSync(`${path}/${node}.ts`);
})
.forEach((node) => {
console.info(`Building node ${node}`);

const source = `${sourcePath}/nodes/${node}`;
const target = `${targetPath}/nodes/${node}`;

buildNodeForm(node, source, target);
/**
* Copy icons of a node.
*
@param {string} node name of the node the form belongs to
@param {string} sourcePath path of the directory containing the node's source files
@param {string} targetPath path of the directory the node's generated files should be saved to
*/
function copyIcons(node, sourcePath, targetPath) {
// skip in case directory `icons` does not exist within the nodes directory
if (!existsSync(`${sourcePath}/icons`)) {
return;
}

copyNodeLocales(node, source, target);
});
}
// create node icons target directory
mkdirSync(`${targetPath}/icons`, {
recursive: true,
});

function buildPlugins() {
readdirSync(`${sourcePath}/plugins`, {
// copy the node's icons
readdirSync(`${sourcePath}/icons`, {
recursive: false,
})
.filter((plugin) => {
const path = `${sourcePath}/plugins/${plugin}`;

return existsSync(`${path}/${plugin}.ts`);
})
.forEach((plugin) => {
console.info(`Building plugin ${plugin}`);

// does nothing yet
});
}).forEach((icon) => {
copyFileSync(`${sourcePath}/icons/${icon}`, `${targetPath}/icons/${icon}`);
});
}

function buildThemes() {
readdirSync(`${sourcePath}/themes`, {
recursive: false,
/*
* Iterate over every directory
*/
readdirSync(nodesSourceDir, {
recursive: false,
})
.filter((node) => {
const path = `${nodesSourceDir}/${node}`;

// filter for directories containing at least a `form.html` and the nodes logic script `node.ts`
return existsSync(`${path}/form.html`) && existsSync(`${path}/${node}.ts`);
})
.filter((theme) => {
const path = `${sourcePath}/themes/${theme}`;
.forEach((node) => {
const sourcePath = `${nodesSourceDir}/${node}`;
const targetPath = `${nodesTargetDir}/${node}`;

return existsSync(`${path}/${theme}.ts`);
})
.forEach((theme) => {
console.info(`Building theme ${theme}`);

// does nothing yet
});
}

if (existsSync(`${sourcePath}/plugins`)) {
buildPlugins();
}

if (existsSync(`${sourcePath}/nodes`)) {
buildNodes();
}
// build the nodes' form for Node-RED Editor
buildForm(node, sourcePath, targetPath);

if (existsSync(`${sourcePath}/themes`)) {
buildThemes();
}
// copy/transfer optional assets
copyLocales(node, sourcePath, targetPath);
copyIcons(node, sourcePath, targetPath);
});
File renamed without changes
File renamed without changes
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,10 @@
},
"main": "dist",
"files": [
"src",
"dist",
"examples",
"icons",
"resources",
"LICENSE",
"README.md"
]
Expand Down
Empty file added resources/.gitkeep
Empty file.
7 changes: 7 additions & 0 deletions src/nodes/sacn-in/icons/sacn-in.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions src/nodes/sacn-out/icons/sacn-out.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions src/nodes/scene-controller/icons/scene-controller.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading