@heswell/vuu-portal 2.1.2 → 2.2.1

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/195.js CHANGED
@@ -1,7 +1,8 @@
1
- import { ConfigFactory, EditSessionRpcHandler, ModuleFactory, Provider, TableDef, VUU_DEFAULT_COLUMNS, ViewPortDef, createConfiguredAuthProviders, createVuuServerApplication } from "@heswell/vuu-server";
2
- import { MODULE_ADMIN_RPC, executeModuleAdminRpc, managedModuleColumnValues, managedModulePermissionValues, validateModuleConfig } from "@heswell/module-admin";
1
+ import { MODULE_ADMIN_RPC, executeModuleAdminRpc, managedModuleColumnValues, managedModulePermissionValues, splitNotificationTopics, validateModuleConfig } from "@heswell/module-admin";
2
+ import { ConfigFactory, EditSessionRpcHandler, ModuleFactory, NotificationModule, NullProvider, PermissionFilter, Provider, TableDef, VUU_DEFAULT_COLUMNS, ViewPortDef, createConfiguredAuthProviders, createVuuServerApplication, getLogger } from "@heswell/vuu-server";
3
3
  import node_fs from "node:fs";
4
4
  import node_path from "node:path";
5
+ import { fileURLToPath } from "node:url";
5
6
  function moduleClientIdentifier(name) {
6
7
  const kebab = name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
7
8
  return kebab.startsWith("vuu-") ? kebab : `vuu-${kebab}`;
@@ -30,7 +31,8 @@ function readModules(table) {
30
31
  mfComponent: stringValue(table, row, "mfComponent"),
31
32
  mfScope: stringValue(table, row, "mfScope"),
32
33
  mfUrl: stringValue(table, row, "mfUrl"),
33
- ...navIcon(table, row)
34
+ ...navIcon(table, row),
35
+ ...navMetadata(table, row)
34
36
  };
35
37
  });
36
38
  }
@@ -40,6 +42,23 @@ function navIcon(table, row) {
40
42
  navIconUrl
41
43
  } : {};
42
44
  }
45
+ function navMetadata(table, row) {
46
+ const metadata = {};
47
+ const navOrder = optionalValue(table, row, "navOrder");
48
+ if ("number" == typeof navOrder && Number.isFinite(navOrder)) metadata.navOrder = navOrder;
49
+ const navBadge = optionalValue(table, row, "navBadge");
50
+ if ("string" == typeof navBadge && navBadge) metadata.navBadge = navBadge;
51
+ const topics = optionalValue(table, row, "notificationTopics");
52
+ if ("string" == typeof topics) {
53
+ const notificationTopics = splitNotificationTopics(topics);
54
+ if (notificationTopics.length) metadata.notificationTopics = notificationTopics;
55
+ }
56
+ return metadata;
57
+ }
58
+ function optionalValue(table, row, column) {
59
+ const index = table.tableDef.columns.findIndex(({ name })=>name === column);
60
+ return -1 === index ? void 0 : row[index];
61
+ }
43
62
  function readModulePermissions(table) {
44
63
  return table.rows.map((row)=>({
45
64
  moduleId: numberValue(table, row, "module_id"),
@@ -69,7 +88,13 @@ function selectModules(modules, modulePermissions, authorizations) {
69
88
  ...latestByName.values()
70
89
  ];
71
90
  const permittedIds = new Set(permittedModules.map(({ id })=>id));
72
- return permittedModules.filter(({ parentModuleId })=>0 === parentModuleId || permittedIds.has(parentModuleId)).map(({ parentModuleId: _parentModuleId, ...module })=>module).sort((left, right)=>left.name.localeCompare(right.name));
91
+ return permittedModules.filter(({ parentModuleId })=>0 === parentModuleId || permittedIds.has(parentModuleId)).map(({ parentModuleId: _parentModuleId, ...module })=>module).sort(compareModules);
92
+ }
93
+ function compareModules(left, right) {
94
+ const leftOrder = left.navOrder ?? 1 / 0;
95
+ const rightOrder = right.navOrder ?? 1 / 0;
96
+ if (leftOrder !== rightOrder) return leftOrder < rightOrder ? -1 : 1;
97
+ return left.name.localeCompare(right.name);
73
98
  }
74
99
  function accessRoleForModule(module, moduleRoles, permittedModuleRoles) {
75
100
  const ownRole = permittedModuleRoles.get(module.id);
@@ -151,6 +176,18 @@ const modulesTable = TableDef({
151
176
  name: "navIconUrl",
152
177
  dataType: "string"
153
178
  },
179
+ {
180
+ name: "navOrder",
181
+ dataType: "double"
182
+ },
183
+ {
184
+ name: "navBadge",
185
+ dataType: "string"
186
+ },
187
+ {
188
+ name: "notificationTopics",
189
+ dataType: "string"
190
+ },
154
191
  ...VUU_DEFAULT_COLUMNS
155
192
  ],
156
193
  keyField: "id",
@@ -331,9 +368,12 @@ function readModulesFile(filePath) {
331
368
  return parsed.modules;
332
369
  }
333
370
  function cloneModules(modules) {
334
- return modules.map((module)=>({
335
- ...module
336
- }));
371
+ return modules.map((module)=>Object.fromEntries(Object.entries(module).filter(([, value])=>void 0 !== value).map(([key, value])=>[
372
+ key,
373
+ Array.isArray(value) ? [
374
+ ...value
375
+ ] : value
376
+ ])));
337
377
  }
338
378
  const managedModuleKeys = [
339
379
  "id",
@@ -353,6 +393,11 @@ const managedModuleKeys = [
353
393
  "navIconUrl",
354
394
  "accessRole"
355
395
  ];
396
+ const optionalModuleKeys = [
397
+ "navOrder",
398
+ "navBadge",
399
+ "notificationTopics"
400
+ ];
356
401
  const retiredModuleKeys = [
357
402
  "vuuConnectionId",
358
403
  "vuuWebsocketUrl",
@@ -360,7 +405,7 @@ const retiredModuleKeys = [
360
405
  ];
361
406
  function parseManagedModule(value, index) {
362
407
  if (!isRecord(value)) throw new Error(`Modules file entry ${index} must be an object`);
363
- for (const key of Object.keys(value))if (!managedModuleKeys.includes(key) && !retiredModuleKeys.includes(key)) throw new Error(`Modules file entry ${index} contains unknown field '${key}'`);
408
+ for (const key of Object.keys(value))if (!managedModuleKeys.includes(key) && !optionalModuleKeys.includes(key) && !retiredModuleKeys.includes(key)) throw new Error(`Modules file entry ${index} contains unknown field '${key}'`);
364
409
  const module = {};
365
410
  for (const key of managedModuleKeys){
366
411
  const fieldValue = value[key];
@@ -376,6 +421,18 @@ function parseManagedModule(value, index) {
376
421
  if ("number" === expected && !Number.isInteger(fieldValue)) throw new Error(`Modules file entry ${index} field '${key}' must be an integer`);
377
422
  module[key] = fieldValue;
378
423
  }
424
+ for (const key of optionalModuleKeys){
425
+ const fieldValue = value[key];
426
+ if (null == fieldValue) continue;
427
+ const valid = "navOrder" === key ? "number" == typeof fieldValue : "navBadge" === key ? "string" == typeof fieldValue : Array.isArray(fieldValue) && fieldValue.every((topic)=>"string" == typeof topic);
428
+ if (!valid) {
429
+ const expected = "navOrder" === key ? "a number" : "navBadge" === key ? "a string" : "a list of strings";
430
+ throw new Error(`Modules file entry ${index} field '${key}' must be ${expected}`);
431
+ }
432
+ module[key] = Array.isArray(fieldValue) ? [
433
+ ...fieldValue
434
+ ] : fieldValue;
435
+ }
379
436
  return module;
380
437
  }
381
438
  function assertValidManagedModules(modules, filePath) {
@@ -424,11 +481,69 @@ function columnsFor(table) {
424
481
  function valuesToRow(table, values) {
425
482
  return table.columns.map(({ name })=>values[name] ?? "");
426
483
  }
484
+ const here = node_path.dirname(fileURLToPath(import.meta.url));
485
+ const BUNDLED_CONFIG_FILE = [
486
+ node_path.resolve(here, "../config/application.conf"),
487
+ node_path.resolve(here, "config/application.conf")
488
+ ].find((candidate)=>node_fs.existsSync(candidate));
489
+ const existingFile = (file, source)=>{
490
+ if (!node_fs.existsSync(file)) throw new Error(`Config file not found at '${file}' (from ${source})`);
491
+ return file;
492
+ };
493
+ function resolvePortalConfig({ configFile, cwd = process.cwd(), env = process.env } = {}) {
494
+ if (configFile) return {
495
+ configFile: existingFile(node_path.resolve(cwd, configFile), "--config"),
496
+ source: "--config"
497
+ };
498
+ const envFile = env.VUU_CONFIG_FILE?.trim();
499
+ if (envFile) return {
500
+ configFile: existingFile(node_path.resolve(cwd, envFile), "VUU_CONFIG_FILE"),
501
+ source: "VUU_CONFIG_FILE"
502
+ };
503
+ const appName = env.VUU_APP?.trim();
504
+ if (appName) return {
505
+ configFile: existingFile(node_path.resolve(cwd, "packages", appName, "application.conf"), "VUU_APP"),
506
+ source: "VUU_APP"
507
+ };
508
+ const cwdFile = node_path.resolve(cwd, "application.conf");
509
+ if (node_fs.existsSync(cwdFile)) return {
510
+ configFile: cwdFile,
511
+ source: "cwd"
512
+ };
513
+ if (!BUNDLED_CONFIG_FILE) throw new Error("Unable to locate application.conf: pass --config, or set VUU_CONFIG_FILE or VUU_APP.");
514
+ const overrides = {
515
+ "vuu.portal.modulesFile": node_path.resolve(cwd, env.VUU_PORTAL_MODULES_FILE?.trim() || "modules.yaml")
516
+ };
517
+ for (const [key, envName] of BUNDLED_PATH_ENV){
518
+ const value = env[envName]?.trim();
519
+ if (value) overrides[key] = node_path.resolve(cwd, value);
520
+ }
521
+ return {
522
+ configFile: BUNDLED_CONFIG_FILE,
523
+ source: "bundled",
524
+ overrides
525
+ };
526
+ }
527
+ const BUNDLED_PATH_ENV = [
528
+ [
529
+ "vuu.certPath",
530
+ "VUU_CERT_PATH"
531
+ ],
532
+ [
533
+ "vuu.keyPath",
534
+ "VUU_KEY_PATH"
535
+ ],
536
+ [
537
+ "vuu.portal.defaultModulesFile",
538
+ "VUU_PORTAL_DEFAULT_MODULES_FILE"
539
+ ]
540
+ ];
541
+ const log = getLogger("vuu.portal");
427
542
  const PORTAL_DEFAULT_HTTPS_PORT = 8443;
428
543
  const PORTAL_DEFAULT_WEBSOCKET_PATH = "/websocket-portal";
429
544
  const PORTAL_DEFAULT_WEBSOCKET_PORT = 8091;
430
545
  function createPortalApplication({ config, configFile } = {}) {
431
- const portalConfig = config ?? ConfigFactory.load(configFile);
546
+ const portalConfig = config ?? loadPortalConfig(configFile);
432
547
  return createVuuServerApplication({
433
548
  authProviders: createConfiguredAuthProviders(portalConfig),
434
549
  config: portalConfig,
@@ -439,10 +554,26 @@ function createPortalApplication({ config, configFile } = {}) {
439
554
  moduleRegistry: createModuleRegistry(tableContainer, user)
440
555
  }),
441
556
  modules: [
557
+ NotificationModule((table)=>new NullProvider(table), (viewport)=>PermissionFilter("audience", new Set([
558
+ "all",
559
+ viewport.user.name
560
+ ])), (table)=>ViewPortDef["default"](table.tableDef.columns)),
442
561
  ModuleDiscoveryModule(loadModuleState(portalConfig), {
443
562
  adminRole: loadModuleAdminRole(portalConfig)
444
563
  })
445
564
  ]
446
565
  });
447
566
  }
448
- export { DEFAULT_MODULE_ADMIN_ROLE, InMemoryModuleStore, ModuleDiscoveryModule, ModuleState, PORTAL_DEFAULT_WEBSOCKET_PATH, YamlModuleStore, createModuleRegistry, createModuleState, createPortalApplication, loadDefaultModules, loadModuleAdminRole, loadModuleState, moduleClientIdentifier };
567
+ function loadPortalConfig(configFile) {
568
+ const resolved = resolvePortalConfig({
569
+ configFile
570
+ });
571
+ const config = ConfigFactory.load(resolved.configFile, {
572
+ overrides: resolved.overrides
573
+ });
574
+ log.info(`using ${resolved.configFile} (from ${resolved.source})`);
575
+ if (config.getBoolean("vuu.ssl", false) && !(config.has("vuu.certPath") && config.has("vuu.keyPath"))) throw new Error(`vuu.ssl=true needs vuu.certPath and vuu.keyPath (set VUU_CERT_PATH and VUU_KEY_PATH, or set them in ${resolved.configFile})`);
576
+ if ("bundled" === resolved.source) log.warn(`no application.conf given: using the bundled default (vuu.auth.mode=${config.getString("vuu.auth.mode", "")}, vuu.ssl=${config.getBoolean("vuu.ssl", false)}). Pass --config or set VUU_CONFIG_FILE for anything but local development. Module catalogue: ${config.getPath("vuu.portal.modulesFile")}`);
577
+ return config;
578
+ }
579
+ export { BUNDLED_CONFIG_FILE, DEFAULT_MODULE_ADMIN_ROLE, InMemoryModuleStore, ModuleDiscoveryModule, ModuleState, PORTAL_DEFAULT_WEBSOCKET_PATH, YamlModuleStore, createModuleRegistry, createModuleState, createPortalApplication, loadDefaultModules, loadModuleAdminRole, loadModuleState, moduleClientIdentifier, resolvePortalConfig };
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # @heswell/vuu-portal
2
2
 
3
3
  Vuu portal server: a [`@heswell/vuu-server`](../vuu-server) application that
4
- hosts the `MODULE_DISCOVERY` module and returns each user's module registry in
4
+ hosts the `MODULE_DISCOVERY` and `NOTIFICATIONS` modules and returns each user's module registry in
5
5
  the login response. Requires the [Bun](https://bun.sh) runtime to run the
6
6
  server. The library exports can be imported under Node.js too.
7
7
 
@@ -49,11 +49,13 @@ Root exports:
49
49
 
50
50
  ## Starting the portal
51
51
 
52
- Everything is configured in `application.conf`. Point the portal at your own
53
- conf and run the stock entry unchanged:
52
+ Everything is configured in `application.conf`. With no conf, the portal starts
53
+ from the default bundled with the package, for local development. Point it at
54
+ your own conf to run the stock entry unchanged:
54
55
 
55
56
  ```sh
56
57
  # bin
58
+ bunx vuu-portal # bundled default conf
57
59
  bunx vuu-portal --config ./path/to/application.conf
58
60
  VUU_CONFIG_FILE=./path/to/application.conf bunx vuu-portal
59
61
 
@@ -70,6 +72,31 @@ The conf file is located in this order:
70
72
  3. `$VUU_APP`, which selects `packages/$VUU_APP/application.conf` under the
71
73
  current directory.
72
74
  4. `./application.conf`.
75
+ 5. The conf bundled with the package,
76
+ [`config/application.conf`](./config/application.conf).
77
+
78
+ The portal logs which conf it used (logger `vuu.portal`).
79
+
80
+ ### The bundled default conf
81
+
82
+ `config/application.conf` is meant for zero-config local development, and the
83
+ portal logs a warning when it is used:
84
+
85
+ - `vuu.auth.mode="permissive"`: any username and password may log in.
86
+ - `vuu.ssl=false`: the websocket is `ws://` and the REST server `http://`. No
87
+ certificate or key is shipped; for TLS set `VUU_SSL=true`, `VUU_CERT_PATH`
88
+ and `VUU_KEY_PATH`. `vuu.ssl=true` without both paths fails at startup.
89
+ - Websocket on 8091 (`/websocket-portal`), REST on 8443.
90
+ - No Keycloak secret is shipped; set `VUU_KEYCLOAK_CLIENT_SECRET` (and
91
+ `VUU_KEYCLOAK_URL`) with `VUU_AUTH_MODE=keycloak`.
92
+ - The seed catalogue, [`config/default-modules.yaml`](./config/default-modules.yaml),
93
+ is empty. The writable `modules.yaml` is kept in the current directory (or at
94
+ `$VUU_PORTAL_MODULES_FILE`), never in the installed package.
95
+ `VUU_PORTAL_MODULES_FILE`, `VUU_PORTAL_DEFAULT_MODULES_FILE`,
96
+ `VUU_CERT_PATH` and `VUU_KEY_PATH` resolve against the current directory.
97
+
98
+ Every setting has a `VUU_*` override; see the comments in the file, or
99
+ `vuu-portal --help`. For anything else, copy it and pass `--config`.
73
100
 
74
101
  In this repository, `npm run start:portal` starts it with
75
102
  `VUU_APP=vuu-portal`, which uses [`application.conf`](./application.conf).
package/bin.js CHANGED
@@ -6,7 +6,13 @@ Starts the Vuu portal server. All settings (ports, websocket path, auth,
6
6
  module catalogue files, module admin role) come from application.conf.
7
7
 
8
8
  Without --config the conf is located via $VUU_CONFIG_FILE, then $VUU_APP
9
- (packages/<app>/application.conf), then ./application.conf.`;
9
+ (packages/<app>/application.conf), then ./application.conf, then the
10
+ conf bundled with the package. The bundled conf is for local development:
11
+ permissive auth, no TLS (ws:// and http://), websocket on 8091 and REST on
12
+ 8443, and modules.yaml in the current directory. Override it with
13
+ VUU_AUTH_MODE, VUU_SSL, VUU_CERT_PATH, VUU_KEY_PATH, VUU_WEBSOCKET_PORT,
14
+ VUU_HTTPS_PORT, VUU_WEBSOCKET_PATH, VUU_PORTAL_MODULES_FILE,
15
+ VUU_PORTAL_DEFAULT_MODULES_FILE, VUU_CORS_ALLOWED_ORIGINS, VUU_LOG_LEVEL.`;
10
16
  const parseConfigFile = (args)=>{
11
17
  let configFile;
12
18
  for(let index = 0; index < args.length; index++){
@@ -0,0 +1,69 @@
1
+ # Default vuu-portal configuration, bundled with the package. It is used when
2
+ # no conf is given: no --config, VUU_CONFIG_FILE or VUU_APP, and no
3
+ # ./application.conf in the current directory.
4
+ #
5
+ # Local development only: any username and password may log in, and the
6
+ # websocket and REST servers run without TLS. Every setting can be changed
7
+ # with the VUU_* environment variable next to it, or copy this file, edit it
8
+ # and pass --config.
9
+ #
10
+ # Relative paths in this file resolve against this directory (inside the
11
+ # installed package). The writable module catalogue (modules.yaml) is kept in
12
+ # the current directory instead, and VUU_PORTAL_MODULES_FILE, VUU_CERT_PATH,
13
+ # VUU_KEY_PATH and VUU_PORTAL_DEFAULT_MODULES_FILE resolve against the
14
+ # current directory.
15
+
16
+ vuu.auth.mode="permissive"
17
+ vuu.auth.mode=${?VUU_AUTH_MODE}
18
+ vuu.auth.path="/api/authn"
19
+ # Comma-separated username:password entries (or a bare username, for a user
20
+ # that may use any password). Empty allows any username with any password.
21
+ vuu.auth.permissive.users=""
22
+ vuu.auth.permissive.users=${?VUU_AUTH_PERMISSIVE_USERS}
23
+
24
+ # TLS is off by default: ws:// and http://. No certificate is shipped. To use
25
+ # TLS set VUU_SSL=true, VUU_CERT_PATH and VUU_KEY_PATH. vuu.ssl=false is only
26
+ # allowed with vuu.auth.mode="permissive".
27
+ vuu.ssl=false
28
+ vuu.ssl=${?VUU_SSL}
29
+ vuu.certPath=${?VUU_CERT_PATH}
30
+ vuu.keyPath=${?VUU_KEY_PATH}
31
+
32
+ vuu.websocket.path="/websocket-portal"
33
+ vuu.websocket.path=${?VUU_WEBSOCKET_PATH}
34
+ vuu.websocket.port=8091
35
+ vuu.websocket.port=${?VUU_WEBSOCKET_PORT}
36
+ vuu.https.port=8443
37
+ vuu.https.port=${?VUU_HTTPS_PORT}
38
+
39
+ # Browser origins allowed to call the REST auth endpoint (CORS).
40
+ # VUU_CORS_ALLOWED_ORIGINS replaces the list with a comma-separated list.
41
+ vuu.auth.cors.allowedOrigins=["http://localhost:5173", "http://127.0.0.1:5173", "http://localhost:5001", "http://127.0.0.1:5001"]
42
+ vuu.auth.cors.allowedOrigins=${?VUU_CORS_ALLOWED_ORIGINS}
43
+
44
+ # Keycloak, used only with VUU_AUTH_MODE=keycloak. No secret is shipped.
45
+ vuu.keycloak.url=${?VUU_KEYCLOAK_URL}
46
+ vuu.keycloak.realm="vuu"
47
+ vuu.keycloak.realm=${?VUU_KEYCLOAK_REALM}
48
+ vuu.keycloak.allowSelfSignedCert=false
49
+ vuu.keycloak.allowSelfSignedCert=${?VUU_KEYCLOAK_ALLOW_SELF_SIGNED_CERT}
50
+ vuu.auth.keycloak.clientId="vuu-portal-server"
51
+ vuu.auth.keycloak.clientId=${?VUU_KEYCLOAK_CLIENT_ID}
52
+ vuu.auth.keycloak.audience="vuu-portal-server"
53
+ vuu.auth.keycloak.audience=${?VUU_KEYCLOAK_AUDIENCE}
54
+ vuu.auth.keycloak.audiencePolicy="require-audience"
55
+ vuu.auth.keycloak.authorizationClientId="vuu-portal"
56
+ vuu.auth.keycloak.expectedAuthorizedParty="vuu-portal"
57
+ vuu.auth.keycloak.tokenExchangeEnabled=false
58
+ vuu.auth.keycloak.clientSecret=${?VUU_KEYCLOAK_CLIENT_SECRET}
59
+
60
+ # The module catalogue. defaultModulesFile (read-only, in this directory)
61
+ # seeds modulesFile on first startup; runtime edits are saved to modulesFile.
62
+ vuu.portal.defaultModulesFile="default-modules.yaml"
63
+ vuu.portal.modulesFile="modules.yaml"
64
+ vuu.portal.moduleAdminRole="module-admin-access"
65
+ vuu.portal.moduleAdminRole=${?VUU_PORTAL_MODULE_ADMIN_ROLE}
66
+
67
+ # Log level: trace, debug, info, warn, error or silent.
68
+ vuu.log.level="info"
69
+ vuu.log.level=${?VUU_LOG_LEVEL}
@@ -0,0 +1,26 @@
1
+ # Default portal modules for the bundled application.conf. Seeds modules.yaml
2
+ # (in the current directory) on first startup; add modules there, or with the
3
+ # module admin service. To ship your own defaults, point
4
+ # VUU_PORTAL_DEFAULT_MODULES_FILE at a file like this one.
5
+ #
6
+ # Example entry:
7
+ #
8
+ # - id: 1
9
+ # version: 1
10
+ # parentModuleId: 0
11
+ # name: "moduleAdmin"
12
+ # title: "Manage remote modules"
13
+ # description: "Create new remote module, update existing modules"
14
+ # enabled: true
15
+ # location: "/Modules/Manage Modules"
16
+ # path: "/modules/admin"
17
+ # mfComponent: "ModuleAdmin"
18
+ # mfScope: "moduleAdmin"
19
+ # mfUrl: "http://localhost:5002"
20
+ # navIconUrl: ""
21
+ # accessRole: "module-admin-access"
22
+ # # Optional nav metadata:
23
+ # navOrder: 1
24
+ # navBadge: "modules.pending"
25
+ # notificationTopics: ["modules"]
26
+ modules: []
package/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  var src_PORTAL_DEFAULT_HTTPS_PORT = 8443;
2
2
  var src_PORTAL_DEFAULT_WEBSOCKET_PORT = 8091;
3
- export { DEFAULT_MODULE_ADMIN_ROLE, InMemoryModuleStore, ModuleDiscoveryModule, ModuleState, PORTAL_DEFAULT_WEBSOCKET_PATH, YamlModuleStore, createModuleRegistry, createModuleState, createPortalApplication, loadDefaultModules, loadModuleAdminRole, loadModuleState, moduleClientIdentifier } from "./195.js";
3
+ export { BUNDLED_CONFIG_FILE, DEFAULT_MODULE_ADMIN_ROLE, InMemoryModuleStore, ModuleDiscoveryModule, ModuleState, PORTAL_DEFAULT_WEBSOCKET_PATH, YamlModuleStore, createModuleRegistry, createModuleState, createPortalApplication, loadDefaultModules, loadModuleAdminRole, loadModuleState, moduleClientIdentifier, resolvePortalConfig } from "./195.js";
4
4
  export { src_PORTAL_DEFAULT_HTTPS_PORT as PORTAL_DEFAULT_HTTPS_PORT, src_PORTAL_DEFAULT_WEBSOCKET_PORT as PORTAL_DEFAULT_WEBSOCKET_PORT };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heswell/vuu-portal",
3
- "version": "2.1.2",
3
+ "version": "2.2.1",
4
4
  "description": "VUU portal server with remote module discovery",
5
5
  "type": "module",
6
6
  "main": "./index.js",
@@ -51,6 +51,7 @@
51
51
  "files": [
52
52
  "*.js",
53
53
  "src/**/*.d.ts",
54
- "README.md"
54
+ "README.md",
55
+ "config"
55
56
  ]
56
57
  }
@@ -7,8 +7,9 @@ export type PortalApplicationOptions = {
7
7
  config?: Config;
8
8
  /**
9
9
  * Path to the portal's application.conf, resolved against the current
10
- * directory. Defaults to ConfigFactory's lookup: $VUU_CONFIG_FILE, then
11
- * $VUU_APP, then ./application.conf.
10
+ * directory. Defaults to $VUU_CONFIG_FILE, then $VUU_APP
11
+ * (packages/<app>/application.conf), then ./application.conf, then the
12
+ * conf bundled with the package (see resolvePortalConfig).
12
13
  */
13
14
  configFile?: string;
14
15
  };
package/src/index.d.ts CHANGED
@@ -3,3 +3,4 @@ export { loadModuleAdminRole, loadModuleState, ModuleDiscoveryModule, ModuleStat
3
3
  export { createModuleState, InMemoryModuleStore, loadDefaultModules, type ModuleStore, YamlModuleStore, } from "./modules/ModuleDiscovery/ModuleStore.js";
4
4
  export { DEFAULT_MODULE_ADMIN_ROLE } from "./modules/ModuleDiscovery/ModuleAdminService.js";
5
5
  export { createPortalApplication, PORTAL_DEFAULT_HTTPS_PORT, PORTAL_DEFAULT_WEBSOCKET_PATH, PORTAL_DEFAULT_WEBSOCKET_PORT, type PortalApplicationOptions, } from "./PortalApplication.js";
6
+ export { BUNDLED_CONFIG_FILE, type PortalConfigSource, type ResolvedPortalConfig, type ResolvePortalConfigOptions, resolvePortalConfig, } from "./resolvePortalConfig.js";
@@ -6,48 +6,14 @@ export interface ModuleStore {
6
6
  export declare class InMemoryModuleStore implements ModuleStore {
7
7
  private modules;
8
8
  constructor(modules?: readonly ManagedModule[]);
9
- load(): {
10
- parentModuleId: number;
11
- name: string;
12
- title: string;
13
- description: string;
14
- enabled: boolean;
15
- location: string;
16
- path: string;
17
- mfComponent: string;
18
- mfScope: string;
19
- mfUrl: string;
20
- navIconUrl: string;
21
- accessRole: string;
22
- id: number;
23
- version: number;
24
- created: number;
25
- updated: number;
26
- }[];
9
+ load(): ManagedModule[];
27
10
  save(modules: readonly ManagedModule[]): void;
28
11
  }
29
12
  export declare class ModuleState {
30
13
  #private;
31
14
  private readonly store;
32
15
  constructor(store: ModuleStore, modules: readonly ManagedModule[]);
33
- get modules(): {
34
- parentModuleId: number;
35
- name: string;
36
- title: string;
37
- description: string;
38
- enabled: boolean;
39
- location: string;
40
- path: string;
41
- mfComponent: string;
42
- mfScope: string;
43
- mfUrl: string;
44
- navIconUrl: string;
45
- accessRole: string;
46
- id: number;
47
- version: number;
48
- created: number;
49
- updated: number;
50
- }[];
16
+ get modules(): ManagedModule[];
51
17
  replaceAndSave(modules: readonly ManagedModule[]): void;
52
18
  }
53
19
  export declare class YamlModuleStore implements ModuleStore {
@@ -0,0 +1,31 @@
1
+ import type { ConfigValue } from "@heswell/vuu-server";
2
+ export type PortalConfigSource = "--config" | "VUU_CONFIG_FILE" | "VUU_APP" | "cwd" | "bundled";
3
+ export type ResolvedPortalConfig = {
4
+ /** Absolute path of the application.conf to load. */
5
+ configFile: string;
6
+ source: PortalConfigSource;
7
+ /**
8
+ * Config overrides. Only set for the bundled conf: the writable module
9
+ * catalogue (vuu.portal.modulesFile) goes in the current directory, or at
10
+ * $VUU_PORTAL_MODULES_FILE, never inside the installed package; and
11
+ * $VUU_CERT_PATH, $VUU_KEY_PATH and $VUU_PORTAL_DEFAULT_MODULES_FILE
12
+ * resolve against the current directory.
13
+ */
14
+ overrides?: Record<string, ConfigValue>;
15
+ };
16
+ export type ResolvePortalConfigOptions = {
17
+ configFile?: string;
18
+ cwd?: string;
19
+ env?: Record<string, string | undefined>;
20
+ };
21
+ /**
22
+ * The application.conf shipped in the package's config/ directory. Source
23
+ * modules live in src/, the built entries at the package root.
24
+ */
25
+ export declare const BUNDLED_CONFIG_FILE: string;
26
+ /**
27
+ * Locates the portal's application.conf: --config, then $VUU_CONFIG_FILE,
28
+ * then $VUU_APP (packages/<app>/application.conf), then ./application.conf,
29
+ * then the conf bundled with the package.
30
+ */
31
+ export declare function resolvePortalConfig({ configFile, cwd, env, }?: ResolvePortalConfigOptions): ResolvedPortalConfig;