linny-r 1.4.1 → 1.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -25,7 +25,7 @@ Technical documentation will be developed on GitHub: https://github.com/pwgbots/
25
25
  Linny-R is developed as a JavaScript package, and requires that **Node.js** is installed on your computer.
26
26
  This software can be downloaded from <a href="https://nodejs.org" target="_blank">https://nodejs.org</a>.
27
27
  Make sure that you choose the correct installer for your computer.
28
- Linny-R is developed using the _current_ release. Presently (June 2023) this is 20.3.0.
28
+ Linny-R is developed using the _current_ release. Presently (August 2023) this is 20.5.1.
29
29
 
30
30
  Run the installer and accept the default settings.
31
31
  There is **no** need to install the optional _Tools for Native Modules_.
@@ -36,33 +36,33 @@ Verify the installation by typing:
36
36
 
37
37
  ``node --version``
38
38
 
39
- The response should be the version number of Node.js, for example: v20.3.0.
39
+ The response should be the version number of Node.js, for example: v20.5.1.
40
40
 
41
41
  ## Installing Linny-R
42
42
  It is advisable to install Linny-R in a directory on your computer, not in a cloud.
43
- In this installation guide, the path to this directory is denoted by `WORKING_DIRECTORY`,
43
+ In this installation guide, the path to this directory is denoted by `Linny-R`,
44
44
  so in all commands you should replace this with the actual directory path.
45
- On a Windows machine you may choose something like `C:\Users\xyz\Documents\Linny-R`,
46
- and on a macOS machine probably `/Users/xyz/Linny-R`.
45
+ On a Windows machine the suggested path is `C:\Users\(your user name)\Documents\Linny-R`,
46
+ and on a macOS machine `/Users/(your user name)/Linny-R`.
47
47
 
48
48
  To install Linny-R in this directory, first create it:
49
49
 
50
- ``mkdir WORKING_DIRECTORY``
50
+ ``mkdir Linny-R``
51
51
 
52
52
  then change to it:
53
53
 
54
- ``cd WORKING_DIRECTORY``
54
+ ``cd Linny-R``
55
55
 
56
56
  and then type at the command line prompt:
57
57
 
58
58
  ``npm install --prefix . linny-r``
59
59
 
60
- **NOTE:** The spacing around the dot is important.
60
+ **NOTE:** The spacing around the dot is important. Type the command in lower case.
61
61
 
62
- After installation has completed, `WORKING_DIRECTORY` should have this directory tree structure:
62
+ After installation has completed, `Linny-R` should have this directory tree structure:
63
63
 
64
64
  <pre>
65
- WORKING_DIRECTORY
65
+ Linny-R
66
66
  |
67
67
  +-node_modules
68
68
  |
@@ -81,7 +81,7 @@ WORKING_DIRECTORY
81
81
  +-sounds
82
82
  </pre>
83
83
 
84
- `WORKING_DIRECTORY` should contain two JSON files `package.json` and `package-lock.json`
84
+ `Linny-R` should contain two JSON files `package.json` and `package-lock.json`
85
85
  that should **not** be removed, or you will have to re-install Linny-R. It should also contain
86
86
  a script file to facilitate (single click) launch: on a macOS machine the shell script `linny-r.command`,
87
87
  on a Windows machine the batch script `linny-r.bat`. By default, this script file contains
@@ -109,17 +109,18 @@ The sub-directories of `static` contain files that are served to the browser by
109
109
 
110
110
  ## Configuring the MILP solver
111
111
 
112
- Linny-R presently supports two MILP solvers: Gurobi and LP_solve.
113
- Gurobi is _considerably_ more powerful than the open source LP_solve solver that has powered Linny-R since 2009,
114
- but it requires a license.
112
+ Linny-R presently supports four MILP solvers: Gurobi, CPLEX, SCIP and LP_solve.
113
+ Gurobi and CPLEX are _considerably_ more powerful than the open source solvers SCIP and LP_solve,
114
+ but they require a license.
115
115
  Academic licenses can be obtained by students and staff of eligible institutions.
116
116
 
117
117
  #### Installing Gurobi
118
118
 
119
- The software you need to install is '''Gurobi Optimizer'''.
119
+ The software you need to install is **Gurobi Optimizer**.
120
120
  More information on how to obtain a license, and instructions for installing
121
121
  Gurobi on your computer can be obtained via this URL:
122
- https://www.gurobi.com/academia/academic-program-and-licenses/
122
+ <a href="https://www.gurobi.com/academia/academic-program-and-licenses/"
123
+ target="_blank">https://www.gurobi.com/academia/academic-program-and-licenses/</a>
123
124
 
124
125
  When running a model, Linny-R will try to execute the command line application `gurobi_cl`.
125
126
  It will look for this application in the directory specified in the environment variable PATH on your computer.
@@ -127,23 +128,51 @@ It will look for this application in the directory specified in the environment
127
128
  When installing Gurobi, please accept the default file locations that are proposed by the installer.
128
129
  Then do **not** move Gurobi files to some other directory, as this is bound to cause problems.
129
130
 
131
+ #### Installing CPLEX
132
+
133
+ The software you need to install is **CPLEX**.
134
+ More information on how to obtain a license, and instructions for installing
135
+ CPLEX on your computer can be obtained via this URL:
136
+ <a href="https://www.ibm.com/products/ilog-cplex-optimization-studio"
137
+ target="_blank">https://www.ibm.com/products/ilog-cplex-optimization-studio</a>
138
+
139
+ When running a model, Linny-R will try to execute the command line application `cplex`.
140
+ It will look for this application in the directory specified in the environment variable PATH
141
+ or more specifically in the environment variable CPLEX_STUDIO_BINARIES<em>nnnn</em>
142
+ (where _nnnn_ denotes the CPLEX version number) on your computer.
143
+
144
+ When installing CPLEX, please accept the default file locations that are proposed by the installer.
145
+ Then do **not** move CPLEX files to some other directory, as this is bound to cause problems.
146
+
147
+ #### Installing SCIP
148
+
149
+ The SCIP software is open source. Instructions for installation can be found via this URL:
150
+ <a href="https://scipopt.org/doc/html/INSTALL.php" target="_blank">https://scipopt.org/doc/html/INSTALL.php</a>
151
+
152
+ When running a model, Linny-R will try to execute the command line application `scip`.
153
+ It will look for this application in the directory specified in the environment variable PATH on your computer.
154
+
155
+ When installing SCIP, please accept the default file locations that are proposed by the installer.
156
+ Then do **not** move SCIP files to some other directory, as this is bound to cause problems.
157
+
130
158
  #### Installing LP_solve
131
159
 
132
160
  The LP_solve software is open source and can be downloaded via this URL:
133
- https://sourceforge.net/projects/lpsolve
161
+ <a href="https://sourceforge.net/projects/lpsolve" target="_blank">https://sourceforge.net/projects/lpsolve</a>
134
162
 
135
- To facilitate installation, the executable files for Windows and macOS can be downloaded from the Linny-R website at Delft University of Technology:
136
- https://sysmod.tbm.tudelft.nl/linny-r/lp_solve
163
+ To facilitate installation, the executable files for Windows and macOS can be downloaded from the Linny-R website
164
+ at Delft University of Technology:
165
+ <a href="https://sysmod.tbm.tudelft.nl/linny-r/lp_solve" target="_blank">https://sysmod.tbm.tudelft.nl/linny-r/lp_solve</a>
137
166
 
138
167
  There you will find links to download LP_solve applications that have been compiled for different platforms.
139
168
  If you do not know which platform to choose, run Linny-R as described below, and the platform will be listed in its output.
140
169
  If no matching LP_solve version is listed, you can try to compile the software from its source.
141
170
  How to do this is explained on the page "Installing LP_solve on a Mac" on the Linny-R documentation site:
142
- https://linny-r.info
171
+ <a href="https://linny-r.info" target="_blank">https://linny-r.info</a>
143
172
 
144
173
  When you have downloaded the file (just `lp_solve` for macOS, `lp_solve.exe` for Windows),
145
- you must copy or move this file to your `WORKING_DIRECTORY`,
146
- as this is where Linny-R will look for it when it does not find Gurobi.
174
+ you must copy or move this file to your `Linny-R` directory,
175
+ as this is where Linny-R will look for it when it does not find one of the other solvers.
147
176
 
148
177
  On a macOS machine, you must then make the file `lp_solve` executable.
149
178
  Open Terminal and change to your Linny-R directory, and then type:
@@ -163,15 +192,15 @@ If you reach this stage, Linny-R will be able to run LP_solve.
163
192
 
164
193
  ## Running Linny-R
165
194
 
166
- Open the Command Line Interface (CLI) of your computer, change to your `WORKING_DIRECTORY` and type:
195
+ Open the Command Line Interface (CLI) of your computer, change to your Linny-R directory and type:
167
196
 
168
197
  ``node node_modules/linny-r/server launch``
169
198
 
170
199
  This response should be something similar to:
171
200
 
172
201
  <pre>
173
- Node.js server for Linny-R version 1.2.1
174
- Node.js version: v20.3.0
202
+ Node.js server for Linny-R version 1.4.2
203
+ Node.js version: v20.5.1
175
204
  ... etc.
176
205
  </pre>
177
206
 
@@ -181,9 +210,9 @@ The Linny-R GUI should show in your browser window,
181
210
  while in the CLI you should see a long series of server log messages like:
182
211
 
183
212
  <pre>
184
- [2023-04-29 22:55:17] Static file: /index.html
185
- [2023-04-29 22:55:17] Static file: /scripts/iro.min.js
186
- [2023-04-29 22:55:17] Static file: /images/open.png
213
+ [2023-08-29 22:55:17] Static file: /index.html
214
+ [2023-08-29 22:55:17] Static file: /scripts/iro.min.js
215
+ [2023-08-29 22:55:17] Static file: /images/open.png
187
216
  ... etc.
188
217
  </pre>
189
218
 
@@ -222,7 +251,7 @@ Optionally, you can add more arguments to the `node` command:
222
251
  dpi=[number] to overrule the default resolution (300 dpi) for Inkscape
223
252
  launch to automatically launch Linny-R in your default browser
224
253
  port=[number] to overrule the default port number (5050)
225
- solver=[name] to overrule the default sequence (Gurobi, LP_solve)
254
+ solver=[name] to overrule the default sequence (Gurobi, CPLEX, SCIP, LP_solve)
226
255
  workspace=[path] to overrule the default path for the user directory
227
256
  </pre>
228
257
 
@@ -237,14 +266,14 @@ The dialog that then appears will allow you to go to the sub-folder `node_module
237
266
  where you should select the file `linny-r.ico`.
238
267
  Finally, rename the shortcut to `Linny-R` and move or copy it to your desktop.
239
268
 
240
- On a macOS machine, open _Terminal_ and change to your Linny-R directory, and then type:
269
+ On a macOS machine, open Terminal and change to your Linny-R directory, and then type:
241
270
 
242
271
  ``chmod +x linny-r.command``
243
272
 
244
273
  to make the script file executable.
245
- To set the icon, open the folder that contains the file `linny-r.command`,
274
+ To set the icon, use Finder to open the folder that contains the file `linny-r.command`,
246
275
  click on its icon (which still is plain) and open the _Info dialog_ by pressing ``Cmd+I``.
247
- Then open your Linny-R folder in _Finder_, change to the sub-folder `node_modules/linny-r/static/images`,
276
+ Then open your Linny-R folder in Finder, change to the sub-folder `node_modules/linny-r/static/images`,
248
277
  and from there drag/drop the file `linny-r.icns` on the icon shown in the top left corner of the _Info dialog_.
249
278
 
250
279
  ## User workspace
@@ -254,13 +283,17 @@ The sub-directories of this directory `user` are used by Linny-R to store files.
254
283
 
255
284
  * `autosave` will contain models that have been _auto-saved_
256
285
  * `channel` and `callback` will be used to interact with Linny-R via its _Receiver_
286
+ * `data` will be used by the _Dataset Manager_ to locate datasets for which a path
287
+ has been specified
257
288
  * `diagrams` will be used to render Scalable Vector Graphics (SVG) files as
258
289
  Portable Network Graphics (PNG) using Inkscape (if installed)
259
290
  * `modules` will contain models stored in the `local host` _repository_
291
+ * `reports` will contain text files with time series data and statistics in tab-separated
292
+ format that can be imported or copy/pasted into Excel
260
293
  * `solver` will contain the files that are exchanged with the Mixed Integer Linear Programming (MILP) solver
261
294
  (the names of the files that will appear in this directory may vary, depending on the MILP-solver you use)
262
295
 
263
- By default, the `user` directory is created in your `WORKING_DIRECTORY`.
296
+ By default, the `user` directory is created in your `Linny-R` directory.
264
297
  You can overrule this by specifying the path to another directory when you start the server.
265
298
  Note that doing this will create a new, empty workspace (the directories listed above)
266
299
  in the specified path. It will **not** affect or duplicate information from existing workspaces.
@@ -281,7 +314,8 @@ Meanwhile, the browser will have opened a new tab that will be "waiting" for thi
281
314
  If rendering was successful, the image will appear in this browser tab;
282
315
  if rendering failed, the original SVG image will be shown.
283
316
 
284
- To install Inkscape, please look here: https://inkscape.org/release
317
+ To install Inkscape, please look here:
318
+ <a href="https://inkscape.org/release" target="_blank">https://inkscape.org/release</a>
285
319
 
286
320
  Linny-R will automatically detect whether Inkscape is installed by searching for it in the environment variable PATH on your computer.
287
321
  On a macOS computer, Linny-R will look for Inkscape in /Applications/Inkscape.app/Contents/MacOS.
@@ -293,7 +327,7 @@ Please check whether you need to do this yourself.
293
327
 
294
328
  The console-only version of Linny-R allows you to run a Linny-R model without a web browser.
295
329
  This may be useful when you want run models from a script (shell script, Python, ...).
296
- If you open a CLI box, change to your `WORKING_DIRECTORY`, and then type:
330
+ If you open a CLI box, change to your `Linny-R` directory, and then type:
297
331
 
298
332
  ``node node_modules/linny-r/console`` _(on Windows, use backslashes)_
299
333
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linny-r",
3
- "version": "1.4.1",
3
+ "version": "1.4.3",
4
4
  "description": "Executable graphical language with WYSIWYG editor for MILP models",
5
5
  "main": "server.js",
6
6
  "scripts": {
package/server.js CHANGED
@@ -87,9 +87,10 @@ function getVersionInfo() {
87
87
  info.current_time = new Date(Date.parse(obj.time[info.current]));
88
88
  info.up_to_date = info.current === info.latest;
89
89
  } catch(err) {
90
- // `latest` = 0 indicates that version check failed
90
+ // `latest` = 0 indicates that version check failed.
91
91
  info.latest = 0;
92
92
  }
93
+ clearNewerVersion();
93
94
  if(!info.latest) {
94
95
  console.log(connectionErrorText('Could not connect to https://registry.npmjs.org/'));
95
96
  } else if(!info.up_to_date) {
@@ -145,12 +146,13 @@ const SOLVER = new MILPSolver(SETTINGS, WORKSPACE);
145
146
  // Create launch script
146
147
  createLaunchScript();
147
148
 
148
- // Create the HTTP server
149
+ // Create the HTTP server.
149
150
  const SERVER = http.createServer((req, res) => {
150
151
  const u = new URL(req.url, 'http://127.0.0.1:' + SETTINGS.port);
151
- // When POST, first get all the full body
152
+ // When POST, first get all the full body.
152
153
  if(req.method === 'POST') {
153
154
  let body = '';
155
+ // @@TO DO: For big data requests, string may become too long.
154
156
  req.on('data', (data) => body += data);
155
157
  req.on('end', () => processRequest(req, res, u.pathname, body));
156
158
  } else if(req.method === 'GET') {
@@ -198,7 +200,7 @@ function logAction(msg) {
198
200
 
199
201
  function autoCheck(res) {
200
202
  // Serves a string with the current version number plus info on a
201
- // newer release if this is available
203
+ // newer release if this is available.
202
204
  let check = VERSION_INFO.current + '|';
203
205
  if(VERSION_INFO.up_to_date) {
204
206
  check += 'up-to-date';
@@ -208,22 +210,46 @@ function autoCheck(res) {
208
210
  servePlainText(res, check);
209
211
  }
210
212
 
211
- // HTML page to show then the server is shut down by the user
213
+ function setNewerVersion() {
214
+ // Creates the file "newer_version" in the working directory, so that
215
+ // when the server is run from the standard batch script it will detect
216
+ // that an update is required.
217
+ const nvf = path.join(WORKING_DIRECTORY, 'newer_version');
218
+ try {
219
+ fs.writeFileSync(nvf, VERSION_INFO.latest);
220
+ } catch(err) {
221
+ console.log('WARNING: Failed to create file:', nvf);
222
+ console.log(err);
223
+ }
224
+ }
225
+
226
+ function clearNewerVersion() {
227
+ // Forestalls auto-update by deleting the file "newer_version" that may
228
+ // have been created at start-up from the working directory.
229
+ try {
230
+ fs.unlink(path.join(WORKING_DIRECTORY, 'newer_version'));
231
+ } catch(err) {
232
+ // No action, as error is nogt fatal.
233
+ }
234
+ }
235
+
236
+ // HTML page to show when the server is shut down by the user.
212
237
  // NOTE: on a macOS machine, this is slightly more work
213
- const
214
- OS_TEXT = (PLATFORM === 'darwin' ? [
238
+ const OS_TEXT = {close: '', reopen: ''};
239
+ if(PLATFORM === 'darwin') {
240
+ OS_TEXT.close =
215
241
  `<p>You can close the <em>Terminal</em> window that shows
216
242
  <tt>[Process Terminated]</tt> at the bottom.
217
- </p>`,
243
+ </p>`;
244
+ OS_TEXT.reopen =
218
245
  `open <em>Terminal</em> again, change to your Linny-R directory by typing:
219
246
  </p>
220
247
  <p><code>cd ${WORKING_DIRECTORY}</code></p>
221
- <p>`
222
- ] : [
223
- '',
224
- 'switch to your <em>Command Prompt</em> window '
225
- ]),
226
- SHUTDOWN_MESSAGE = `<!DOCTYPE html>
248
+ <p>`;
249
+ } else {
250
+ OS_TEXT.reopen = 'switch to your <em>Command Prompt</em> window ';
251
+ }
252
+ const SHUTDOWN_MESSAGE = `<!DOCTYPE html>
227
253
  <html lang="en-US">
228
254
  <head>
229
255
  <meta http-equiv="content-type" content="text/html; charset=UTF-8">
@@ -243,13 +269,8 @@ const
243
269
  </style>
244
270
  </head>
245
271
  <body>
246
- <h3>Linny-R server (127.0.0.1) is shutting down</h3>` + OS_TEXT[0] + `
247
- <p>To restart Linny-R, ` + OS_TEXT[1] + ` and then at the prompt` +
248
- (VERSION_INFO.up_to_date ? '' : `
249
- first type:</p>
250
- <p><code>npm update linny-r</code><p>
251
- to upgrade to Linny-R version ${VERSION_INFO.latest}, and then`) +
252
- ` type:</p>
272
+ <h3>Linny-R server (127.0.0.1) is shutting down</h3>${OS_TEXT.close}
273
+ <p>To restart Linny-R, ${OS_TEXT.reopen} and then at the prompt type:</p>
253
274
  <p><code>node node_modules${path.sep}linny-r${path.sep}server</code></p>
254
275
  <p>
255
276
  Then switch back to this window, and click this
@@ -1064,7 +1085,7 @@ function rcvrReport(res, rpath, rfile, run, data, stats, log) {
1064
1085
  }
1065
1086
  }
1066
1087
  }
1067
- if(n) console.log(n + 'report file' + (n > 1 ? 's' : '') + 'purged');
1088
+ if(n) console.log(n + ' report file' + (n > 1 ? 's' : '') + ' purged');
1068
1089
  } catch(err) {
1069
1090
  // Log error, but do not abort.
1070
1091
  console.log(err);
@@ -1207,16 +1228,20 @@ function processRequest(req, res, cmd, data) {
1207
1228
  // NOTE: `data` is a string of form field1=value1&field2=value2& ... etc.
1208
1229
  // regardless of the request method (GET or POST)
1209
1230
  if(permittedFile(cmd)) {
1210
- // Path contains valid MIME file type extension => serve if allowed
1231
+ // Path contains valid MIME file type extension => serve if allowed.
1211
1232
  serveStaticFile(res, cmd);
1212
- } else if(cmd === '/solver/') {
1233
+ return;
1234
+ }
1235
+ // Be permissive w.r.t. leading and trailing slashes.
1236
+ cmd = cmd.replace(/^\/+/, '').replace(/\/+$/, '');
1237
+ if(cmd === 'solver') {
1213
1238
  const
1214
1239
  sp = new URLSearchParams(data),
1215
1240
  action = sp.get('action');
1216
- // NOTE: on remote servers, solver actions require authentication
1241
+ // NOTE: On remote servers, solver actions require authentication.
1217
1242
  if(action === 'logon') {
1218
- // No authentication -- simply return the passed token, "local host" as
1219
- // server name, and the identifier of the solver
1243
+ // No authentication -- simply return the passed token, "local host"
1244
+ // as server name, and the identifier of the solver.
1220
1245
  serveJSON(res,
1221
1246
  {token: 'local host', server: 'local host', solver: SOLVER.id});
1222
1247
  } else if(action === 'png') {
@@ -1229,19 +1254,40 @@ function processRequest(req, res, cmd, data) {
1229
1254
  console.log(msg);
1230
1255
  serveJSON(res, {error: msg});
1231
1256
  }
1232
- } else if(cmd === '/shutdown') {
1233
- // Shut down this server
1257
+ } else if(cmd === 'shutdown') {
1258
+ // Shut down this server WITHOUT updating, and show page with
1259
+ // "shut down" message and restart button.
1260
+ clearNewerVersion();
1234
1261
  serveHTML(res, SHUTDOWN_MESSAGE);
1235
1262
  SERVER.close();
1236
- } else if(cmd === '/auto-check') {
1263
+ } else if(cmd === 'version') {
1264
+ logAction('HERE version = ' + VERSION_INFO.current);
1265
+ servePlainText(res, 'Current version is ' + VERSION_INFO.current);
1266
+ } else if(cmd === 'update') {
1267
+ // Shut down this server silently. When the server was started from
1268
+ // a batch script, this will update via npm, and then restart.
1269
+ // NOTE: Self-protect against overwriting development scripts.
1270
+ if(WORKING_DIRECTORY.indexOf('LTR3') >= 0) {
1271
+ servePlainText(res, 'No version update in development environment');
1272
+ } else {
1273
+ setNewerVersion();
1274
+ servePlainText(res, 'Installing Linny-R version ' + VERSION_INFO.latest);
1275
+ SERVER.close();
1276
+ }
1277
+ } else if(cmd === 'no-update') {
1278
+ // Remove file "newer_version" so no update will take place when
1279
+ // server is shut down.
1280
+ clearNewerVersion();
1281
+ servePlainText(res, 'No update to version ' + VERSION_INFO.latest);
1282
+ } else if(cmd === 'auto-check') {
1237
1283
  autoCheck(res);
1238
- } else if(cmd === '/autosave/') {
1284
+ } else if(cmd === 'autosave') {
1239
1285
  autoSave(res, new URLSearchParams(data));
1240
- } else if(cmd === '/repo/') {
1286
+ } else if(cmd === 'repo') {
1241
1287
  repo(res, new URLSearchParams(data));
1242
- } else if(cmd === '/load-data/') {
1288
+ } else if(cmd === 'load-data') {
1243
1289
  loadData(res, (new URLSearchParams(data)).get('url'));
1244
- } else if(cmd === '/receiver/') {
1290
+ } else if(cmd === 'receiver') {
1245
1291
  receiver(res, new URLSearchParams(data));
1246
1292
  } else {
1247
1293
  serveJSON(res, {error: `Unknown Linny-R request: "${cmd}"`});
@@ -1249,7 +1295,7 @@ function processRequest(req, res, cmd, data) {
1249
1295
  }
1250
1296
 
1251
1297
  function servePlainText(res, msg) {
1252
- // Serve string `msg` as plain text
1298
+ // Serve string `msg` as plain text.
1253
1299
  res.setHeader('Content-Type', 'text/plain');
1254
1300
  res.writeHead(200);
1255
1301
  res.end(msg);
@@ -1698,28 +1744,53 @@ function createLaunchScript() {
1698
1744
  // Creates platform-specific script with Linny-R start-up command
1699
1745
  const lines = [
1700
1746
  '# The first line (without the comment symbol #) should be like this:',
1701
- '# cd ',
1747
+ '',
1702
1748
  '',
1703
1749
  '# Then this command to launch the Linny-R server should work:',
1704
- 'node ' + path.join('node_modules', 'linny-r', 'server') + ' launch'
1750
+ '',
1751
+ '# After shut-down, check whether new version should be installed:'
1705
1752
  ];
1753
+ lines[2] = 'cd ' + WORKING_DIRECTORY;
1706
1754
  let sp;
1707
1755
  if(PLATFORM.startsWith('win')) {
1708
1756
  sp = path.join(WORKING_DIRECTORY, 'linny-r.bat');
1709
- lines[1] += 'C:\\path\\to\\main\\Linny-R\\directory';
1757
+ lines.push(
1758
+ ':loop',
1759
+ 'if exist newer_version (',
1760
+ ' del newer_version',
1761
+ ' npm update linny-r',
1762
+ ' node node_modules\\linny-r\\server',
1763
+ ' goto loop',
1764
+ ')');
1765
+ lines[1] = '# cd C:\\path\\to\\main\\Linny-R\\directory';
1766
+ lines[4] = 'node node_modules\\linny-r\\server launch';
1710
1767
  } else {
1711
1768
  sp = path.join(WORKING_DIRECTORY, 'linny-r.command');
1712
- lines[1] += '/path/to/main/Linny-R/directory';
1769
+ lines.push(
1770
+ 'while test -f newer_version; do',
1771
+ ' unlink newer_version',
1772
+ ' npm update linny-r',
1773
+ ' node node_modules/linny-r/server',
1774
+ 'done');
1775
+ lines[1] = '# cd /path/to/main/Linny-R/directory';
1776
+ lines[4] = 'node node_modules/linny-r/server launch';
1713
1777
  }
1714
- lines[2] = 'cd ' + WORKING_DIRECTORY;
1715
1778
  try {
1779
+ let make_script = false,
1780
+ code = lines.join(os.EOL);
1781
+ if(PLATFORM.startsWith('win')) code = code.replaceAll('#', '::');
1716
1782
  try {
1717
1783
  fs.accessSync(sp);
1784
+ // Only write the script content if the file has not been customized
1785
+ // by the user...
1786
+ const data = fs.readFileSync(sp, 'utf-8');
1787
+ make_script = code.indexOf(data) >= 0;
1718
1788
  } catch(err) {
1719
- // Only write the script content if the file it does not yet exist
1789
+ // ... or if it does not exist yet.
1790
+ make_script = true;
1791
+ }
1792
+ if(make_script) {
1720
1793
  console.log('Creating launch script:', sp);
1721
- let code = lines.join(os.EOL);
1722
- if(PLATFORM.startsWith('win')) code = code.replaceAll('#', '::');
1723
1794
  fs.writeFileSync(sp, code, 'utf8');
1724
1795
  }
1725
1796
  } catch(err) {