@heswell/vuu-portal 2.1.2 → 2.2.0

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";
1
+ import { ConfigFactory, EditSessionRpcHandler, ModuleFactory, Provider, TableDef, VUU_DEFAULT_COLUMNS, ViewPortDef, createConfiguredAuthProviders, createVuuServerApplication, getLogger } from "@heswell/vuu-server";
2
2
  import { MODULE_ADMIN_RPC, executeModuleAdminRpc, managedModuleColumnValues, managedModulePermissionValues, validateModuleConfig } from "@heswell/module-admin";
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}`;
@@ -424,11 +425,69 @@ function columnsFor(table) {
424
425
  function valuesToRow(table, values) {
425
426
  return table.columns.map(({ name })=>values[name] ?? "");
426
427
  }
428
+ const here = node_path.dirname(fileURLToPath(import.meta.url));
429
+ const BUNDLED_CONFIG_FILE = [
430
+ node_path.resolve(here, "../config/application.conf"),
431
+ node_path.resolve(here, "config/application.conf")
432
+ ].find((candidate)=>node_fs.existsSync(candidate));
433
+ const existingFile = (file, source)=>{
434
+ if (!node_fs.existsSync(file)) throw new Error(`Config file not found at '${file}' (from ${source})`);
435
+ return file;
436
+ };
437
+ function resolvePortalConfig({ configFile, cwd = process.cwd(), env = process.env } = {}) {
438
+ if (configFile) return {
439
+ configFile: existingFile(node_path.resolve(cwd, configFile), "--config"),
440
+ source: "--config"
441
+ };
442
+ const envFile = env.VUU_CONFIG_FILE?.trim();
443
+ if (envFile) return {
444
+ configFile: existingFile(node_path.resolve(cwd, envFile), "VUU_CONFIG_FILE"),
445
+ source: "VUU_CONFIG_FILE"
446
+ };
447
+ const appName = env.VUU_APP?.trim();
448
+ if (appName) return {
449
+ configFile: existingFile(node_path.resolve(cwd, "packages", appName, "application.conf"), "VUU_APP"),
450
+ source: "VUU_APP"
451
+ };
452
+ const cwdFile = node_path.resolve(cwd, "application.conf");
453
+ if (node_fs.existsSync(cwdFile)) return {
454
+ configFile: cwdFile,
455
+ source: "cwd"
456
+ };
457
+ if (!BUNDLED_CONFIG_FILE) throw new Error("Unable to locate application.conf: pass --config, or set VUU_CONFIG_FILE or VUU_APP.");
458
+ const overrides = {
459
+ "vuu.portal.modulesFile": node_path.resolve(cwd, env.VUU_PORTAL_MODULES_FILE?.trim() || "modules.yaml")
460
+ };
461
+ for (const [key, envName] of BUNDLED_PATH_ENV){
462
+ const value = env[envName]?.trim();
463
+ if (value) overrides[key] = node_path.resolve(cwd, value);
464
+ }
465
+ return {
466
+ configFile: BUNDLED_CONFIG_FILE,
467
+ source: "bundled",
468
+ overrides
469
+ };
470
+ }
471
+ const BUNDLED_PATH_ENV = [
472
+ [
473
+ "vuu.certPath",
474
+ "VUU_CERT_PATH"
475
+ ],
476
+ [
477
+ "vuu.keyPath",
478
+ "VUU_KEY_PATH"
479
+ ],
480
+ [
481
+ "vuu.portal.defaultModulesFile",
482
+ "VUU_PORTAL_DEFAULT_MODULES_FILE"
483
+ ]
484
+ ];
485
+ const log = getLogger("vuu.portal");
427
486
  const PORTAL_DEFAULT_HTTPS_PORT = 8443;
428
487
  const PORTAL_DEFAULT_WEBSOCKET_PATH = "/websocket-portal";
429
488
  const PORTAL_DEFAULT_WEBSOCKET_PORT = 8091;
430
489
  function createPortalApplication({ config, configFile } = {}) {
431
- const portalConfig = config ?? ConfigFactory.load(configFile);
490
+ const portalConfig = config ?? loadPortalConfig(configFile);
432
491
  return createVuuServerApplication({
433
492
  authProviders: createConfiguredAuthProviders(portalConfig),
434
493
  config: portalConfig,
@@ -445,4 +504,16 @@ function createPortalApplication({ config, configFile } = {}) {
445
504
  ]
446
505
  });
447
506
  }
448
- export { DEFAULT_MODULE_ADMIN_ROLE, InMemoryModuleStore, ModuleDiscoveryModule, ModuleState, PORTAL_DEFAULT_WEBSOCKET_PATH, YamlModuleStore, createModuleRegistry, createModuleState, createPortalApplication, loadDefaultModules, loadModuleAdminRole, loadModuleState, moduleClientIdentifier };
507
+ function loadPortalConfig(configFile) {
508
+ const resolved = resolvePortalConfig({
509
+ configFile
510
+ });
511
+ const config = ConfigFactory.load(resolved.configFile, {
512
+ overrides: resolved.overrides
513
+ });
514
+ log.info(`using ${resolved.configFile} (from ${resolved.source})`);
515
+ 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})`);
516
+ 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")}`);
517
+ return config;
518
+ }
519
+ 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
@@ -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,22 @@
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
+ 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.0",
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";
@@ -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;