TotallyInformation / node-red-contrib-uibuilder

Easily create data-driven web UI's for Node-RED using any (or no) front-end framework.
https://totallyinformation.github.io/node-red-contrib-uibuilder/#/
Apache License 2.0
452 stars 86 forks source link

Installing bootstrap theme via Manage Frontend Libraries fails #67

Closed CarstenMaul closed 5 years ago

CarstenMaul commented 5 years ago

Hi,

I try to install the bootstrap theme startbootstrap-sb-admin-2 https://github.com/BlackrockDigital/startbootstrap-sb-admin-2 via "Manage Frontend Libraries" but no success after a lot of hours of trying and debugging.

I enabled the log in uibuilder.js uibnpmmanage but the stdout of npm looks fine, still the result is "[uibuilder/uibnpmmanage] Admin API. npm command failed. npm install for package startbootstrap-sb-admin-2"

I assume I have to successfully install it via uibuilder so ExpressJS gets knowledge about the themes files.

manual installation via npm works fine by the way, unfortunately uibuilder doesn´t know about the theme after its installation.

I kindly ask for help, I spend alot of hours digging into this but now im am tired and frustrated ;-)

CarstenMaul commented 5 years ago

I tried this on a Windows and a Linux hosted Node-RED, same result on both

TotallyInformation commented 5 years ago

Hi, sorry you've had some issues with this. I can replicate the issue but I am not sure why the end of the installation is failing. I will need to do some more digging.

It is possible/probable that this is another edge-case with npm which is an abysmal tool with several anti-patterns that make it virtually impossible to be certain that an npm command as succeeded. I expect that this is the issue but I will have to add some additional logging to make sure.

Good news however is that you don't have to rely on the npm admin interface in uibuilder. I've allowed for manual installs. Possibly the documentation for it has not been surfaced properly, sorry for that.

After doing a manual npm install into your userDir folder (probably ~/.node-red/), go into the ~/.node-red/uibuilder/.config folder and edit the file called packageList.json.

Simply add the package name to the end of the JSON list, make sure that you add it as valid JSON though or you will get an error.

uibuilder uses this file and updates it itself but you can also edit it manually. After editing manually, you may have to restart Node-RED.

Urm, not such good news actually. Looks like uibuilder is removing that node from the list when Node-RED restarts which shouldn't happen. It won't even accept it if I put it in the master list. which is strange indeed.

Sorry, but it looks like more investigation is needed. I probably won't be able to do much until at least tomorrow evening (24hrs), maybe not till the weekend. But I will try to fix as soon as possible.

CarstenMaul commented 5 years ago

So the npm has NOT failed because the error condition ist not set. The error I get "npm command failed" is thrown because the success condition ist not true.

        // Run the command - against the correct instance or userDir (cwd)
        var output = [], errOut = null, success = false
        child_process.exec(command, {'cwd': folder}, (error, stdout, stderr) => {
            if ( error ) {
                log.warn(`[uibuilder/uibnpmmanage] Admin API. ERROR Running npm ${params.cmd} for package ${params.package}`, error)
            }

....
            }

            if (success === true) {
                log.info(`[uibuilder/uibnpmmanage] Admin API. npm command success. npm ${params.cmd} for package ${params.package}`)
            } else {
                log.error(`[uibuilder/uibnpmmanage] Admin API. npm command failed. npm ${params.cmd} for package ${params.package}`, jResult)
            }
....
        })

The success condition depends on the output of uiblib.checkInstalledPackages, which is evaluated here:

            // Update the packageList
            uib.installedPackages = uiblib.checkInstalledPackages(params.package, uib, userDir, log)
...
                case 'install': {
                    // package name should exist in uib.installedPackages
                    if ( uib.installedPackages.hasOwnProperty(params.package) ) success = true
                    if (success === true) {
                        // Add an ExpressJS URL
                        uiblib.servePackage(params.package, uib, userDir, log, app)
                    }
                    break

So my question is: why you don´t just rely on the exit code of NPM (which is returned in the way that if error is set exit code > 0) and perform uiblib.servePackage if child_process.exec !error?

TotallyInformation commented 5 years ago

Because npm sometimes returns exit codes >0 when it has actually installed OK. This can happen when you have other packages that are creating errors.

Anyway, this is beside the point because that isn't the problem at all. The problem is that require.resolve() which is used to find the location of the package is unable to resolve 'startbootstrap-sb-admin-2' which you can test for yourself with a small node.js script run from your userDir.

I suspect that the name may be too long but I need to do some more digging.

Bottom line is that tilib.findPackage cannot find the installed package even though it is absolutely there. That function uses the recommended way of finding an installed package so there is an upstream bug somewhere.

> const x = require.resolve('startbootstrap-sb-admin-2')
{ Error: Cannot find module 'startbootstrap-sb-admin-2'
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:582:15)
    at Function.resolve (internal/modules/cjs/helpers.js:30:19) code: 'MODULE_NOT_FOUND' }
> let y2 = require.resolve('sprintf-js')
undefined
> y2
'C:\\src\\nr\\data\\node_modules\\sprintf-js\\src\\sprintf.js'
C:\src\nr\data
λ  ls ./node_modules/start*

    Directory: C:\src\nr\data\node_modules

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----       2019-09-12     21:54                startbootstrap-sb-admin-2
TotallyInformation commented 5 years ago

Issue raised against node:

https://github.com/nodejs/node/issues/29549

TotallyInformation commented 5 years ago

So the problem apparently is that require.resolve only works if the package defines a main entry point.

startbootstrap-sb-admin-2 does not do that which is why uibuilder cannot know that it is installed and available.

I am going to have to try and work out an alternative, more reliable method for discovery of the package folder.

TotallyInformation commented 5 years ago

My plan at the moment is to add an additional check to the tilib.findPackage function that, if all else fails, it will check the filing system manually.

I suspect that will still not find every edge case (I don't know that it will find a module with no main defined that is @ scoped).

But as far as I can tell, there is no fully reliable method of finding the installation folder of an installed package. If you look in the code for the npm ls command for example, you will find that it is massively complex and still doesn't return the folder. It also typically takes several seconds to execute even on a fast device.

TotallyInformation commented 5 years ago

I have a work-around now. I'll be publishing shortly, please look for uibuilder v2.0.3 and update accordingly.

CarstenMaul commented 5 years ago

Thank you very much, the workaround works. Thanks for the fast response! but I the install dialog never finishes, but when I reload the node-red page the modul is shown as installed. Minor issue, because at the end it works :-)