retold-remote 0.0.22 → 0.0.25
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/css/retold-remote.css +87 -20
- package/docs/README.md +59 -11
- package/docs/_sidebar.md +1 -0
- package/docs/collections.md +30 -0
- package/docs/ebook-reader.md +75 -1
- package/docs/image-explorer.md +27 -1
- package/docs/server-setup.md +28 -18
- package/docs/stack-launcher.md +218 -0
- package/docs/ultravisor-integration.md +2 -0
- package/package.json +10 -7
- package/source/Pict-Application-RetoldRemote.js +2 -0
- package/source/RetoldRemote-ExtensionMaps.js +1 -1
- package/source/cli/RetoldRemote-Server-Setup.js +240 -2
- package/source/cli/RetoldRemote-Stack-Launcher.js +387 -0
- package/source/cli/RetoldRemote-Stack-Run.js +41 -0
- package/source/cli/commands/RetoldRemote-Command-Serve.js +129 -54
- package/source/providers/CollectionManager-AddItems.js +166 -0
- package/source/providers/Pict-Provider-GalleryNavigation.js +46 -0
- package/source/providers/keyboard-handlers/KeyHandler-ImageExplorer.js +5 -0
- package/source/providers/keyboard-handlers/KeyHandler-Viewer.js +23 -0
- package/source/server/RetoldRemote-CollectionExportService.js +696 -0
- package/source/server/RetoldRemote-CollectionService.js +5 -0
- package/source/server/RetoldRemote-EbookService.js +194 -3
- package/source/server/RetoldRemote-SubimageService.js +530 -0
- package/source/server/RetoldRemote-ToolDetector.js +50 -0
- package/source/server/RetoldRemote-UltravisorOperations.js +6 -6
- package/source/views/MediaViewer-EbookViewer.js +419 -1
- package/source/views/MediaViewer-PdfViewer.js +963 -0
- package/source/views/PictView-Remote-CollectionsPanel.js +166 -0
- package/source/views/PictView-Remote-ImageExplorer.js +606 -1
- package/source/views/PictView-Remote-ImageViewer.js +2 -2
- package/source/views/PictView-Remote-Layout.js +12 -0
- package/source/views/PictView-Remote-MediaViewer.js +83 -25
- package/source/views/PictView-Remote-SubimagesPanel.js +353 -0
- package/web-application/css/retold-remote.css +87 -20
- package/web-application/docs/README.md +59 -11
- package/web-application/docs/_sidebar.md +1 -0
- package/web-application/docs/collections.md +30 -0
- package/web-application/docs/ebook-reader.md +75 -1
- package/web-application/docs/image-explorer.md +27 -1
- package/web-application/docs/server-setup.md +28 -18
- package/web-application/docs/stack-launcher.md +218 -0
- package/web-application/docs/ultravisor-integration.md +2 -0
- package/web-application/retold-remote.js +399 -45
- package/web-application/retold-remote.js.map +1 -1
- package/web-application/retold-remote.min.js +13 -12
- package/web-application/retold-remote.min.js.map +1 -1
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retold Remote -- Stack Launcher
|
|
3
|
+
*
|
|
4
|
+
* Spawns the full Retold stack as a unit:
|
|
5
|
+
* - Ultravisor (mesh coordinator) as a child process
|
|
6
|
+
* - Retold Remote (this process) connecting to it as a beacon
|
|
7
|
+
* - Orator-Conversion (embedded inside Retold Remote)
|
|
8
|
+
*
|
|
9
|
+
* Provides XDG-style default data paths so the stack runs sanely
|
|
10
|
+
* from anywhere without configuration.
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* const libStackLauncher = require('./RetoldRemote-Stack-Launcher');
|
|
14
|
+
* libStackLauncher.start({ Logger: log }, (pError, pStackInfo) => { ... });
|
|
15
|
+
*
|
|
16
|
+
* @license MIT
|
|
17
|
+
*/
|
|
18
|
+
const libFs = require('fs');
|
|
19
|
+
const libPath = require('path');
|
|
20
|
+
const libOs = require('os');
|
|
21
|
+
const libHttp = require('http');
|
|
22
|
+
const libChildProcess = require('child_process');
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Resolve XDG-style data paths for the Retold stack.
|
|
26
|
+
*
|
|
27
|
+
* Uses XDG Base Directory Specification environment variables
|
|
28
|
+
* with sensible defaults under the user's home directory.
|
|
29
|
+
*
|
|
30
|
+
* @returns {object} { ConfigDir, DataDir, CacheDir, UltravisorData, UltravisorStaging, UltravisorCache, RetoldCache }
|
|
31
|
+
*/
|
|
32
|
+
function resolveStackPaths()
|
|
33
|
+
{
|
|
34
|
+
let tmpHome = libOs.homedir();
|
|
35
|
+
let tmpConfigBase = process.env.XDG_CONFIG_HOME || libPath.join(tmpHome, '.config');
|
|
36
|
+
let tmpDataBase = process.env.XDG_DATA_HOME || libPath.join(tmpHome, '.local', 'share');
|
|
37
|
+
let tmpCacheBase = process.env.XDG_CACHE_HOME || libPath.join(tmpHome, '.cache');
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
ConfigDir: libPath.join(tmpConfigBase, 'retold-stack'),
|
|
41
|
+
DataDir: libPath.join(tmpDataBase, 'retold-stack'),
|
|
42
|
+
CacheDir: libPath.join(tmpCacheBase, 'retold-stack'),
|
|
43
|
+
UltravisorData: libPath.join(tmpDataBase, 'ultravisor', 'datastore'),
|
|
44
|
+
UltravisorStaging: libPath.join(tmpDataBase, 'ultravisor', 'staging'),
|
|
45
|
+
UltravisorCache: libPath.join(tmpCacheBase, 'ultravisor'),
|
|
46
|
+
RetoldCache: libPath.join(tmpCacheBase, 'retold-remote')
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Ensure a directory exists, creating it (and parents) if necessary.
|
|
52
|
+
*
|
|
53
|
+
* @param {string} pDir - Directory path
|
|
54
|
+
*/
|
|
55
|
+
function ensureDir(pDir)
|
|
56
|
+
{
|
|
57
|
+
if (!libFs.existsSync(pDir))
|
|
58
|
+
{
|
|
59
|
+
libFs.mkdirSync(pDir, { recursive: true });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Find the absolute path to the ultravisor CLI runner script.
|
|
65
|
+
* Resolves through node's module resolution so it works no matter
|
|
66
|
+
* where retold-remote is installed.
|
|
67
|
+
*
|
|
68
|
+
* @returns {string|null} Absolute path or null if not found
|
|
69
|
+
*/
|
|
70
|
+
function resolveUltravisorBin()
|
|
71
|
+
{
|
|
72
|
+
try
|
|
73
|
+
{
|
|
74
|
+
// Resolve the package.json so we can read its bin entry
|
|
75
|
+
let tmpPackageJsonPath = require.resolve('ultravisor/package.json');
|
|
76
|
+
let tmpPackageDir = libPath.dirname(tmpPackageJsonPath);
|
|
77
|
+
let tmpPackage = JSON.parse(libFs.readFileSync(tmpPackageJsonPath, 'utf8'));
|
|
78
|
+
|
|
79
|
+
let tmpBinEntry = null;
|
|
80
|
+
if (typeof tmpPackage.bin === 'string')
|
|
81
|
+
{
|
|
82
|
+
tmpBinEntry = tmpPackage.bin;
|
|
83
|
+
}
|
|
84
|
+
else if (tmpPackage.bin && typeof tmpPackage.bin === 'object')
|
|
85
|
+
{
|
|
86
|
+
tmpBinEntry = tmpPackage.bin.ultravisor || Object.values(tmpPackage.bin)[0];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!tmpBinEntry)
|
|
90
|
+
{
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return libPath.resolve(tmpPackageDir, tmpBinEntry);
|
|
95
|
+
}
|
|
96
|
+
catch (pError)
|
|
97
|
+
{
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Check whether a TCP port is accepting connections.
|
|
104
|
+
*
|
|
105
|
+
* @param {number} pPort - Port to test
|
|
106
|
+
* @param {string} pHost - Host to test (default localhost)
|
|
107
|
+
* @param {Function} fCallback - Callback(pIsOpen)
|
|
108
|
+
*/
|
|
109
|
+
function checkPortOpen(pPort, pHost, fCallback)
|
|
110
|
+
{
|
|
111
|
+
let tmpRequest = libHttp.get(
|
|
112
|
+
{
|
|
113
|
+
host: pHost || '127.0.0.1',
|
|
114
|
+
port: pPort,
|
|
115
|
+
path: '/',
|
|
116
|
+
timeout: 1000
|
|
117
|
+
},
|
|
118
|
+
(pResponse) =>
|
|
119
|
+
{
|
|
120
|
+
// Any HTTP response means the port is open
|
|
121
|
+
pResponse.resume();
|
|
122
|
+
fCallback(true);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
tmpRequest.on('error', () => fCallback(false));
|
|
126
|
+
tmpRequest.on('timeout', () =>
|
|
127
|
+
{
|
|
128
|
+
tmpRequest.destroy();
|
|
129
|
+
fCallback(false);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Wait for ultravisor to be ready by polling its HTTP port.
|
|
135
|
+
*
|
|
136
|
+
* @param {number} pPort - Port to poll
|
|
137
|
+
* @param {number} pTimeoutMs - Total wait timeout in milliseconds
|
|
138
|
+
* @param {Function} fCallback - Callback(pError) — pError null if ready
|
|
139
|
+
*/
|
|
140
|
+
function waitForUltravisor(pPort, pTimeoutMs, fCallback)
|
|
141
|
+
{
|
|
142
|
+
let tmpStart = Date.now();
|
|
143
|
+
let tmpAttempts = 0;
|
|
144
|
+
|
|
145
|
+
let _attempt = () =>
|
|
146
|
+
{
|
|
147
|
+
tmpAttempts++;
|
|
148
|
+
checkPortOpen(pPort, '127.0.0.1', (pIsOpen) =>
|
|
149
|
+
{
|
|
150
|
+
if (pIsOpen)
|
|
151
|
+
{
|
|
152
|
+
return fCallback(null, tmpAttempts);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (Date.now() - tmpStart > pTimeoutMs)
|
|
156
|
+
{
|
|
157
|
+
return fCallback(new Error(`Ultravisor did not become ready within ${pTimeoutMs}ms`));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
setTimeout(_attempt, 500);
|
|
161
|
+
});
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// Initial delay so we don't poll before the process has started
|
|
165
|
+
setTimeout(_attempt, 750);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Spawn ultravisor as a child process with sane defaults.
|
|
170
|
+
*
|
|
171
|
+
* Writes a temporary config file with the user-specified data paths,
|
|
172
|
+
* then launches `node ultravisor-bin start -c <config>`.
|
|
173
|
+
*
|
|
174
|
+
* @param {object} pOptions - { Port, DataPath, StagingPath, ConfigDir, Logger }
|
|
175
|
+
* @param {Function} fCallback - Callback(pError, pChildProcess)
|
|
176
|
+
*/
|
|
177
|
+
function spawnUltravisor(pOptions, fCallback)
|
|
178
|
+
{
|
|
179
|
+
let tmpLog = pOptions.Logger || console;
|
|
180
|
+
let tmpUltravisorBin = resolveUltravisorBin();
|
|
181
|
+
|
|
182
|
+
if (!tmpUltravisorBin)
|
|
183
|
+
{
|
|
184
|
+
return fCallback(new Error('Could not locate the ultravisor package. Run `npm install ultravisor` in retold-remote.'));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (!libFs.existsSync(tmpUltravisorBin))
|
|
188
|
+
{
|
|
189
|
+
return fCallback(new Error(`Ultravisor binary not found at ${tmpUltravisorBin}`));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
ensureDir(pOptions.ConfigDir);
|
|
193
|
+
ensureDir(pOptions.DataPath);
|
|
194
|
+
ensureDir(pOptions.StagingPath);
|
|
195
|
+
|
|
196
|
+
// Write a config file pointing at the user-specified paths
|
|
197
|
+
let tmpConfig =
|
|
198
|
+
{
|
|
199
|
+
UltravisorAPIServerPort: pOptions.Port,
|
|
200
|
+
UltravisorFileStorePath: pOptions.DataPath,
|
|
201
|
+
UltravisorStagingRoot: pOptions.StagingPath
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
let tmpConfigPath = libPath.join(pOptions.ConfigDir, 'ultravisor-stack.json');
|
|
205
|
+
libFs.writeFileSync(tmpConfigPath, JSON.stringify(tmpConfig, null, '\t'));
|
|
206
|
+
|
|
207
|
+
tmpLog.info(`[stack] launching ultravisor (port ${pOptions.Port})`);
|
|
208
|
+
tmpLog.info(`[stack] data: ${pOptions.DataPath}`);
|
|
209
|
+
tmpLog.info(`[stack] staging: ${pOptions.StagingPath}`);
|
|
210
|
+
|
|
211
|
+
let tmpChild = libChildProcess.spawn(
|
|
212
|
+
process.execPath,
|
|
213
|
+
[tmpUltravisorBin, 'start', '-c', tmpConfigPath],
|
|
214
|
+
{
|
|
215
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
216
|
+
detached: false
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// Stream child output with a prefix for clarity
|
|
220
|
+
tmpChild.stdout.on('data', (pChunk) =>
|
|
221
|
+
{
|
|
222
|
+
let tmpLines = pChunk.toString().split('\n');
|
|
223
|
+
for (let i = 0; i < tmpLines.length; i++)
|
|
224
|
+
{
|
|
225
|
+
if (tmpLines[i].length > 0)
|
|
226
|
+
{
|
|
227
|
+
tmpLog.info('[ultravisor] ' + tmpLines[i]);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
tmpChild.stderr.on('data', (pChunk) =>
|
|
233
|
+
{
|
|
234
|
+
let tmpLines = pChunk.toString().split('\n');
|
|
235
|
+
for (let i = 0; i < tmpLines.length; i++)
|
|
236
|
+
{
|
|
237
|
+
if (tmpLines[i].length > 0)
|
|
238
|
+
{
|
|
239
|
+
tmpLog.warn('[ultravisor] ' + tmpLines[i]);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
tmpChild.on('exit', (pCode, pSignal) =>
|
|
245
|
+
{
|
|
246
|
+
if (pCode !== 0 && pCode !== null)
|
|
247
|
+
{
|
|
248
|
+
tmpLog.warn(`[stack] ultravisor exited with code ${pCode}`);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
tmpChild.on('error', (pError) =>
|
|
253
|
+
{
|
|
254
|
+
tmpLog.error(`[stack] ultravisor failed to launch: ${pError.message}`);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
return fCallback(null, tmpChild);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Start the full stack: spawn ultravisor as a child process, wait for
|
|
262
|
+
* it to be ready, then return so the caller can start retold-remote.
|
|
263
|
+
*
|
|
264
|
+
* @param {object} pOptions
|
|
265
|
+
* @param {object} pOptions.Logger - A fable-style logger ({ info, warn, error })
|
|
266
|
+
* @param {number} [pOptions.UltravisorPort=54321] - Port for ultravisor
|
|
267
|
+
* @param {string} [pOptions.DataPath] - Override ultravisor data path
|
|
268
|
+
* @param {string} [pOptions.StagingPath] - Override ultravisor staging path
|
|
269
|
+
* @param {string} [pOptions.ConfigDir] - Override ultravisor config dir
|
|
270
|
+
* @param {Function} fCallback - Callback(pError, pStackInfo)
|
|
271
|
+
* pStackInfo: { UltravisorURL, UltravisorChild, UltravisorPort, Paths }
|
|
272
|
+
*/
|
|
273
|
+
function start(pOptions, fCallback)
|
|
274
|
+
{
|
|
275
|
+
let tmpLog = pOptions.Logger || console;
|
|
276
|
+
let tmpPaths = resolveStackPaths();
|
|
277
|
+
|
|
278
|
+
let tmpPort = pOptions.UltravisorPort || 54321;
|
|
279
|
+
let tmpDataPath = pOptions.DataPath || tmpPaths.UltravisorData;
|
|
280
|
+
let tmpStagingPath = pOptions.StagingPath || tmpPaths.UltravisorStaging;
|
|
281
|
+
let tmpConfigDir = pOptions.ConfigDir || tmpPaths.ConfigDir;
|
|
282
|
+
|
|
283
|
+
tmpLog.info('==========================================================');
|
|
284
|
+
tmpLog.info(' Retold Stack Launcher');
|
|
285
|
+
tmpLog.info('==========================================================');
|
|
286
|
+
|
|
287
|
+
// Check if ultravisor is already running on the target port
|
|
288
|
+
checkPortOpen(tmpPort, '127.0.0.1', (pAlreadyRunning) =>
|
|
289
|
+
{
|
|
290
|
+
if (pAlreadyRunning)
|
|
291
|
+
{
|
|
292
|
+
tmpLog.info(`[stack] ultravisor already running on port ${tmpPort}, reusing`);
|
|
293
|
+
return fCallback(null,
|
|
294
|
+
{
|
|
295
|
+
UltravisorURL: 'http://localhost:' + tmpPort,
|
|
296
|
+
UltravisorChild: null,
|
|
297
|
+
UltravisorPort: tmpPort,
|
|
298
|
+
Paths: tmpPaths,
|
|
299
|
+
AlreadyRunning: true
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Spawn ultravisor as a child process
|
|
304
|
+
spawnUltravisor(
|
|
305
|
+
{
|
|
306
|
+
Port: tmpPort,
|
|
307
|
+
DataPath: tmpDataPath,
|
|
308
|
+
StagingPath: tmpStagingPath,
|
|
309
|
+
ConfigDir: tmpConfigDir,
|
|
310
|
+
Logger: tmpLog
|
|
311
|
+
},
|
|
312
|
+
(pSpawnError, pChild) =>
|
|
313
|
+
{
|
|
314
|
+
if (pSpawnError)
|
|
315
|
+
{
|
|
316
|
+
return fCallback(pSpawnError);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Wait for ultravisor to start accepting connections
|
|
320
|
+
waitForUltravisor(tmpPort, 30000, (pWaitError, pAttempts) =>
|
|
321
|
+
{
|
|
322
|
+
if (pWaitError)
|
|
323
|
+
{
|
|
324
|
+
try { pChild.kill(); } catch (e) { /* ignore */ }
|
|
325
|
+
return fCallback(pWaitError);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
tmpLog.info(`[stack] ultravisor ready (after ${pAttempts} attempts)`);
|
|
329
|
+
|
|
330
|
+
return fCallback(null,
|
|
331
|
+
{
|
|
332
|
+
UltravisorURL: 'http://localhost:' + tmpPort,
|
|
333
|
+
UltravisorChild: pChild,
|
|
334
|
+
UltravisorPort: tmpPort,
|
|
335
|
+
Paths: tmpPaths,
|
|
336
|
+
AlreadyRunning: false
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Stop the stack — kill the ultravisor child process if we spawned it.
|
|
345
|
+
*
|
|
346
|
+
* @param {object} pStackInfo - The object returned from start()
|
|
347
|
+
* @param {Function} fCallback - Callback() called after shutdown
|
|
348
|
+
*/
|
|
349
|
+
function stop(pStackInfo, fCallback)
|
|
350
|
+
{
|
|
351
|
+
if (pStackInfo && pStackInfo.UltravisorChild && !pStackInfo.AlreadyRunning)
|
|
352
|
+
{
|
|
353
|
+
try
|
|
354
|
+
{
|
|
355
|
+
pStackInfo.UltravisorChild.kill('SIGTERM');
|
|
356
|
+
}
|
|
357
|
+
catch (pError)
|
|
358
|
+
{
|
|
359
|
+
// ignore
|
|
360
|
+
}
|
|
361
|
+
// Give it a moment to exit gracefully
|
|
362
|
+
setTimeout(() =>
|
|
363
|
+
{
|
|
364
|
+
try
|
|
365
|
+
{
|
|
366
|
+
pStackInfo.UltravisorChild.kill('SIGKILL');
|
|
367
|
+
}
|
|
368
|
+
catch (pError)
|
|
369
|
+
{
|
|
370
|
+
// ignore — already gone
|
|
371
|
+
}
|
|
372
|
+
if (fCallback) fCallback();
|
|
373
|
+
}, 1000);
|
|
374
|
+
}
|
|
375
|
+
else if (fCallback)
|
|
376
|
+
{
|
|
377
|
+
fCallback();
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
module.exports =
|
|
382
|
+
{
|
|
383
|
+
resolveStackPaths: resolveStackPaths,
|
|
384
|
+
resolveUltravisorBin: resolveUltravisorBin,
|
|
385
|
+
start: start,
|
|
386
|
+
stop: stop
|
|
387
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Retold Stack — convenience entry point.
|
|
4
|
+
*
|
|
5
|
+
* Equivalent to running: retold-remote serve --stack [args...]
|
|
6
|
+
*
|
|
7
|
+
* Spawns Ultravisor as a child process, embeds Orator-Conversion,
|
|
8
|
+
* and starts the Retold Remote media browser pointed at the supplied
|
|
9
|
+
* directory (or the current working directory by default).
|
|
10
|
+
*
|
|
11
|
+
* Data paths default to XDG-style locations:
|
|
12
|
+
* ~/.local/share/ultravisor/ — Ultravisor datastore + staging
|
|
13
|
+
* ~/.cache/retold-remote/ — Retold Remote cache
|
|
14
|
+
* ~/.config/retold-stack/ — Stack config files
|
|
15
|
+
*
|
|
16
|
+
* @license MIT
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
// Inject 'serve --stack' as the first arguments if the user did not
|
|
20
|
+
// already specify a subcommand. This makes `retold-stack /some/path`
|
|
21
|
+
// equivalent to `retold-remote serve --stack /some/path`.
|
|
22
|
+
let tmpArgs = process.argv.slice(2);
|
|
23
|
+
|
|
24
|
+
// Detect whether the user already passed a known subcommand
|
|
25
|
+
let tmpKnownCommands = { 'serve': true };
|
|
26
|
+
let tmpHasSubcommand = tmpArgs.length > 0 && tmpKnownCommands[tmpArgs[0]];
|
|
27
|
+
|
|
28
|
+
if (!tmpHasSubcommand)
|
|
29
|
+
{
|
|
30
|
+
tmpArgs = ['serve', '--stack'].concat(tmpArgs);
|
|
31
|
+
}
|
|
32
|
+
else if (tmpArgs.indexOf('--stack') === -1)
|
|
33
|
+
{
|
|
34
|
+
// User passed `retold-stack serve <path>` — append --stack
|
|
35
|
+
tmpArgs.splice(1, 0, '--stack');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
process.argv = [process.argv[0], process.argv[1]].concat(tmpArgs);
|
|
39
|
+
|
|
40
|
+
const libRetoldRemoteProgram = require('./RetoldRemote-CLI-Program.js');
|
|
41
|
+
libRetoldRemoteProgram.run();
|
|
@@ -29,6 +29,9 @@ class RetoldRemoteCommandServe extends libCommandLineCommand
|
|
|
29
29
|
this.options.CommandOptions.push(
|
|
30
30
|
{ Name: '-u, --ultravisor [url]', Description: 'Connect to Ultravisor mesh. URL defaults to http://localhost:54321 if omitted.', Default: '' });
|
|
31
31
|
|
|
32
|
+
this.options.CommandOptions.push(
|
|
33
|
+
{ Name: '--stack', Description: 'Run the full stack: spawn Ultravisor as a child process and connect to it. Uses XDG-style data paths under ~/.local/share and ~/.cache.', Default: false });
|
|
34
|
+
|
|
32
35
|
this.options.CommandOptions.push(
|
|
33
36
|
{ Name: '-l, --logfile [path]', Description: 'Write logs to a file (auto-generates timestamped name if path omitted).', Default: '' });
|
|
34
37
|
|
|
@@ -78,14 +81,26 @@ class RetoldRemoteCommandServe extends libCommandLineCommand
|
|
|
78
81
|
}
|
|
79
82
|
|
|
80
83
|
let tmpSelf = this;
|
|
81
|
-
let tmpSetupServer = require('../RetoldRemote-Server-Setup.js');
|
|
82
|
-
|
|
83
84
|
let tmpHashedFilenames = !(this.CommandOptions.noHash);
|
|
85
|
+
let tmpStackMode = !!this.CommandOptions.stack;
|
|
86
|
+
|
|
87
|
+
// Resolve XDG-style stack paths once (used by --stack mode)
|
|
88
|
+
let libStackLauncher = require('../RetoldRemote-Stack-Launcher.js');
|
|
89
|
+
let tmpStackPaths = libStackLauncher.resolveStackPaths();
|
|
90
|
+
|
|
91
|
+
// Cache root: explicit > stack default > package default
|
|
92
|
+
let tmpCacheRoot = null;
|
|
93
|
+
if (this.CommandOptions.cachePath)
|
|
94
|
+
{
|
|
95
|
+
tmpCacheRoot = libPath.resolve(this.CommandOptions.cachePath);
|
|
96
|
+
}
|
|
97
|
+
else if (tmpStackMode)
|
|
98
|
+
{
|
|
99
|
+
tmpCacheRoot = tmpStackPaths.RetoldCache;
|
|
100
|
+
}
|
|
84
101
|
|
|
85
|
-
let tmpCacheRoot = this.CommandOptions.cachePath
|
|
86
|
-
? libPath.resolve(this.CommandOptions.cachePath)
|
|
87
|
-
: null;
|
|
88
102
|
let tmpCacheServer = this.CommandOptions.cacheServer || null;
|
|
103
|
+
|
|
89
104
|
// -u with no URL → true (Commander behavior for [optional]), default to localhost
|
|
90
105
|
let tmpUltravisorOpt = this.CommandOptions.ultravisor;
|
|
91
106
|
let tmpUltravisorURL = null;
|
|
@@ -98,65 +113,125 @@ class RetoldRemoteCommandServe extends libCommandLineCommand
|
|
|
98
113
|
tmpUltravisorURL = tmpUltravisorOpt;
|
|
99
114
|
}
|
|
100
115
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
return fCallback(pError);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
tmpSelf.log.info('');
|
|
120
|
-
tmpSelf.log.info('==========================================================');
|
|
121
|
-
tmpSelf.log.info(` Retold Remote running on http://localhost:${pServerInfo.Port}`);
|
|
122
|
-
tmpSelf.log.info('==========================================================');
|
|
123
|
-
tmpSelf.log.info(` Content: ${tmpContentPath}`);
|
|
124
|
-
tmpSelf.log.info(` Assets: ${tmpDistPath}`);
|
|
125
|
-
tmpSelf.log.info(` Browse: http://localhost:${pServerInfo.Port}/`);
|
|
126
|
-
if (pServerInfo.UltravisorBeacon && pServerInfo.UltravisorBeacon.isEnabled())
|
|
127
|
-
{
|
|
128
|
-
tmpSelf.log.info(` Beacon: registered with Ultravisor at ${tmpUltravisorURL}`);
|
|
129
|
-
}
|
|
130
|
-
else if (tmpUltravisorURL)
|
|
116
|
+
// In stack mode, automatically set the ultravisor URL
|
|
117
|
+
if (tmpStackMode && !tmpUltravisorURL)
|
|
118
|
+
{
|
|
119
|
+
tmpUltravisorURL = 'http://localhost:54321';
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Hold the stack info so we can clean up on exit
|
|
123
|
+
let tmpStackInfo = null;
|
|
124
|
+
|
|
125
|
+
// Bind the actual server startup so we can call it after (optionally) launching ultravisor
|
|
126
|
+
let _startRetoldRemote = () =>
|
|
127
|
+
{
|
|
128
|
+
let tmpSetupServer = require('../RetoldRemote-Server-Setup.js');
|
|
129
|
+
|
|
130
|
+
tmpSetupServer(
|
|
131
131
|
{
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
132
|
+
ContentPath: tmpContentPath,
|
|
133
|
+
DistPath: tmpDistPath,
|
|
134
|
+
Port: tmpPort,
|
|
135
|
+
HashedFilenames: tmpHashedFilenames,
|
|
136
|
+
CacheRoot: tmpCacheRoot,
|
|
137
|
+
CacheServer: tmpCacheServer,
|
|
138
|
+
UltravisorURL: tmpUltravisorURL
|
|
139
|
+
},
|
|
140
|
+
function (pError, pServerInfo)
|
|
141
141
|
{
|
|
142
|
+
if (pError)
|
|
143
|
+
{
|
|
144
|
+
tmpSelf.log.error(`Failed to start server: ${pError.message}`);
|
|
145
|
+
if (tmpStackInfo)
|
|
146
|
+
{
|
|
147
|
+
libStackLauncher.stop(tmpStackInfo, () => {});
|
|
148
|
+
}
|
|
149
|
+
return fCallback(pError);
|
|
150
|
+
}
|
|
151
|
+
|
|
142
152
|
tmpSelf.log.info('');
|
|
143
|
-
tmpSelf.log.info('
|
|
153
|
+
tmpSelf.log.info('==========================================================');
|
|
154
|
+
tmpSelf.log.info(` Retold Remote running on http://localhost:${pServerInfo.Port}`);
|
|
155
|
+
tmpSelf.log.info('==========================================================');
|
|
156
|
+
tmpSelf.log.info(` Content: ${tmpContentPath}`);
|
|
157
|
+
tmpSelf.log.info(` Cache: ${tmpCacheRoot || '(default)'}`);
|
|
158
|
+
tmpSelf.log.info(` Browse: http://localhost:${pServerInfo.Port}/`);
|
|
144
159
|
if (pServerInfo.UltravisorBeacon && pServerInfo.UltravisorBeacon.isEnabled())
|
|
145
160
|
{
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
161
|
+
tmpSelf.log.info(` Beacon: registered with Ultravisor at ${tmpUltravisorURL}`);
|
|
162
|
+
}
|
|
163
|
+
else if (tmpUltravisorURL)
|
|
164
|
+
{
|
|
165
|
+
tmpSelf.log.info(` Beacon: not connected (Ultravisor may be unreachable)`);
|
|
150
166
|
}
|
|
151
|
-
|
|
167
|
+
if (tmpStackMode)
|
|
152
168
|
{
|
|
153
|
-
|
|
169
|
+
tmpSelf.log.info(` Stack: ultravisor + retold-remote (orator-conversion embedded)`);
|
|
154
170
|
}
|
|
171
|
+
tmpSelf.log.info('==========================================================');
|
|
172
|
+
tmpSelf.log.info('');
|
|
173
|
+
tmpSelf.log.info(' Press Ctrl+C to stop.');
|
|
174
|
+
tmpSelf.log.info('');
|
|
175
|
+
|
|
176
|
+
// Graceful shutdown: disconnect beacon and stop child processes before exit
|
|
177
|
+
let _shutdown = () =>
|
|
178
|
+
{
|
|
179
|
+
tmpSelf.log.info('');
|
|
180
|
+
tmpSelf.log.info('Shutting down...');
|
|
181
|
+
|
|
182
|
+
let _finish = () =>
|
|
183
|
+
{
|
|
184
|
+
if (tmpStackInfo)
|
|
185
|
+
{
|
|
186
|
+
libStackLauncher.stop(tmpStackInfo, () => process.exit(0));
|
|
187
|
+
}
|
|
188
|
+
else
|
|
189
|
+
{
|
|
190
|
+
process.exit(0);
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
if (pServerInfo.UltravisorBeacon && pServerInfo.UltravisorBeacon.isEnabled())
|
|
195
|
+
{
|
|
196
|
+
pServerInfo.UltravisorBeacon.disconnectBeacon(_finish);
|
|
197
|
+
}
|
|
198
|
+
else
|
|
199
|
+
{
|
|
200
|
+
_finish();
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
process.on('SIGINT', _shutdown);
|
|
205
|
+
process.on('SIGTERM', _shutdown);
|
|
206
|
+
|
|
207
|
+
// Intentionally do NOT call fCallback() here.
|
|
208
|
+
// The server should keep running.
|
|
155
209
|
});
|
|
210
|
+
};
|
|
156
211
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
212
|
+
// In stack mode, launch ultravisor first, then start retold-remote
|
|
213
|
+
if (tmpStackMode)
|
|
214
|
+
{
|
|
215
|
+
libStackLauncher.start(
|
|
216
|
+
{
|
|
217
|
+
Logger: tmpSelf.log,
|
|
218
|
+
UltravisorPort: 54321
|
|
219
|
+
},
|
|
220
|
+
(pStackError, pStackInfo) =>
|
|
221
|
+
{
|
|
222
|
+
if (pStackError)
|
|
223
|
+
{
|
|
224
|
+
tmpSelf.log.error(`Stack launch failed: ${pStackError.message}`);
|
|
225
|
+
return fCallback(pStackError);
|
|
226
|
+
}
|
|
227
|
+
tmpStackInfo = pStackInfo;
|
|
228
|
+
_startRetoldRemote();
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
else
|
|
232
|
+
{
|
|
233
|
+
_startRetoldRemote();
|
|
234
|
+
}
|
|
160
235
|
}
|
|
161
236
|
}
|
|
162
237
|
|