Node RED v4 : Single File Executable

To support running within an SFE or not,
Something like this should work.

const getStaticPath = () => {
    if (process.pkg !== undefined) {
        return join(dirname(process.argv0), 'static');
    } else {
        return join(__dirname, 'static');
    }
}

httpStatic: {
        path: getStaticPath(),
        root: '/',
        options: {
            maxAge:'1d',
            setHeaders:function(res,path,stat){
                res.set('x-timestamp',Date.now())
                if(path.endsWith("png")){res.setHeader('Cache-Control','public, max-age=86400')}
            }
        }
    },

Explanation.

The SFE will treat relative paths as relative paths inside the VFS - so we actually need to use absolute paths.

The if (process.pkg !== undefined) block just checks if we should use the path that the executable its self is run from, or from the uncompiled script location.

process.argv0 is the SFE that was launched.

You can see it happening here.
Whether the SFE should use an embedded flow or create a standard User Directory if not embedded (it create the user Direct next to the executable)

And to detect the correct prefix.

const pathPrefix = process.platform === 'win32' ? 'c:/' : '/';

So the absolute path on Windows is: C:\Users\Bannd\Downloads\GameDev\Node-RED-SFE-main\NRUserDir\static
so far I think I have tried:
httpStatic: { path: 'c:\\Users\\Bannd\\Downloads\\GameDev\\Node-RED-SFE-main\\NRUserDir\\static\\' },

httpStatic: { path: 'C:\\Users\\Bannd\\Downloads\\GameDev\\Node-RED-SFE-main\\NRUserDir\\static\\' },

httpStatic: { path: 'c:\Users\Bannd\Downloads\GameDev\Node-RED-SFE-main\NRUserDir\static\' },

httpStatic: { path: 'C:\Users\Bannd\Downloads\GameDev\Node-RED-SFE-main\NRUserDir\static\' },

httpStatic: { path: 'c:/Users/Bannd/Downloads/GameDev/Node-RED-SFE-main/NRUserDir/static/' },

httpStatic: { path: 'C:/Users/Bannd/Downloads/GameDev/Node-RED-SFE-main/NRUserDir/static/' },

httpStatic: { path: 'c:\\Users\\Bannd\\Downloads\\GameDev\\Node-RED-SFE-main\\NRUserDir\\static' },

httpStatic: { path: 'C:\\Users\\Bannd\\Downloads\\GameDev\\Node-RED-SFE-main\\NRUserDir\\static' },

httpStatic: { path: 'c:\Users\Bannd\Downloads\GameDev\Node-RED-SFE-main\NRUserDir\static' },

httpStatic: { path: 'C:\Users\Bannd\Downloads\GameDev\Node-RED-SFE-main\NRUserDir\static' },

httpStatic: { path: 'c:/Users/Bannd/Downloads/GameDev/Node-RED-SFE-main/NRUserDir/static' },

httpStatic: { path: 'C:/Users/Bannd/Downloads/GameDev/Node-RED-SFE-main/NRUserDir/static' },
But non seem to work even in a regular Node-RED instance (not SFE), can anyone clarify the correct format for me?

I vaguely remember trying httpStatic in a standard Node RED install, and always receving Cannot GET /static

You can actually just add it to your own app instance.

app.use('/static', express.static(path.join(__dirname, 'static')))

Have never really experimented apart from my attempts with httpStatic

So including

app.use('/', express.static(join(__dirname, 'static')));
console.log(join(__dirname, 'static'));

Works perfectly but logs C:\Users\Bannd\Downloads\GameDev\Node-RED-SFE-main\static to console.

According to the README:

Node-RED SFE can actually operate in two ways:

 - With an Embedded flow (the main use case)
 - A standard Node-RED portable application

 Both scenarios will output an SFE, so Node JS is still not required in either setup.
 The mode is manipulated based on whether there is a directory called `NRHomeDir`.

 When you run `npm run-script build`, it will check if there is such a directory.
 If there is, it will package it up and cause it to "lock" to the developed/embedded flow.

I'm supposed to be able to embed a flow file but I always seem to get the result:

[2024-08-06 01:36:51] info      Embedded UserDir Found : false

Looking through the code, I see no such checks for NRHomeDir to exist, only for NRUserDir

Can you clarify on how a flow can be embedded?

Hi @HaroldPetersInskipp,

During the build process (ESBuild), if there is a folder called NRUserDir.

It will move that folder to the build folder - which is used by pkg

Later it checks for this folder within the VFS, during start up,
and uses the embedded location as part of the --userDir parameter of Node RED

There might be a typo in the read me :innocent:

npm run-script build : Moves things to the build folder + ESBuilds a few things
npm run-script package : compiles everything in the build folder to the final executable

npm run-script develop : starts up Node RED - allowing you to start building flows + creates NRUserDir

NRUserDir : is no different to the normal .node-red directory.

There is nothing stopping you taking everything in a .node-red directory, moving its contents to a NRUserDir directory resulting in an embedded build (based on what was in .node-red)

So I copied everything in my .node-red directory over to the NRUserDir like you suggested, and when I run npm run-script build it does move things to the build folder. However, when I then run npm run-script package it creates an executable with no flow and no installed nodes. But it works fine when I run npm run-script develop however I still get

[2024-08-06 01:36:51] info      Embedded UserDir Found : false

even with the NRUserDir in place?

Mmm I'm not seeing this.

Is the folder NRUserDir in the same location as all the build scripts?
It checks for this folder relative to the build.js script

plus are you working within the directory SFE was downloaded to?

cd <path-to-SFE-Working-Directory>
npm run-script build
npm run-script package

My folder looks like this and I still get it:

So that is Design Time
Not a packaged image being executed.

During Design Time - nothing is embedded - Node RED is started and has been set to use the local NRuserDir

Design Time does nothing other then

  • Starts Node RED (as normal)
  • Sets its userDir param to the local folder you see (NRUserDir) - therefore its not embedded (yet)

At this stage, you're designing a flow/installing the extra nodes etc etc

Im not sure what tool is being used - but running npm run-script build followed by npm run-script package will package all that up into an executable.

You need to be in : C:\Users\Bannd\Downloads\GameDev\Node-RED-SFE-main within command prompt and run those commands.

as per my screen shot - not sure why your not getting the same.

the output will be placed in build/dist

I had to make a change to line 54-56 of the node-red.js

const ns = 'Node-RED-SFE-main';
const userDir = 'NRUserDir';
const userDirPath = `${pathPrefix}snapshot/${ns}/${userDir}`;

And switch to using C:/snapshot/Node-RED-SFE-main for my development directory to get it working. I think something about how the path is formed is incorrect/hard-coded?

You should try to avoid changing this.
This variable is updated during build, to reflect your working directory, hence why it is a place holder.

const projectName = path.dirname(__filename).split(path.sep).pop();
const replacements = [
		['{SFE_PROJECT_DIR}', projectName]
];

pkg makes reference to the immediate parent folder during compile (and used internally in the executable)

If you're going to hard code it - you must always ensure the directory in which you're working is : Node-RED-SFE-main - or whatever you set as ns

C:/snapshot, /snapshot is just the mount point that pkg will use.
there is no need to pay any attention to it or work in a directory called snapshot.

Im really not sure why it's not working for you, unless you make the changes you have.
I get flawless builds in OSX and Windows without these steps.

I use VSCode or Command Prompt both set the working directory, so scripts are run with the current working directory in context. not sure if that has anything to do with it.

I:

  • Pull down the scripts and everything else (into any folder I want)
  • in command prompt (or VS Code) move into this directory
  • npm install
  • npm run-script develop OR
    • mkdir NRUserDir and copy the contents of an already .node-red environment
  • npm run-script build
  • npm run-script package

If you can share a project with me, I'm happy to see what may be going on - But maybe send me a PM (I'm away for 3 days however).

This thread is getting large :grimacing:

I am working in the directory C:\Users\Bannd\Downloads\GameDev\Node-RED-SFE-main
As soon as I change these variables back it no longer embeds:

const ns = '{SFE_PROJECT_DIR}';
const userDir = 'NRUserDir';
const userDirPath = `${pathPrefix}snapshot/${ns}/${userDir}`;

and I get the following log when running npm run-script develop:

[2024-08-07 09:48:30] info      UserDirPath            : c:/snapshot/{SFE_PROJECT_DIR}/NRUserDir
[2024-08-07 09:48:30] info      Node Version           : 20.7.0
[2024-08-07 09:48:30] info      Node-RED Version       : 4.0.2
[2024-08-07 09:48:30] info      Mode                   : Design Time
[2024-08-07 09:48:30] info      Namespace              : {SFE_PROJECT_DIR}
[2024-08-07 09:48:30] info      Embedded UserDir Found : false
[2024-08-07 09:48:30] info      UserDir                : C:\Users\Bannd\Downloads\GameDev\Node-RED-SFE-main\NRUserDir
[2024-08-07 09:48:30] info      Flow File              : flows.json
[2024-08-07 09:48:30] info      UI Enabled             : true
[2024-08-07 09:48:31] info      UI Endpoint            : http://127.0.0.1:1880/
[2024-08-07 09:48:31] info      Copy into browser to play: : http://127.0.0.1:1880/dashboard/title

At the top I included logging for the variable UserDirPath

line 64 of node-rd.js:

const isEmbedded = checkFileExists(userDirPath);

Cant possibly equal true because that folder location doesn't exist on my system.

Also in your screenshot above the namespace has been replaced but as you can see in mine it is still stuck as '{SFE_PROJECT_DIR}'.

Ignore the fact the place holder isnā€™t updated - it is not updated while in design time - and you currently are according to your log.

isEmbedded, userDirPath, ns - is not relevant, until a build has taken place and you are executing that build.

Those parameters are for when the SFE is being executed after a build (build/dist)

userDirPath is used to check if it can find the NRUserDir - and this references an internal VFS path - so not relevant during design time

Sorry, not sure what else to say, I fail to get any errors/issues on widows - and can successfully execute an embedded flow - when following the guide, and executing the resulting output.

Iā€™m away for 3 days - so canā€™t investigate any edge cases if this one is :person_shrugging: