333 lines
11 KiB
JavaScript
333 lines
11 KiB
JavaScript
/**
|
|
* Copyright OpenJS Foundation and other contributors, https://openjsf.org/
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
**/
|
|
|
|
const Enquirer = require('enquirer');
|
|
const color = require('ansi-colors');
|
|
|
|
const mustache = require("mustache");
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
|
|
let bcrypt;
|
|
try { bcrypt = require('@node-rs/bcrypt'); }
|
|
catch(e) { bcrypt = require('bcryptjs'); }
|
|
|
|
function prompt(opts) {
|
|
const enq = new Enquirer();
|
|
return enq.prompt(opts);
|
|
}
|
|
|
|
/**
|
|
* 1. identify userdir
|
|
* 2. check if settings file already exists
|
|
* 3. Enable projects feature with version control?
|
|
|
|
* 3. get flowFile name
|
|
* 3. get credentialSecret
|
|
* 4. ask to setup adminAuth
|
|
* - prompt for username
|
|
* - prompt for password
|
|
*/
|
|
|
|
async function loadTemplateSettings() {
|
|
const templateFile = path.join(__dirname,"resources/settings.js.mustache");
|
|
return fs.promises.readFile(templateFile,"utf8");
|
|
}
|
|
|
|
async function fillTemplate(template, context) {
|
|
return mustache.render(template,context);
|
|
}
|
|
|
|
function heading(str) {
|
|
console.log("\n"+color.cyan(str));
|
|
console.log(color.cyan(Array(str.length).fill("=").join("")));
|
|
}
|
|
function message(str) {
|
|
console.log(color.bold(str)+"\n");
|
|
}
|
|
|
|
async function promptSettingsFile(opts) {
|
|
const defaultSettingsFile = path.join(opts.userDir,"settings.js");
|
|
const responses = await prompt([
|
|
{
|
|
type: 'input',
|
|
name: 'settingsFile',
|
|
initial: defaultSettingsFile,
|
|
message: "Settings file",
|
|
onSubmit(key, value, p) {
|
|
p.state.answers.exists = fs.existsSync(value);
|
|
}
|
|
},
|
|
{
|
|
type: 'select',
|
|
name: 'confirmOverwrite',
|
|
initial: "No",
|
|
message: 'That file already exists. Are you sure you want to overwrite it?',
|
|
choices: ['Yes', 'No'],
|
|
result(value) {
|
|
return value === "Yes"
|
|
},
|
|
skip() {
|
|
return !this.state.answers.exists
|
|
}
|
|
}
|
|
]);
|
|
|
|
if (responses.exists && !responses.confirmOverwrite) {
|
|
return promptSettingsFile(opts);
|
|
}
|
|
// const alreadyExists = await fs.exists(responses.settingsFile);
|
|
// responses.exists =
|
|
return responses;
|
|
}
|
|
|
|
async function promptUser() {
|
|
const responses = await prompt([
|
|
{
|
|
type: 'input',
|
|
name: 'username',
|
|
message: "Username",
|
|
validate(val) { return !!val.trim() ? true: "Invalid username"}
|
|
},
|
|
{
|
|
type: 'password',
|
|
name: 'password',
|
|
message: "Password",
|
|
validate(val) {
|
|
if (val.length < 8) {
|
|
return "Password too short. Must be at least 8 characters"
|
|
}
|
|
return true
|
|
}
|
|
},
|
|
{
|
|
type: 'select',
|
|
name: 'permissions',
|
|
message: "User permissions",
|
|
choices: [ {name:"full access", value:"*"}, {name:"read-only access", value:"read"}],
|
|
result(value) {
|
|
return this.find(value).value;
|
|
}
|
|
}
|
|
])
|
|
responses.password = bcrypt.hashSync(responses.password, 8);
|
|
return responses;
|
|
}
|
|
|
|
async function promptSecurity() {
|
|
heading("User Security");
|
|
|
|
const responses = await prompt([
|
|
{
|
|
type: 'select',
|
|
name: 'adminAuth',
|
|
initial: "Yes",
|
|
message: 'Do you want to setup user security?',
|
|
choices: ['Yes', 'No'],
|
|
result(value) {
|
|
return value === "Yes"
|
|
}
|
|
}
|
|
])
|
|
if (responses.adminAuth) {
|
|
responses.users = [];
|
|
while(true) {
|
|
responses.users.push(await promptUser());
|
|
const resp = await prompt({
|
|
type: 'select',
|
|
name: 'addMore',
|
|
initial: "No",
|
|
message: 'Add another user?',
|
|
choices: ['Yes', 'No'],
|
|
result(value) {
|
|
return value === "Yes"
|
|
}
|
|
})
|
|
if (!resp.addMore) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return responses;
|
|
}
|
|
|
|
async function promptProjects() {
|
|
heading("Projects");
|
|
message("The Projects feature allows you to version control your flow using a local git repository.");
|
|
const responses = await prompt([
|
|
{
|
|
type: 'select',
|
|
name: 'enabled',
|
|
initial: "No",
|
|
message: 'Do you want to enable the Projects feature?',
|
|
choices: ['Yes', 'No'],
|
|
result(value) {
|
|
return value === "Yes";
|
|
}
|
|
},
|
|
// {
|
|
// type: 'select',
|
|
// name: '_continue',
|
|
// message: 'Node-RED will help you create your project the first time you access the editor.',
|
|
// choices: ['Continue'],
|
|
// skip() {
|
|
// return !this.state.answers.enabled;
|
|
// }
|
|
// },
|
|
{
|
|
type: 'select',
|
|
name: 'workflow',
|
|
message: 'What project workflow do you want to use?',
|
|
choices: [
|
|
{value: 'manual', name: 'manual - you must manually commit changes'},
|
|
{value: 'auto', name: 'auto - changes are automatically committed'}
|
|
],
|
|
skip() {
|
|
return !this.state.answers.enabled;
|
|
},
|
|
result(value) {
|
|
return this.find(value).value;
|
|
}
|
|
}
|
|
])
|
|
// delete responses._continue;
|
|
return responses
|
|
}
|
|
|
|
async function promptFlowFileSettings() {
|
|
heading("Flow File settings");
|
|
const responses = await prompt([
|
|
{
|
|
type: 'input',
|
|
name: 'flowFile',
|
|
message: 'Enter a name for your flows file',
|
|
default: 'flows.json'
|
|
},
|
|
{
|
|
type: 'password',
|
|
name: 'credentialSecret',
|
|
message: 'Provide a passphrase to encrypt your credentials file'
|
|
}
|
|
])
|
|
return responses
|
|
}
|
|
|
|
async function promptNodeSettings() {
|
|
heading("Node settings");
|
|
const responses = await prompt([
|
|
{
|
|
type: 'select',
|
|
name: 'functionExternalModules',
|
|
message: 'Allow Function nodes to load external modules? (functionExternalModules)',
|
|
initial: 'Yes',
|
|
choices: ['Yes', 'No'],
|
|
result(value) {
|
|
return value === "Yes"
|
|
},
|
|
}
|
|
]);
|
|
return responses;
|
|
}
|
|
async function promptEditorSettings() {
|
|
heading("Editor settings");
|
|
const responses = await prompt([
|
|
{
|
|
type: 'select',
|
|
name: 'theme',
|
|
message: 'Select a theme for the editor. To use any theme other than "default", you will need to install @node-red-contrib-themes/theme-collection in your Node-RED user directory.',
|
|
initial: 'default',
|
|
choices: [ "default", "aurora", "cobalt2", "dark", "dracula", "espresso-libre", "github-dark", "github-dark-default", "github-dark-dimmed", "midnight-red", "monoindustrial", "monokai", "monokai-dimmed", "noctis", "oceanic-next", "oled", "one-dark-pro", "one-dark-pro-darker", "solarized-dark", "solarized-light", "tokyo-night", "tokyo-night-light", "tokyo-night-storm", "totallyinformation", "zenburn"],
|
|
},
|
|
{
|
|
type: 'select',
|
|
name: 'codeEditor',
|
|
message: 'Select the text editor component to use in the Node-RED Editor',
|
|
initial: 'monaco',
|
|
choices: [ {name:"monaco (default)", value:"monaco"}, {name:"ace", value:"ace"} ],
|
|
result(value) {
|
|
return this.find(value).value;
|
|
}
|
|
}
|
|
]);
|
|
return responses;
|
|
}
|
|
async function command(argv, result) {
|
|
const config = {
|
|
intro: `Node-RED Settings created at ${new Date().toUTCString()}`,
|
|
flowFile: "flows.json",
|
|
editorTheme: ""
|
|
};
|
|
|
|
heading("Node-RED Settings File initialisation");
|
|
message(`This tool will help you create a Node-RED settings file.`);
|
|
|
|
|
|
const userDir = argv["u"] || argv["userDir"] || path.join(process.env.HOME || process.env.USERPROFILE || process.env.HOMEPATH || process.env.NODE_RED_HOME,".node-red");
|
|
|
|
const fileSettings = await promptSettingsFile({userDir});
|
|
|
|
const securityResponses = await promptSecurity();
|
|
if (securityResponses.adminAuth) {
|
|
let adminAuth = {
|
|
type: "credentials",
|
|
users: securityResponses.users
|
|
};
|
|
config.adminAuth = JSON.stringify(adminAuth, null, 4).replace(/\n/g,"\n ");
|
|
}
|
|
|
|
const projectsResponses = await promptProjects();
|
|
let flowFileSettings = {};
|
|
if (!projectsResponses.enabled) {
|
|
flowFileSettings = await promptFlowFileSettings();
|
|
config.flowFile = flowFileSettings.flowFile;
|
|
if (flowFileSettings.hasOwnProperty("credentialSecret")) {
|
|
config.credentialSecret = flowFileSettings.credentialSecret?`"${flowFileSettings.credentialSecret}"`:"false"
|
|
}
|
|
config.projects = {
|
|
enabled: false,
|
|
workflow: "manual"
|
|
}
|
|
} else {
|
|
config.projects = projectsResponses
|
|
}
|
|
const editorSettings = await promptEditorSettings();
|
|
config.codeEditor = editorSettings.codeEditor;
|
|
if (editorSettings.theme !== "default") {
|
|
config.editorTheme = editorSettings.theme
|
|
}
|
|
const nodeSettings = await promptNodeSettings();
|
|
config.functionExternalModules = nodeSettings.functionExternalModules;
|
|
|
|
|
|
const template = await loadTemplateSettings();
|
|
const settings = await fillTemplate(template, config)
|
|
|
|
const settingsDir = path.dirname(fileSettings.settingsFile);
|
|
await fs.promises.mkdir(settingsDir,{recursive: true});
|
|
await fs.promises.writeFile(fileSettings.settingsFile, settings, "utf-8");
|
|
|
|
|
|
console.log(color.yellow(`\n\nSettings file written to ${fileSettings.settingsFile}`));
|
|
|
|
if (config.editorTheme) {
|
|
console.log(color.yellow(`To use the '${config.editorTheme}' editor theme, remember to install @node-red-contrib-themes/theme-collection in your Node-RED user directory`))
|
|
}
|
|
}
|
|
command.alias = "init";
|
|
command.usage = command.alias;
|
|
command.description = "Initialise a Node-RED settings file";
|
|
|
|
module.exports = command;
|