-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathnode-cli
More file actions
executable file
·333 lines (293 loc) · 20.3 KB
/
Copy pathnode-cli
File metadata and controls
executable file
·333 lines (293 loc) · 20.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
#!/usr/bin/env bash
printf "param[]='%s'\n" "$@"
source /usr/lib/bg_core.sh
# Command node-cli
# usage: node-cli [--cjs] [--guiMain|--guiWin] [<options>] <scriptFile>
# usage: node-guiMain [<options>] <scriptFile>
# usage: node-guiWin [<options>] <scriptFile>
# usage: node-cli-cjs [<options>] <scriptFile>
# usage: node-guiMain-cjs [<options>] <scriptFile>
# usage: node-guiWin-cjs [<options>] <scriptFile>
# usage: <commonOptions> [-imain|--repl=main|-iwin|--repl=win] [--help|-h] [--version|-v] [--abi|-a] [-r|--require=<module>]
# write script commands in javascript and distribute through linux packages instead of npm or yarn.
#
# These commands are wrappers over either the node (-cli) or electron (-guiMain|-guiWin) executables that are installed in the host's
# system folder. Unlike python, php and other script languages executables, node and electron are purposely hostile to using them
# as command interpreters. This package enables that usecase against the odds.
#
# The main features are...
# * getting node/electron to accept an extensionless filename
# * selecting whether the input file will be parsed as esm (the default for these commands) or cjs
# * shebang lines that do not rely on passing optional argument (which is not universally supportted)
# * being able to write scripts for cli (text only) guiMain (electron's main process) guiWin (an electron window renderer process)
#
# ES Modules and Cjs Modules:
# This wrapper command flips the typical default for javascript modules so that the input module will be treated as an ES module
# by default.
#
# To write a javascript command using cjs syntax (i.e. using require(<modName>) instead of import ... from <modName>, use one of
# the command interpreters that ends in -cjs.
#
# As of the initial release of this package (circa 2022-12), electron is at version 22 and has very little support for ES modules so
# the third party module 'esm' is used to load the main and renderer input modules. The esm parser is lax compared to a compliant ES
# module parser which means scripts written using one of the gui* commands will be allowed to use require() as well as import and
# start using globals without declaring them with const,var or let. There may be other differences as well. A future release will
# use the the native parser so you should try to write compliant code and not rely on the 'extra' features that the esm parser allows.
#
# Dependancies:
# This distribution system creates folder structures during developement and when installed on a target host that do not rely on
# NODE_PATHS.
#
# A javascript command should be written in a package project created and maintained by the bg-dev tool and package. A package project
# can contain 0 or more commands written in javascript and they can coexist with commands written in other languages and any other
# asset type present in the package project.
#
# The package project folder will have a package.json that contains an export for each javascript command and a node_modules/ folder
# that contains the union of all dependencies used by any javascript command in the project.
#
# The developement time project structure is compatible with using npm/yarn and other packaging tools to manage and install dependencies.
# node_modules/ is git ignored and dependencies are tracked and managed in package.json/package-lock.json the same as in the familiar
# nodejs/electron workflow.
#
# In addition to the nodejs workflows, it is also possible to clone a dependency's git repo under node_modules/ and use git submodules
# to track the version that your project uses. Both methods can be used side by side in the same project. The build tools ensure that
# the node_modules/ and package*.json are consistent and up-to-date when the project is built into a deb or rpm package for publishing.
# Using git submodules is convenient when you are a contributor to the dependent project also. This allows you to make changes in
# both projects and then manage the publishing workflow in tandem with the tools found in 'bg-dev sdlc ...'.
#
# Another addition to the nodejs conventions is that your package project can declare dependencies on other deb/rpm packages that
# contain one or more globally installed js modules. These dependencies are resolved with symlinks in your project's node_module/
# folder. The symlinks are mangaed by the bg-dev tool at development time and the package installer scripts when installed on a
# target machine. The strategy for versioning with deb/rpm package is that your package must be compatible with the version of the
# dependent package that is contained in the same repository that you publish your package to. Note that this allows different versions
# of the dependent js module to be installed by different packages. Your project will get the version installed by the package that
# you declare the denpendency on. Pacakage dependencies are maintained in pkgControl/{deb,rpm}Control.
#
# Unlike npm/yarn, the dependencies in node_modules/ become part of the packaged project and will be installed on the target along
# with your code when the package is installed. A folder will be created in the target for your package at
# '/usr/lib/node_modules/<packageName>' (or similar in other OS). In that folder will be the package.json, package-lock.json, the
# node_modules/ folder and each of your javascript command files from your project. Symlinks are created for each command from
# /usr/bin/<jsCommand> pointing to the corresponding command '/usr/lib/node_modules/<packageName>/<jsCommand>.(mjs|cjs)'. Symlinks
# in the node_modules/ subfolder will be created to the /usr/lib/nodes_module/<pkgName> of any packages your project is dependent on
# that contain jsmodules.
#
# Native js modules are not initially supported as depedencies. This is because the bg-dev tools so far do not build architecture specific
# (i.e. compiled) packages. A future release wil lsupport them and at that time, if you include a native module, your package will
# need to be built for each architecture you plan to support.
#
# Shebang Aliases:
# Since not all OS support passing arguments to the interpretter in the shebang line, aliases are provided that call this node-cli
# with the options needed to define how to interpret the script file.
#
# The shebang line in a script must be the very first line in the file with no leading whitespace.
# #!/usr/bin/env node-cli : calls node-cli <scriptFile>
# #!/usr/bin/env node-guiMain : calls node-cli --guiMain <scriptFile>
# #!/usr/bin/env node-guiWin : calls node-cli --guiWin <scriptFile>
# #!/usr/bin/env node-guiHtml : calls node-cli --guiHtml <scriptFile>
# #!/usr/bin/env node-cli-cjs : calls node-cli --cjs <scriptFile>
# #!/usr/bin/env node-guiMain-cjs : calls node-cli --cjs --guiMain <scriptFile>
# #!/usr/bin/env node-guiWin-cjs : calls node-cli --cjs --guiWin <scriptFile>
#
# Security Model:
# The internet browser security model draws the defensive line between the user's machine and the code running in the browser. In
# other words browser security is based on allowing untrusted second party code on the user's machine. That is a major motivation
# of electron separating applications in the main process which is on the user's side and the window renderer process which is
# assumed to be against the user.
#
# The security model of the bg-core family of software is that software delivered through trusted repositories should have a fiduciary
# responsibility to act on behalf of the user putting the user's right to privacy above the software author's interests.
#
# When a script command is written for a renderer process, the BrowserWindow in which the script runs has restrictions turned off
# so that the script can (to the extend allowed by electron) access the user's machine and data. Just like any installed command,
# it is subject to the local machine's security policies in affect for the user the command is running as.
#
# node-guiWin scripts should be aware of this and restrict its use of electron's features to avoid running remote code. This means
# that it is a security violation to load a remote URL into the BrowserWindow created for a node-guiWin script.
#
# As this library matures, I will look for ways get electron to enforce this alternate security policy but in the mean time script
# authors need to be aware that these tools are meant to run only locally installed trusted code.
#
# Params:
# <scriptFile> : the input file to be ran. This is typically passed as a result of the shebang line at the top of the
# script. The specific shebang interpretter used determines whether the file should be javascript or html and whether
# the javascript file runs as the main process or as s renderer process.
# Options:
# --cjs : by default the javascript script file passed into this command will be parsed as ES module syntax supportting
# the modern javascript syntax ('import' instead of 'require()'). This option will allow scripts to be written in
# the older cjs syntax.
# --guiMain : the script is an electron main process which should be passed directly to electron as input. The main process has
# no DOM nor window in which to display a gui but it can create windows with new BrowserWindow(...)
# --guiWin : the script is an electron window process which will be loaded into a new BrowserWindow(...) created by the default
# main process module provided by this package. The main process code provides some common commands available to the
# window/rendered process script via the ipcRenderer.invoke(<channel> [, <args1>..<argsN>])
# -imain|--repl=main : start a repl (aka command prompt) from the main process
# -iwin|--repl=win|--win-interactive : start a repl (aka command prompt) from the renderer process
# -i|--repl|--interactive : start a repl (aka command prompt) from the renderer or main process depending on whether
# <scriptFile> being executed is a main or renderer script.
# --help|-h : display this help
# --version|-v : print the versions of node,electron and then exit
# --abi|-a : print the abi version and exit
# -r|--require=<module> : preload <module>. This can be specified multiple times.
#
##################################################################################################################################
# Process Options to this script
declare jsScriptPath="";
declare modType="mjs";
declare useGUI='';
declare inputType="--input-type=module";
declare electronOpts=();
bgtraceParams "$@"
while [ $# -gt 0 ]; do case $1 in
--guiMain) useGUI="--guiMain"; bgOptionGetOpt val: jsScriptPath "$@" && shift ;;
--guiWin) useGUI="--guiWin" ; bgOptionGetOpt val: jsScriptPath "$@" && shift ;;
--guiHtml) useGUI="--guiHtml"; bgOptionGetOpt val: jsScriptPath "$@" && shift ;;
--cjs)
modType='cjs';
inputType='';
;;
--remote-debugging-port*) bgOptionGetOpt opt: electronOpts "$@" && shift ;;
*) bgOptionsEndLoop "$@" && break; set -- "${bgOptionsExpandedOpts[@]}"; esac; shift;
done
bgtraceParams "$@"
##################################################################################################################################
# When running nodejs (!useGUI) we do not need NODE_PATH anymore and its best not to use it becuase the ES spec says that the ES
# loader cannot respect it
# When running electron however, there is no ES support for the first entry point so we preload the old esm module and we use
# NODE_PATH to find esm and the electronCliApp ourselves and then pass in the fullly qualified path to those.
# The script will have access to the node_modules from its package.
# TODO: I dont think this is needed -- vinstalled project, the script is in the project which has a packag.json and a node_modules/
# When installed, we put all the js scripts and the package.json and node_modules/ in /usr/(local/?)lib/node_modules/<packageName>/
# and then installer puts a symlink in /usr/bin/<scriptName> -> /usr/(local/?)lib/node_modules/<packageName>/<scriptName>
# The installer can also put a foldre symlink in /usr/(local/?)lib/node_modules/<packageName>/<scriptName>/node_modules/globalDeps/ -> /usr/local/lib/
# So it is always able to find its deps in the nodejs way.
if [[ "$bgVinstalledPaths:" == */"$packageName":* ]]; then
bgVinstalledScriptPkgPath="${bgVinstalledPaths%$packageName:*}"
bgVinstalledScriptPkgPath="${bgVinstalledScriptPkgPath##*:}$packageName"
export NODE_PATH+="${NODE_PATH:+:}${bgVinstalledScriptPkgPath%/}/node_modules"
fi
# bg-debugCntr puts the global_node_modules of all vinstalled projects into NODE_PATH.
# TODO: I think only this script needs NODE_PATH so we could modify it to look in the vinstalled paths and then these 2 paths directly
export NODE_PATH+="${NODE_PATH:+:}/usr/local/lib/node_modules"
export NODE_PATH+="${NODE_PATH:+:}/usr/lib/node_modules"
pathGetCanonStr -e "$jsScriptPath" "jsScriptPath"
jsScriptName="${jsScriptPath##*/}"
scriptPkg=''; manifestGetPkgForPath "scriptPkg" "$jsScriptPath"
#echo "scriptPkg='$scriptPkg'"
# TODO: add bg-dev installers for node_modules, global_node_modules and .js files. Maybe we dont need global_node_modules anymore
# installer should create a package.json using the es exports attribute to list all the scripts from this module
##################################################################################################################################
# Find the globally installed node
declare NODE_CMD='node';
! which "$NODE_CMD" &>/dev/null && NODE_CMD='nodejs'
! which "$NODE_CMD" &>/dev/null && assertError "
The executable file 'node' was not found (nor was 'nodejs'). Do you need to install node?.
* the Ubuntu standard package installs it as 'nodejs'
* the nodejs.org instructions installs it as 'node'
The convention for typical nodejs development is to install node in the user's home folder instead of to the system folder
which would require root permissions on many systems. Its common to use 'nvm' (aka node version manager) to install multiple
versions of nodejs to the home folder and for each terminal a different version can be selected.
Note that you should make the deb package depend on a deb node package that delivers the version you need
"
##################################################################################################################################
# Handle useGui
# if its a gui js app...
# 1) the NODE_CMD line below will invoke electron's cli.js which launches a new child process running electron with its embedded node
# 2) the new electron process will run the default app that is bundled with it which expects the first param passed to it to be
# a modules it can run (we pass our default app -- bg-electronCliApp)
# 3) bg-electronCliApp is similar to electron's default app but it adds some features like including a main process stub so that
# the script with the shebang can be a window/renderer process script.
# node <path to electron's cli.js> <pathToOurDefaultElectronApp> [--default-code-type=win] <pathToScript> <args...>
if [ "$useGUI" ]; then
# note that the cli.js file just launches the dist/electron and has some code that does things on close and SGINT,SIGTERM
# but I have not yet seen the pont of that stuff so starting off running the dist/electron cmd directly
ELECTRON_CMD="/usr/local/lib/node_modules/electron/dist/electron"
! which "$ELECTRON_CMD" &>/dev/null && ELECTRON_CMD="/usr/local/lib/node_modules/electron/cli.js"
! which "$ELECTRON_CMD" &>/dev/null && ELECTRON_CMD="/usr/lib/node_modules/electron/cli.js"
if ! which "$ELECTRON_CMD" &>/dev/null; then
declare -a nodePaths; IFS=: read -r nodePaths <<<"$NODE_PATH";
for nodePath in "${nodePaths[@]}"; do
if which "$nodePath/electron/cli.js"; then
ELECTRON_CMD="$nodePath/electron/cli.js"
break;
fi
done
fi
! which "$ELECTRON_CMD" &>/dev/null && assertError -v ELECTRON_CMD "
electron is not installed globally
"
# note that electron does not honor NODE_PATH when looking up the module passed to its default app
# this finds a package.json file that is under the folder bg-electronCliApp/ relative to the first matching NODE_PATH
declare bgDefaultAppPath; findInPaths -R bgDefaultAppPath -r bg-electronCliApp/ package.json "$NODE_PATH"
bgDefaultAppPath="${bgDefaultAppPath%package.json}"
declare esmHelperPath; findInPaths -R esmHelperPath -r esm/ package.json "$NODE_PATH"
esmHelperPath="${esmHelperPath%package.json}"
fi
##################################################################################################################################
# A source map file is a JSON file that contains information about the original source code of a JavaScript file and its compiled
# or minified version. The source map file specifies the mapping between the original source code and the compiled code, which
# allows a debugger or other tool to display the original source code when debugging the compiled code.
#
# Here is an example of the format of a source map file:
# {
# "version": 3,
# "file": "file.min.js",
# "sources": [
# "file.js"
# ],
# "names": [],
# "mappings":
# getting node to treat the file passed to it as an ES module.
# As of 19.x, node provides exactly three ways to determine that input is an ES module.
# see https://nodejs.org/docs/latest/api/packages.html#determining-module-system
# 1) a file has an mjs extension
# (but installed commands should not have extensions)
# 2) its in a folder tree where the nearest "package.json" has type:module
# (we could install a /usr/bin/package.json but then all js scripts would need to be ES and not cjs)
# 3) if --input-type=module is passed to node, strings passed to to --eval, or piped to node via STDIN
# (we can pipe in the contents of our script but we can not pass it the script file -- error msgs are bad)
# In 2020, I had an exchange (I think in a git hub issue) about this and they were adimant not to provide a way to make the file
# to node an ES module.
# We can install an ES module loader hook to affect the first load but that works in 16+ but only with --experimental-loader and a
# warning that can not be suppressed
# Invoke node
case ${useGUI:---textOnly}:$modType in
--textOnly:*)
nodeVersion="$(node --version)"
# TODO: 15.3.0 was first to remove the experimental flag
versionGt "$nodeVersion" "v12.0.0" || assertError -v nodeVersion -v NODE_CMD "
The globally installed node(js) is too old to run this script with the shebang '#!/usr/bin/env node-cli'
JS scripts written with ES module syntax are not supportted with node versions before 12.x
CJS scripts should use the shebang '#!/usr/bin/env node-cli-cjs'
"
# # this is an alternate that I tried. it works but making a .map file to fixup the /tmp path to the actual path seems harder
# ln -snf "$jsScriptPath" "/tmp/$jsScriptName.mjs"
# bgtrace "(esm)" $NODE_CMD ...
# $NODE_CMD --preserve-symlinks-main "/tmp/$jsScriptName.mjs" "$@"
# We stream the contents to node's stdin and use --input-type="module" to tell it that the code on stdin is ES syntax.
# We replace the shebang line (-n+2 removes just one line) with the sourceURL comment which is a simplified verion of a source map
# The '--' tells it that there are no more options and the following '-' says that the input file is on stdin. These two
# arguments are needed so that the rest of the arguments (if any) will be interpretted as arguments passed to the script.
{
echo "//# sourceURL=$jsScriptPath"
tail -n+2 "$jsScriptPath"
} | $NODE_CMD $inputType -- - "$@"
;;
#{ echo "//# sourceURL=$PWD/bg-desktopTiledSSR" tail -n+2 "bg-desktopTiledSSR" } | node --input-type=module -- -
--guiMain:cjs)
$ELECTRON_CMD "${electronOpts[@]}" "$jsScriptPath" "$@"
;;
--guiMain:mjs)
$ELECTRON_CMD "${electronOpts[@]}" "$bgDefaultAppPath" --esmPath "$esmHelperPath" "$jsScriptPath" "$@"
;;
--guiWin:cjs)
$ELECTRON_CMD "${electronOpts[@]}" "$bgDefaultAppPath" --win "$jsScriptPath" "$@"
;;
--guiWin:mjs)
echo $ELECTRON_CMD "${electronOpts[@]}" "$bgDefaultAppPath" --esmPath "$esmHelperPath" --win "$jsScriptPath" "$@"
$ELECTRON_CMD "${electronOpts[@]}" "$bgDefaultAppPath" --esmPath "$esmHelperPath" --win "$jsScriptPath" "$@"
;;
--guiHtml:*)
$ELECTRON_CMD "${electronOpts[@]}" "$bgDefaultAppPath" --html "$jsScriptPath" "$@"
;;
*) assertError -v useGUI -v modType 'unknown combination of ${useGUI:---textOnly}:$modType'
esac