retold-content-system 1.0.10 → 1.0.11
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "retold-content-system",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.11",
|
|
4
4
|
"description": "Retold Content System - Markdown content viewer and editor",
|
|
5
5
|
"main": "source/Pict-ContentSystem-Bundle.js",
|
|
6
6
|
"bin": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"LICENSE"
|
|
18
18
|
],
|
|
19
19
|
"scripts": {
|
|
20
|
-
"start": "node source/
|
|
20
|
+
"start": "node source/cli/ContentSystem-CLI-Run.js serve",
|
|
21
21
|
"build": "npx quack build && npx quack copy",
|
|
22
22
|
"build-codemirror": "node build/build-codemirror-bundle.js",
|
|
23
23
|
"build-codejar": "node build/build-codejar-bundle.js",
|
|
@@ -27,11 +27,14 @@
|
|
|
27
27
|
},
|
|
28
28
|
"author": "steven velozo <steven@velozo.com>",
|
|
29
29
|
"license": "MIT",
|
|
30
|
+
"optionalDependencies": {
|
|
31
|
+
"ultravisor-beacon": "^0.0.1"
|
|
32
|
+
},
|
|
30
33
|
"dependencies": {
|
|
31
|
-
"fable": "^3.1.
|
|
34
|
+
"fable": "^3.1.66",
|
|
32
35
|
"orator": "^6.0.4",
|
|
33
36
|
"orator-serviceserver-restify": "^2.0.9",
|
|
34
|
-
"pict": "^1.0.
|
|
37
|
+
"pict": "^1.0.359",
|
|
35
38
|
"pict-application": "^1.0.33",
|
|
36
39
|
"pict-docuserve": "^0.0.32",
|
|
37
40
|
"pict-provider": "^1.0.12",
|
|
@@ -50,7 +53,7 @@
|
|
|
50
53
|
"codemirror": "^6.0.2",
|
|
51
54
|
"esbuild": "^0.27.4",
|
|
52
55
|
"highlight.js": "^11.11.1",
|
|
53
|
-
"quackage": "^1.0.
|
|
56
|
+
"quackage": "^1.0.65"
|
|
54
57
|
},
|
|
55
58
|
"copyFilesSettings": {
|
|
56
59
|
"whenFileExists": "overwrite"
|
|
@@ -402,6 +402,15 @@ function setupContentSystemServer(pOptions, fCallback)
|
|
|
402
402
|
tmpOrator.startService(
|
|
403
403
|
function ()
|
|
404
404
|
{
|
|
405
|
+
// --- Optional Ultravisor Beacon Integration ---
|
|
406
|
+
// When pOptions.Beacon is provided and Enabled is true,
|
|
407
|
+
// register this content system as a beacon worker.
|
|
408
|
+
let tmpBeaconConfig = pOptions.Beacon || {};
|
|
409
|
+
if (tmpBeaconConfig.Enabled)
|
|
410
|
+
{
|
|
411
|
+
_initializeBeacon(tmpFable, tmpContentPath, tmpBeaconConfig);
|
|
412
|
+
}
|
|
413
|
+
|
|
405
414
|
return fCallback(null,
|
|
406
415
|
{
|
|
407
416
|
Fable: tmpFable,
|
|
@@ -412,4 +421,266 @@ function setupContentSystemServer(pOptions, fCallback)
|
|
|
412
421
|
});
|
|
413
422
|
}
|
|
414
423
|
|
|
424
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
425
|
+
// Ultravisor Beacon Integration
|
|
426
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Initialize the Ultravisor beacon service and register ContentSystem
|
|
430
|
+
* capabilities. This is opt-in — only called when Beacon.Enabled is true.
|
|
431
|
+
*
|
|
432
|
+
* @param {object} pFable - Fable instance
|
|
433
|
+
* @param {string} pContentPath - Resolved content directory path
|
|
434
|
+
* @param {object} pBeaconConfig - Beacon configuration object
|
|
435
|
+
*/
|
|
436
|
+
function _initializeBeacon(pFable, pContentPath, pBeaconConfig)
|
|
437
|
+
{
|
|
438
|
+
let libBeaconService;
|
|
439
|
+
try
|
|
440
|
+
{
|
|
441
|
+
libBeaconService = require('ultravisor-beacon');
|
|
442
|
+
}
|
|
443
|
+
catch (pError)
|
|
444
|
+
{
|
|
445
|
+
pFable.log.warn(`Content System Beacon: ultravisor-beacon not installed. Skipping beacon init.`);
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
pFable.serviceManager.addAndInstantiateServiceType('UltravisorBeacon', libBeaconService, pBeaconConfig);
|
|
450
|
+
let tmpBeacon = pFable.services.UltravisorBeacon;
|
|
451
|
+
|
|
452
|
+
tmpBeacon.registerCapability({
|
|
453
|
+
Capability: 'ContentSystem',
|
|
454
|
+
Name: 'ContentSystemProvider',
|
|
455
|
+
actions:
|
|
456
|
+
{
|
|
457
|
+
'ReadFile':
|
|
458
|
+
{
|
|
459
|
+
Description: 'Read a content file',
|
|
460
|
+
SettingsSchema: [
|
|
461
|
+
{ Name: 'FilePath', DataType: 'String', Required: true }
|
|
462
|
+
],
|
|
463
|
+
Handler: function (pWorkItem, pContext, fCallback)
|
|
464
|
+
{
|
|
465
|
+
try
|
|
466
|
+
{
|
|
467
|
+
let tmpFilePath = sanitizePath(pWorkItem.Settings.FilePath);
|
|
468
|
+
if (!tmpFilePath)
|
|
469
|
+
{
|
|
470
|
+
return fCallback(null, {
|
|
471
|
+
Outputs: { Content: '', StdOut: 'Invalid file path.' },
|
|
472
|
+
Log: ['ReadFile: invalid or unsafe path rejected.']
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
let tmpFullPath = libPath.join(pContentPath, tmpFilePath);
|
|
477
|
+
let tmpRealContent = libFs.realpathSync(pContentPath);
|
|
478
|
+
if (!tmpFullPath.startsWith(tmpRealContent))
|
|
479
|
+
{
|
|
480
|
+
return fCallback(null, {
|
|
481
|
+
Outputs: { Content: '', StdOut: 'Access denied.' },
|
|
482
|
+
Log: ['ReadFile: path outside content directory.']
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (!libFs.existsSync(tmpFullPath))
|
|
487
|
+
{
|
|
488
|
+
return fCallback(null, {
|
|
489
|
+
Outputs: { Content: '', StdOut: 'File not found.' },
|
|
490
|
+
Log: [`ReadFile: ${tmpFilePath} not found.`]
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
let tmpContent = libFs.readFileSync(tmpFullPath, 'utf8');
|
|
495
|
+
pFable.log.info(`Beacon ReadFile: ${tmpFilePath} (${tmpContent.length} bytes)`);
|
|
496
|
+
return fCallback(null, {
|
|
497
|
+
Outputs: { Content: tmpContent, StdOut: `Read ${tmpContent.length} bytes from ${tmpFilePath}` },
|
|
498
|
+
Log: [`ReadFile: read ${tmpContent.length} bytes from ${tmpFilePath}`]
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
catch (pError)
|
|
502
|
+
{
|
|
503
|
+
return fCallback(pError);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
},
|
|
507
|
+
|
|
508
|
+
'SaveFile':
|
|
509
|
+
{
|
|
510
|
+
Description: 'Save content to a file',
|
|
511
|
+
SettingsSchema: [
|
|
512
|
+
{ Name: 'FilePath', DataType: 'String', Required: true },
|
|
513
|
+
{ Name: 'Content', DataType: 'String', Required: true }
|
|
514
|
+
],
|
|
515
|
+
Handler: function (pWorkItem, pContext, fCallback)
|
|
516
|
+
{
|
|
517
|
+
try
|
|
518
|
+
{
|
|
519
|
+
let tmpFilePath = sanitizePath(pWorkItem.Settings.FilePath);
|
|
520
|
+
if (!tmpFilePath)
|
|
521
|
+
{
|
|
522
|
+
return fCallback(null, {
|
|
523
|
+
Outputs: { FilePath: '', StdOut: 'Invalid file path.' },
|
|
524
|
+
Log: ['SaveFile: invalid or unsafe path rejected.']
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
let tmpFullPath = libPath.join(pContentPath, tmpFilePath);
|
|
529
|
+
let tmpRealContent = libFs.realpathSync(pContentPath);
|
|
530
|
+
let tmpTargetDir = libPath.dirname(tmpFullPath);
|
|
531
|
+
if (!tmpTargetDir.startsWith(tmpRealContent))
|
|
532
|
+
{
|
|
533
|
+
return fCallback(null, {
|
|
534
|
+
Outputs: { FilePath: '', StdOut: 'Access denied.' },
|
|
535
|
+
Log: ['SaveFile: path outside content directory.']
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Ensure directory exists
|
|
540
|
+
if (!libFs.existsSync(tmpTargetDir))
|
|
541
|
+
{
|
|
542
|
+
libFs.mkdirSync(tmpTargetDir, { recursive: true });
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
let tmpContent = pWorkItem.Settings.Content || '';
|
|
546
|
+
libFs.writeFileSync(tmpFullPath, tmpContent, 'utf8');
|
|
547
|
+
pFable.log.info(`Beacon SaveFile: ${tmpFilePath} (${tmpContent.length} bytes)`);
|
|
548
|
+
return fCallback(null, {
|
|
549
|
+
Outputs: { FilePath: tmpFilePath, StdOut: `Saved ${tmpContent.length} bytes to ${tmpFilePath}` },
|
|
550
|
+
Log: [`SaveFile: wrote ${tmpContent.length} bytes to ${tmpFilePath}`]
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
catch (pError)
|
|
554
|
+
{
|
|
555
|
+
return fCallback(pError);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
},
|
|
559
|
+
|
|
560
|
+
'ListFiles':
|
|
561
|
+
{
|
|
562
|
+
Description: 'List files in a content directory',
|
|
563
|
+
SettingsSchema: [
|
|
564
|
+
{ Name: 'Path', DataType: 'String', Required: false }
|
|
565
|
+
],
|
|
566
|
+
Handler: function (pWorkItem, pContext, fCallback)
|
|
567
|
+
{
|
|
568
|
+
try
|
|
569
|
+
{
|
|
570
|
+
let tmpRelPath = pWorkItem.Settings.Path || '';
|
|
571
|
+
let tmpSafePath = tmpRelPath ? sanitizePath(tmpRelPath) : '';
|
|
572
|
+
let tmpTargetPath = tmpSafePath
|
|
573
|
+
? libPath.join(pContentPath, tmpSafePath)
|
|
574
|
+
: pContentPath;
|
|
575
|
+
|
|
576
|
+
let tmpRealContent = libFs.realpathSync(pContentPath);
|
|
577
|
+
if (!tmpTargetPath.startsWith(tmpRealContent))
|
|
578
|
+
{
|
|
579
|
+
return fCallback(null, {
|
|
580
|
+
Outputs: { Files: '[]', StdOut: 'Access denied.' },
|
|
581
|
+
Log: ['ListFiles: path outside content directory.']
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
if (!libFs.existsSync(tmpTargetPath))
|
|
586
|
+
{
|
|
587
|
+
return fCallback(null, {
|
|
588
|
+
Outputs: { Files: '[]', StdOut: 'Directory not found.' },
|
|
589
|
+
Log: [`ListFiles: ${tmpSafePath || '/'} not found.`]
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
let tmpEntries = libFs.readdirSync(tmpTargetPath, { withFileTypes: true });
|
|
594
|
+
let tmpFiles = tmpEntries.map(function (pEntry)
|
|
595
|
+
{
|
|
596
|
+
return {
|
|
597
|
+
Name: pEntry.name,
|
|
598
|
+
IsDirectory: pEntry.isDirectory()
|
|
599
|
+
};
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
pFable.log.info(`Beacon ListFiles: ${tmpSafePath || '/'} (${tmpFiles.length} entries)`);
|
|
603
|
+
return fCallback(null, {
|
|
604
|
+
Outputs: { Files: JSON.stringify(tmpFiles), StdOut: `Found ${tmpFiles.length} entries in ${tmpSafePath || '/'}` },
|
|
605
|
+
Log: [`ListFiles: ${tmpFiles.length} entries in ${tmpSafePath || '/'}`]
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
catch (pError)
|
|
609
|
+
{
|
|
610
|
+
return fCallback(pError);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
},
|
|
614
|
+
|
|
615
|
+
'CreateFolder':
|
|
616
|
+
{
|
|
617
|
+
Description: 'Create a folder in the content directory',
|
|
618
|
+
SettingsSchema: [
|
|
619
|
+
{ Name: 'Path', DataType: 'String', Required: true }
|
|
620
|
+
],
|
|
621
|
+
Handler: function (pWorkItem, pContext, fCallback)
|
|
622
|
+
{
|
|
623
|
+
try
|
|
624
|
+
{
|
|
625
|
+
let tmpSafePath = sanitizePath(pWorkItem.Settings.Path);
|
|
626
|
+
if (!tmpSafePath)
|
|
627
|
+
{
|
|
628
|
+
return fCallback(null, {
|
|
629
|
+
Outputs: { StdOut: 'Invalid folder path.' },
|
|
630
|
+
Log: ['CreateFolder: invalid or unsafe path rejected.']
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
let tmpFullPath = libPath.join(pContentPath, tmpSafePath);
|
|
635
|
+
let tmpRealContent = libFs.realpathSync(pContentPath);
|
|
636
|
+
let tmpTargetParent = libPath.dirname(tmpFullPath);
|
|
637
|
+
if (libFs.existsSync(tmpTargetParent))
|
|
638
|
+
{
|
|
639
|
+
tmpTargetParent = libFs.realpathSync(tmpTargetParent);
|
|
640
|
+
}
|
|
641
|
+
if (!tmpTargetParent.startsWith(tmpRealContent))
|
|
642
|
+
{
|
|
643
|
+
return fCallback(null, {
|
|
644
|
+
Outputs: { StdOut: 'Access denied.' },
|
|
645
|
+
Log: ['CreateFolder: path outside content directory.']
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
if (libFs.existsSync(tmpFullPath))
|
|
650
|
+
{
|
|
651
|
+
return fCallback(null, {
|
|
652
|
+
Outputs: { StdOut: 'Folder already exists.' },
|
|
653
|
+
Log: [`CreateFolder: ${tmpSafePath} already exists.`]
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
libFs.mkdirSync(tmpFullPath, { recursive: true });
|
|
658
|
+
pFable.log.info(`Beacon CreateFolder: ${tmpSafePath}`);
|
|
659
|
+
return fCallback(null, {
|
|
660
|
+
Outputs: { StdOut: `Created folder ${tmpSafePath}` },
|
|
661
|
+
Log: [`CreateFolder: created ${tmpSafePath}`]
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
catch (pError)
|
|
665
|
+
{
|
|
666
|
+
return fCallback(pError);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
tmpBeacon.enable(function (pError)
|
|
674
|
+
{
|
|
675
|
+
if (pError)
|
|
676
|
+
{
|
|
677
|
+
pFable.log.warn(`Content System Beacon: enable failed: ${pError.message}`);
|
|
678
|
+
}
|
|
679
|
+
else
|
|
680
|
+
{
|
|
681
|
+
pFable.log.info('Content System Beacon: enabled and connected to Ultravisor.');
|
|
682
|
+
}
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
|
|
415
686
|
module.exports = setupContentSystemServer;
|
|
@@ -18,6 +18,15 @@ class ContentSystemCommandServe extends libCommandLineCommand
|
|
|
18
18
|
this.options.CommandOptions.push(
|
|
19
19
|
{ Name: '-p, --port [port]', Description: 'Port to serve on (defaults to random 7000-7999).', Default: '0' });
|
|
20
20
|
|
|
21
|
+
this.options.CommandOptions.push(
|
|
22
|
+
{ Name: '-b, --beacon [url]', Description: 'Enable beacon mode and connect to the Ultravisor server at [url] (e.g. http://localhost:54321).' });
|
|
23
|
+
|
|
24
|
+
this.options.CommandOptions.push(
|
|
25
|
+
{ Name: '--beacon-name [name]', Description: 'Beacon identity name (defaults to "content-system-1").', Default: 'content-system-1' });
|
|
26
|
+
|
|
27
|
+
this.options.CommandOptions.push(
|
|
28
|
+
{ Name: '--beacon-password [password]', Description: 'Beacon authentication password.', Default: '' });
|
|
29
|
+
|
|
21
30
|
this.addCommand();
|
|
22
31
|
}
|
|
23
32
|
|
|
@@ -55,6 +64,17 @@ class ContentSystemCommandServe extends libCommandLineCommand
|
|
|
55
64
|
this.log.info(`Created content directory: ${tmpContentPath}`);
|
|
56
65
|
}
|
|
57
66
|
|
|
67
|
+
let tmpBeaconConfig = {};
|
|
68
|
+
if (this.CommandOptions.beacon)
|
|
69
|
+
{
|
|
70
|
+
tmpBeaconConfig = {
|
|
71
|
+
Enabled: true,
|
|
72
|
+
ServerURL: (typeof this.CommandOptions.beacon === 'string') ? this.CommandOptions.beacon : 'http://localhost:54321',
|
|
73
|
+
Name: this.CommandOptions.beaconName || 'content-system-1',
|
|
74
|
+
Password: this.CommandOptions.beaconPassword || ''
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
58
78
|
let tmpSelf = this;
|
|
59
79
|
let tmpSetupServer = require('../ContentSystem-Server-Setup.js');
|
|
60
80
|
|
|
@@ -62,7 +82,8 @@ class ContentSystemCommandServe extends libCommandLineCommand
|
|
|
62
82
|
{
|
|
63
83
|
ContentPath: tmpContentPath,
|
|
64
84
|
DistPath: tmpDistPath,
|
|
65
|
-
Port: tmpPort
|
|
85
|
+
Port: tmpPort,
|
|
86
|
+
Beacon: tmpBeaconConfig
|
|
66
87
|
},
|
|
67
88
|
function (pError, pServerInfo)
|
|
68
89
|
{
|