@twin.org/node-core 0.0.2-next.17 → 0.0.2-next.18

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.
@@ -11,6 +11,7 @@ var vaultModels = require('@twin.org/vault-models');
11
11
  var walletModels = require('@twin.org/wallet-models');
12
12
  var promises = require('node:fs/promises');
13
13
  var path = require('node:path');
14
+ var cliCore = require('@twin.org/cli-core');
14
15
  var rightsManagementRestClient = require('@twin.org/rights-management-rest-client');
15
16
  var engineServer = require('@twin.org/engine-server');
16
17
  var modules = require('@twin.org/modules');
@@ -70,20 +71,19 @@ const NodeFeatures = {
70
71
 
71
72
  // Copyright 2024 IOTA Stiftung.
72
73
  // SPDX-License-Identifier: Apache-2.0.
73
- /* eslint-disable no-console */
74
74
  /**
75
75
  * Initialise the locales for the application.
76
76
  * @param localesDirectory The directory containing the locales.
77
77
  */
78
78
  async function initialiseLocales(localesDirectory) {
79
79
  const localesFile = path.resolve(path.join(localesDirectory, "en.json"));
80
- console.info("Locales File:", localesFile);
80
+ cliCore.CLIDisplay.value("Locales File", localesFile);
81
81
  if (await fileExists(localesFile)) {
82
82
  const enLangContent = await promises.readFile(localesFile, "utf8");
83
83
  core.I18n.addDictionary("en", JSON.parse(enLangContent));
84
84
  }
85
85
  else {
86
- console.warn(`Locales file not found: ${localesFile}`);
86
+ cliCore.CLIDisplay.error(`Locales file not found: ${localesFile}`);
87
87
  }
88
88
  }
89
89
  /**
@@ -1815,7 +1815,6 @@ async function buildEngineServerConfiguration(envVars, coreEngineConfig, serverI
1815
1815
 
1816
1816
  // Copyright 2024 IOTA Stiftung.
1817
1817
  // SPDX-License-Identifier: Apache-2.0.
1818
- /* eslint-disable no-console */
1819
1818
  /**
1820
1819
  * Handles the configuration of the extensions.
1821
1820
  * @param envVars The environment variables for the node.
@@ -1828,11 +1827,11 @@ async function extensionsConfiguration(envVars, nodeEngineConfig) {
1828
1827
  for (const extension of extensions) {
1829
1828
  let initialiseConfigMethod;
1830
1829
  try {
1831
- console.info(`Loading extension "${extension}"`);
1830
+ cliCore.CLIDisplay.value(core.I18n.formatMessage("node.extensionLoading"), extension);
1832
1831
  initialiseConfigMethod = await modules.ModuleHelper.getModuleMethod(extension, "extensionInitialise");
1833
1832
  }
1834
1833
  catch (err) {
1835
- console.error(`Failed to load extension "${extension}":`, err);
1834
+ throw new core.GeneralError("node", "extensionLoadingError", { extension }, err);
1836
1835
  }
1837
1836
  if (core.Is.function(initialiseConfigMethod)) {
1838
1837
  await initialiseConfigMethod(envVars, nodeEngineConfig);
@@ -1853,6 +1852,7 @@ async function extensionsInitialiseEngine(envVars, engineCore) {
1853
1852
  for (const extension of extensions) {
1854
1853
  let initialiseEngineMethod;
1855
1854
  try {
1855
+ engineCore.logInfo(core.I18n.formatMessage("node.extensionInitialisingEngine", { extension }));
1856
1856
  initialiseEngineMethod =
1857
1857
  await modules.ModuleHelper.getModuleMethod(extension, "extensionInitialiseEngine");
1858
1858
  }
@@ -1876,6 +1876,7 @@ async function extensionsInitialiseEngineServer(envVars, engineCore, engineServe
1876
1876
  for (const extension of extensions) {
1877
1877
  let initialiseEngineServerMethod;
1878
1878
  try {
1879
+ engineCore.logInfo(core.I18n.formatMessage("node.extensionInitialisingEngineServer", { extension }));
1879
1880
  initialiseEngineServerMethod =
1880
1881
  await modules.ModuleHelper.getModuleMethod(extension, "extensionInitialiseEngineServer");
1881
1882
  }
@@ -1889,14 +1890,16 @@ async function extensionsInitialiseEngineServer(envVars, engineCore, engineServe
1889
1890
  /**
1890
1891
  * Handles the shutdown of the extensions.
1891
1892
  * @param envVars The environment variables for the node.
1893
+ * @param engineCore The engine core instance.
1892
1894
  * @returns Nothing.
1893
1895
  */
1894
- async function shutdownExtensions(envVars) {
1896
+ async function shutdownExtensions(envVars, engineCore) {
1895
1897
  if (core.Is.stringValue(envVars.extensions)) {
1896
1898
  const extensions = envVars.extensions.split(",");
1897
1899
  for (const extension of extensions) {
1898
1900
  let shutdownMethod;
1899
1901
  try {
1902
+ engineCore.logInfo(core.I18n.formatMessage("node.extensionShutdown", { extension }));
1900
1903
  shutdownMethod = await modules.ModuleHelper.getModuleMethod(extension, "extensionShutdown");
1901
1904
  }
1902
1905
  catch { }
@@ -1909,7 +1912,6 @@ async function shutdownExtensions(envVars) {
1909
1912
 
1910
1913
  // Copyright 2024 IOTA Stiftung.
1911
1914
  // SPDX-License-Identifier: Apache-2.0.
1912
- /* eslint-disable no-console */
1913
1915
  /**
1914
1916
  * Start the engine server.
1915
1917
  * @param nodeOptions Optional run options for the engine server.
@@ -1940,13 +1942,13 @@ async function start(nodeOptions, nodeEngineConfig, envVars) {
1940
1942
  const server = new engineServer.EngineServer({ engineCore: engine$1 });
1941
1943
  // Extend the engine.
1942
1944
  if (core.Is.function(nodeOptions?.extendEngine)) {
1943
- console.info("Extending Engine");
1945
+ engine$1.logInfo(core.I18n.formatMessage("node.extendingEngine"));
1944
1946
  await nodeOptions.extendEngine(engine$1);
1945
1947
  }
1946
1948
  await extensionsInitialiseEngine(envVars, engine$1);
1947
1949
  // Extend the engine server.
1948
1950
  if (core.Is.function(nodeOptions?.extendEngineServer)) {
1949
- console.info("Extending Engine Server");
1951
+ engine$1.logInfo(core.I18n.formatMessage("node.extendingEngineServer"));
1950
1952
  await nodeOptions?.extendEngineServer(server);
1951
1953
  }
1952
1954
  await extensionsInitialiseEngineServer(envVars, engine$1, server);
@@ -1961,7 +1963,7 @@ async function start(nodeOptions, nodeEngineConfig, envVars) {
1961
1963
  server,
1962
1964
  shutdown: async () => {
1963
1965
  await server.stop();
1964
- await shutdownExtensions(envVars);
1966
+ await shutdownExtensions(envVars, engine$1);
1965
1967
  }
1966
1968
  };
1967
1969
  }
@@ -1969,7 +1971,6 @@ async function start(nodeOptions, nodeEngineConfig, envVars) {
1969
1971
 
1970
1972
  // Copyright 2024 IOTA Stiftung.
1971
1973
  // SPDX-License-Identifier: Apache-2.0.
1972
- /* eslint-disable no-console */
1973
1974
  /**
1974
1975
  * Run the TWIN Node server.
1975
1976
  * @param nodeOptions Optional configuration options for running the server.
@@ -1980,42 +1981,48 @@ async function run(nodeOptions) {
1980
1981
  nodeOptions ??= {};
1981
1982
  const serverInfo = {
1982
1983
  name: nodeOptions?.serverName ?? "TWIN Node Server",
1983
- version: nodeOptions?.serverVersion ?? "0.0.2-next.17" // x-release-please-version
1984
+ version: nodeOptions?.serverVersion ?? "0.0.2-next.18" // x-release-please-version
1984
1985
  };
1985
- console.log(`\u001B[4m🌩️ ${serverInfo.name} v${serverInfo.version}\u001B[24m\n`);
1986
+ cliCore.CLIDisplay.header(serverInfo.name, serverInfo.version, "🌩️ ");
1986
1987
  if (!core.Is.stringValue(nodeOptions?.executionDirectory)) {
1987
1988
  nodeOptions.executionDirectory = getExecutionDirectory();
1988
1989
  }
1989
- console.info("Execution Directory:", nodeOptions.executionDirectory);
1990
+ cliCore.CLIDisplay.value("Execution Directory", nodeOptions.executionDirectory);
1990
1991
  nodeOptions.localesDirectory =
1991
1992
  nodeOptions?.localesDirectory ??
1992
1993
  path.resolve(path.join(nodeOptions.executionDirectory, "dist", "locales"));
1993
- console.info("Locales Directory:", nodeOptions.localesDirectory);
1994
+ cliCore.CLIDisplay.value("Locales Directory", nodeOptions.localesDirectory);
1994
1995
  await initialiseLocales(nodeOptions.localesDirectory);
1995
1996
  if (core.Is.empty(nodeOptions?.openApiSpecFile)) {
1996
1997
  const specFile = path.resolve(path.join(nodeOptions.executionDirectory ?? "", "docs", "open-api", "spec.json"));
1997
- console.info("Default OpenAPI Spec File:", specFile);
1998
+ cliCore.CLIDisplay.value("Default OpenAPI Spec File", specFile);
1998
1999
  if (await fileExists(specFile)) {
1999
2000
  nodeOptions ??= {};
2000
2001
  nodeOptions.openApiSpecFile = specFile;
2001
2002
  }
2002
2003
  }
2004
+ else {
2005
+ cliCore.CLIDisplay.value("OpenAPI Spec File", nodeOptions.openApiSpecFile);
2006
+ }
2003
2007
  if (core.Is.empty(nodeOptions?.favIconFile)) {
2004
2008
  const favIconFile = path.resolve(path.join(nodeOptions.executionDirectory ?? "", "static", "favicon.png"));
2005
- console.info("Default Favicon File:", favIconFile);
2009
+ cliCore.CLIDisplay.value("Default Favicon File", favIconFile);
2006
2010
  if (await fileExists(favIconFile)) {
2007
2011
  nodeOptions ??= {};
2008
2012
  nodeOptions.favIconFile = favIconFile;
2009
2013
  }
2010
2014
  }
2015
+ else {
2016
+ cliCore.CLIDisplay.value("Favicon File", nodeOptions.favIconFile);
2017
+ }
2011
2018
  nodeOptions.envPrefix ??= "TWIN_NODE_";
2012
- console.info("Environment Prefix:", nodeOptions.envPrefix);
2019
+ cliCore.CLIDisplay.value("Environment Variable Prefix", nodeOptions.envPrefix);
2013
2020
  overrideModuleImport(nodeOptions.executionDirectory ?? "");
2014
2021
  const { nodeEngineConfig, nodeEnvVars: envVars } = await buildConfiguration(
2015
2022
  // This is the only location in the code base that should access process.env directly
2016
2023
  // eslint-disable-next-line no-restricted-syntax
2017
2024
  process.env, nodeOptions, serverInfo);
2018
- console.info();
2025
+ cliCore.CLIDisplay.break();
2019
2026
  const startResult = await start(nodeOptions, nodeEngineConfig, envVars);
2020
2027
  if (!core.Is.empty(startResult)) {
2021
2028
  for (const signal of ["SIGHUP", "SIGINT", "SIGTERM"]) {
@@ -2026,7 +2033,7 @@ async function run(nodeOptions) {
2026
2033
  }
2027
2034
  }
2028
2035
  catch (err) {
2029
- console.error(core.ErrorHelper.formatErrors(err).join("\n"));
2036
+ cliCore.CLIDisplay.error(err);
2030
2037
  // eslint-disable-next-line unicorn/no-process-exit
2031
2038
  process.exit(1);
2032
2039
  }
@@ -2043,7 +2050,7 @@ async function buildConfiguration(processEnv, options, serverInfo) {
2043
2050
  let defaultEnvOnly = false;
2044
2051
  if (core.Is.empty(options?.envFilenames)) {
2045
2052
  const envFile = path.resolve(path.join(options.executionDirectory ?? "", ".env"));
2046
- console.info("Default Environment File:", envFile);
2053
+ cliCore.CLIDisplay.value("Default Environment File", envFile);
2047
2054
  options ??= {};
2048
2055
  options.envFilenames = [envFile];
2049
2056
  defaultEnvOnly = true;
@@ -2071,18 +2078,18 @@ async function buildConfiguration(processEnv, options, serverInfo) {
2071
2078
  const filePath = envVars[key].slice(6).trim();
2072
2079
  const embeddedFile = path.resolve(path.join(options.executionDirectory ?? "", filePath));
2073
2080
  if (envVars[key].startsWith("@text:")) {
2074
- console.info(`Expanding Environment Variable: ${key} from text file: ${embeddedFile}`);
2081
+ cliCore.CLIDisplay.value(`Expanding Environment Variable: ${key} from text file`, embeddedFile);
2075
2082
  envVars[key] = await loadTextFile(embeddedFile);
2076
2083
  }
2077
2084
  else if (envVars[key].startsWith("@json:")) {
2078
- console.info(`Expanding Environment Variable: ${key} from JSON file: ${embeddedFile}`);
2085
+ cliCore.CLIDisplay.value(`Expanding Environment Variable: ${key} from JSON file`, embeddedFile);
2079
2086
  envVars[key] = await loadJsonFile(embeddedFile);
2080
2087
  }
2081
2088
  }
2082
2089
  }
2083
2090
  // Extend the environment variables with any additional custom configuration.
2084
2091
  if (core.Is.function(options?.extendEnvVars)) {
2085
- console.info("Extending Environment Variables");
2092
+ cliCore.CLIDisplay.task("Extending Environment Variables");
2086
2093
  await options.extendEnvVars(envVars);
2087
2094
  }
2088
2095
  // Build the engine configuration from the environment variables.
@@ -2091,19 +2098,19 @@ async function buildConfiguration(processEnv, options, serverInfo) {
2091
2098
  // Merge any custom configuration provided in the options.
2092
2099
  if (core.Is.arrayValue(options?.configFilenames)) {
2093
2100
  for (const configFile of options.configFilenames) {
2094
- console.info("Loading Configuration File:", configFile);
2101
+ cliCore.CLIDisplay.value("Loading Configuration File", configFile);
2095
2102
  const configFilePath = path.resolve(path.join(options.executionDirectory ?? "", configFile));
2096
2103
  const config = await loadJsonFile(configFilePath);
2097
2104
  Object.assign(engineServerConfig, config);
2098
2105
  }
2099
2106
  }
2100
2107
  if (core.Is.objectValue(options?.config)) {
2101
- console.info("Merging Custom Configuration");
2108
+ cliCore.CLIDisplay.task("Merging Custom Configuration");
2102
2109
  Object.assign(engineServerConfig, options.config);
2103
2110
  }
2104
2111
  // Merge any custom configuration provided in the options.
2105
2112
  if (core.Is.function(options?.extendConfig)) {
2106
- console.info("Extending Configuration");
2113
+ cliCore.CLIDisplay.task("Extending Configuration");
2107
2114
  await options.extendConfig(envVars, engineServerConfig);
2108
2115
  }
2109
2116
  const nodeEngineConfig = await extensionsConfiguration(envVars, engineServerConfig);
@@ -1,5 +1,5 @@
1
1
  import { PasswordHelper } from '@twin.org/api-auth-entity-storage-service';
2
- import { I18n, Is, Coerce, Converter, RandomHelper, Urn, GeneralError, ErrorHelper, EnvHelper } from '@twin.org/core';
2
+ import { I18n, Is, Coerce, Converter, RandomHelper, Urn, GeneralError, EnvHelper } from '@twin.org/core';
3
3
  import { PasswordGenerator, Bip39 } from '@twin.org/crypto';
4
4
  import { AuthenticationComponentType, InformationComponentType, RestRouteProcessorType, SocketRouteProcessorType, AuthenticationAdminComponentType } from '@twin.org/engine-server-types';
5
5
  import { WalletConnectorType, IdentityConnectorType, EntityStorageConnectorType, BlobStorageConnectorType, BlobStorageComponentType, VaultConnectorType, DltConfigType, LoggingConnectorType, LoggingComponentType, BackgroundTaskConnectorType, TaskSchedulerComponentType, EventBusConnectorType, EventBusComponentType, TelemetryConnectorType, TelemetryComponentType, MessagingEmailConnectorType, MessagingSmsConnectorType, MessagingPushNotificationConnectorType, MessagingAdminComponentType, MessagingComponentType, FaucetConnectorType, EngineTypeHelper, NftConnectorType, NftComponentType, VerifiableStorageConnectorType, VerifiableStorageComponentType, ImmutableProofComponentType, IdentityComponentType, IdentityResolverConnectorType, IdentityResolverComponentType, IdentityProfileConnectorType, IdentityProfileComponentType, AttestationConnectorType, AttestationComponentType, DataProcessingComponentType, DataConverterConnectorType, DataExtractorConnectorType, AuditableItemGraphComponentType, AuditableItemStreamComponentType, DocumentManagementComponentType, AuthenticationGeneratorComponentType, RightsManagementPapComponentType, RightsManagementPmpComponentType, RightsManagementPipComponentType, RightsManagementPxpComponentType, RightsManagementPdpComponentType, RightsManagementPepComponentType, RightsManagementPnpComponentType, RightsManagementPnapComponentType, RightsManagementDapComponentType, RightsManagementDarpComponentType, SynchronisedStorageComponentType, FederatedCatalogueComponentType, DataSpaceConnectorComponentType } from '@twin.org/engine-types';
@@ -9,6 +9,7 @@ import { VaultConnectorFactory, VaultKeyType } from '@twin.org/vault-models';
9
9
  import { WalletConnectorFactory } from '@twin.org/wallet-models';
10
10
  import { readFile, stat, readdir } from 'node:fs/promises';
11
11
  import path from 'node:path';
12
+ import { CLIDisplay } from '@twin.org/cli-core';
12
13
  import { PolicyNegotiationPointClient, DataAccessPointClient } from '@twin.org/rights-management-rest-client';
13
14
  import { addDefaultRestPaths, addDefaultSocketPaths, EngineServer } from '@twin.org/engine-server';
14
15
  import { ModuleHelper } from '@twin.org/modules';
@@ -49,20 +50,19 @@ const NodeFeatures = {
49
50
 
50
51
  // Copyright 2024 IOTA Stiftung.
51
52
  // SPDX-License-Identifier: Apache-2.0.
52
- /* eslint-disable no-console */
53
53
  /**
54
54
  * Initialise the locales for the application.
55
55
  * @param localesDirectory The directory containing the locales.
56
56
  */
57
57
  async function initialiseLocales(localesDirectory) {
58
58
  const localesFile = path.resolve(path.join(localesDirectory, "en.json"));
59
- console.info("Locales File:", localesFile);
59
+ CLIDisplay.value("Locales File", localesFile);
60
60
  if (await fileExists(localesFile)) {
61
61
  const enLangContent = await readFile(localesFile, "utf8");
62
62
  I18n.addDictionary("en", JSON.parse(enLangContent));
63
63
  }
64
64
  else {
65
- console.warn(`Locales file not found: ${localesFile}`);
65
+ CLIDisplay.error(`Locales file not found: ${localesFile}`);
66
66
  }
67
67
  }
68
68
  /**
@@ -1794,7 +1794,6 @@ async function buildEngineServerConfiguration(envVars, coreEngineConfig, serverI
1794
1794
 
1795
1795
  // Copyright 2024 IOTA Stiftung.
1796
1796
  // SPDX-License-Identifier: Apache-2.0.
1797
- /* eslint-disable no-console */
1798
1797
  /**
1799
1798
  * Handles the configuration of the extensions.
1800
1799
  * @param envVars The environment variables for the node.
@@ -1807,11 +1806,11 @@ async function extensionsConfiguration(envVars, nodeEngineConfig) {
1807
1806
  for (const extension of extensions) {
1808
1807
  let initialiseConfigMethod;
1809
1808
  try {
1810
- console.info(`Loading extension "${extension}"`);
1809
+ CLIDisplay.value(I18n.formatMessage("node.extensionLoading"), extension);
1811
1810
  initialiseConfigMethod = await ModuleHelper.getModuleMethod(extension, "extensionInitialise");
1812
1811
  }
1813
1812
  catch (err) {
1814
- console.error(`Failed to load extension "${extension}":`, err);
1813
+ throw new GeneralError("node", "extensionLoadingError", { extension }, err);
1815
1814
  }
1816
1815
  if (Is.function(initialiseConfigMethod)) {
1817
1816
  await initialiseConfigMethod(envVars, nodeEngineConfig);
@@ -1832,6 +1831,7 @@ async function extensionsInitialiseEngine(envVars, engineCore) {
1832
1831
  for (const extension of extensions) {
1833
1832
  let initialiseEngineMethod;
1834
1833
  try {
1834
+ engineCore.logInfo(I18n.formatMessage("node.extensionInitialisingEngine", { extension }));
1835
1835
  initialiseEngineMethod =
1836
1836
  await ModuleHelper.getModuleMethod(extension, "extensionInitialiseEngine");
1837
1837
  }
@@ -1855,6 +1855,7 @@ async function extensionsInitialiseEngineServer(envVars, engineCore, engineServe
1855
1855
  for (const extension of extensions) {
1856
1856
  let initialiseEngineServerMethod;
1857
1857
  try {
1858
+ engineCore.logInfo(I18n.formatMessage("node.extensionInitialisingEngineServer", { extension }));
1858
1859
  initialiseEngineServerMethod =
1859
1860
  await ModuleHelper.getModuleMethod(extension, "extensionInitialiseEngineServer");
1860
1861
  }
@@ -1868,14 +1869,16 @@ async function extensionsInitialiseEngineServer(envVars, engineCore, engineServe
1868
1869
  /**
1869
1870
  * Handles the shutdown of the extensions.
1870
1871
  * @param envVars The environment variables for the node.
1872
+ * @param engineCore The engine core instance.
1871
1873
  * @returns Nothing.
1872
1874
  */
1873
- async function shutdownExtensions(envVars) {
1875
+ async function shutdownExtensions(envVars, engineCore) {
1874
1876
  if (Is.stringValue(envVars.extensions)) {
1875
1877
  const extensions = envVars.extensions.split(",");
1876
1878
  for (const extension of extensions) {
1877
1879
  let shutdownMethod;
1878
1880
  try {
1881
+ engineCore.logInfo(I18n.formatMessage("node.extensionShutdown", { extension }));
1879
1882
  shutdownMethod = await ModuleHelper.getModuleMethod(extension, "extensionShutdown");
1880
1883
  }
1881
1884
  catch { }
@@ -1888,7 +1891,6 @@ async function shutdownExtensions(envVars) {
1888
1891
 
1889
1892
  // Copyright 2024 IOTA Stiftung.
1890
1893
  // SPDX-License-Identifier: Apache-2.0.
1891
- /* eslint-disable no-console */
1892
1894
  /**
1893
1895
  * Start the engine server.
1894
1896
  * @param nodeOptions Optional run options for the engine server.
@@ -1919,13 +1921,13 @@ async function start(nodeOptions, nodeEngineConfig, envVars) {
1919
1921
  const server = new EngineServer({ engineCore: engine });
1920
1922
  // Extend the engine.
1921
1923
  if (Is.function(nodeOptions?.extendEngine)) {
1922
- console.info("Extending Engine");
1924
+ engine.logInfo(I18n.formatMessage("node.extendingEngine"));
1923
1925
  await nodeOptions.extendEngine(engine);
1924
1926
  }
1925
1927
  await extensionsInitialiseEngine(envVars, engine);
1926
1928
  // Extend the engine server.
1927
1929
  if (Is.function(nodeOptions?.extendEngineServer)) {
1928
- console.info("Extending Engine Server");
1930
+ engine.logInfo(I18n.formatMessage("node.extendingEngineServer"));
1929
1931
  await nodeOptions?.extendEngineServer(server);
1930
1932
  }
1931
1933
  await extensionsInitialiseEngineServer(envVars, engine, server);
@@ -1940,7 +1942,7 @@ async function start(nodeOptions, nodeEngineConfig, envVars) {
1940
1942
  server,
1941
1943
  shutdown: async () => {
1942
1944
  await server.stop();
1943
- await shutdownExtensions(envVars);
1945
+ await shutdownExtensions(envVars, engine);
1944
1946
  }
1945
1947
  };
1946
1948
  }
@@ -1948,7 +1950,6 @@ async function start(nodeOptions, nodeEngineConfig, envVars) {
1948
1950
 
1949
1951
  // Copyright 2024 IOTA Stiftung.
1950
1952
  // SPDX-License-Identifier: Apache-2.0.
1951
- /* eslint-disable no-console */
1952
1953
  /**
1953
1954
  * Run the TWIN Node server.
1954
1955
  * @param nodeOptions Optional configuration options for running the server.
@@ -1959,42 +1960,48 @@ async function run(nodeOptions) {
1959
1960
  nodeOptions ??= {};
1960
1961
  const serverInfo = {
1961
1962
  name: nodeOptions?.serverName ?? "TWIN Node Server",
1962
- version: nodeOptions?.serverVersion ?? "0.0.2-next.17" // x-release-please-version
1963
+ version: nodeOptions?.serverVersion ?? "0.0.2-next.18" // x-release-please-version
1963
1964
  };
1964
- console.log(`\u001B[4m🌩️ ${serverInfo.name} v${serverInfo.version}\u001B[24m\n`);
1965
+ CLIDisplay.header(serverInfo.name, serverInfo.version, "🌩️ ");
1965
1966
  if (!Is.stringValue(nodeOptions?.executionDirectory)) {
1966
1967
  nodeOptions.executionDirectory = getExecutionDirectory();
1967
1968
  }
1968
- console.info("Execution Directory:", nodeOptions.executionDirectory);
1969
+ CLIDisplay.value("Execution Directory", nodeOptions.executionDirectory);
1969
1970
  nodeOptions.localesDirectory =
1970
1971
  nodeOptions?.localesDirectory ??
1971
1972
  path.resolve(path.join(nodeOptions.executionDirectory, "dist", "locales"));
1972
- console.info("Locales Directory:", nodeOptions.localesDirectory);
1973
+ CLIDisplay.value("Locales Directory", nodeOptions.localesDirectory);
1973
1974
  await initialiseLocales(nodeOptions.localesDirectory);
1974
1975
  if (Is.empty(nodeOptions?.openApiSpecFile)) {
1975
1976
  const specFile = path.resolve(path.join(nodeOptions.executionDirectory ?? "", "docs", "open-api", "spec.json"));
1976
- console.info("Default OpenAPI Spec File:", specFile);
1977
+ CLIDisplay.value("Default OpenAPI Spec File", specFile);
1977
1978
  if (await fileExists(specFile)) {
1978
1979
  nodeOptions ??= {};
1979
1980
  nodeOptions.openApiSpecFile = specFile;
1980
1981
  }
1981
1982
  }
1983
+ else {
1984
+ CLIDisplay.value("OpenAPI Spec File", nodeOptions.openApiSpecFile);
1985
+ }
1982
1986
  if (Is.empty(nodeOptions?.favIconFile)) {
1983
1987
  const favIconFile = path.resolve(path.join(nodeOptions.executionDirectory ?? "", "static", "favicon.png"));
1984
- console.info("Default Favicon File:", favIconFile);
1988
+ CLIDisplay.value("Default Favicon File", favIconFile);
1985
1989
  if (await fileExists(favIconFile)) {
1986
1990
  nodeOptions ??= {};
1987
1991
  nodeOptions.favIconFile = favIconFile;
1988
1992
  }
1989
1993
  }
1994
+ else {
1995
+ CLIDisplay.value("Favicon File", nodeOptions.favIconFile);
1996
+ }
1990
1997
  nodeOptions.envPrefix ??= "TWIN_NODE_";
1991
- console.info("Environment Prefix:", nodeOptions.envPrefix);
1998
+ CLIDisplay.value("Environment Variable Prefix", nodeOptions.envPrefix);
1992
1999
  overrideModuleImport(nodeOptions.executionDirectory ?? "");
1993
2000
  const { nodeEngineConfig, nodeEnvVars: envVars } = await buildConfiguration(
1994
2001
  // This is the only location in the code base that should access process.env directly
1995
2002
  // eslint-disable-next-line no-restricted-syntax
1996
2003
  process.env, nodeOptions, serverInfo);
1997
- console.info();
2004
+ CLIDisplay.break();
1998
2005
  const startResult = await start(nodeOptions, nodeEngineConfig, envVars);
1999
2006
  if (!Is.empty(startResult)) {
2000
2007
  for (const signal of ["SIGHUP", "SIGINT", "SIGTERM"]) {
@@ -2005,7 +2012,7 @@ async function run(nodeOptions) {
2005
2012
  }
2006
2013
  }
2007
2014
  catch (err) {
2008
- console.error(ErrorHelper.formatErrors(err).join("\n"));
2015
+ CLIDisplay.error(err);
2009
2016
  // eslint-disable-next-line unicorn/no-process-exit
2010
2017
  process.exit(1);
2011
2018
  }
@@ -2022,7 +2029,7 @@ async function buildConfiguration(processEnv, options, serverInfo) {
2022
2029
  let defaultEnvOnly = false;
2023
2030
  if (Is.empty(options?.envFilenames)) {
2024
2031
  const envFile = path.resolve(path.join(options.executionDirectory ?? "", ".env"));
2025
- console.info("Default Environment File:", envFile);
2032
+ CLIDisplay.value("Default Environment File", envFile);
2026
2033
  options ??= {};
2027
2034
  options.envFilenames = [envFile];
2028
2035
  defaultEnvOnly = true;
@@ -2050,18 +2057,18 @@ async function buildConfiguration(processEnv, options, serverInfo) {
2050
2057
  const filePath = envVars[key].slice(6).trim();
2051
2058
  const embeddedFile = path.resolve(path.join(options.executionDirectory ?? "", filePath));
2052
2059
  if (envVars[key].startsWith("@text:")) {
2053
- console.info(`Expanding Environment Variable: ${key} from text file: ${embeddedFile}`);
2060
+ CLIDisplay.value(`Expanding Environment Variable: ${key} from text file`, embeddedFile);
2054
2061
  envVars[key] = await loadTextFile(embeddedFile);
2055
2062
  }
2056
2063
  else if (envVars[key].startsWith("@json:")) {
2057
- console.info(`Expanding Environment Variable: ${key} from JSON file: ${embeddedFile}`);
2064
+ CLIDisplay.value(`Expanding Environment Variable: ${key} from JSON file`, embeddedFile);
2058
2065
  envVars[key] = await loadJsonFile(embeddedFile);
2059
2066
  }
2060
2067
  }
2061
2068
  }
2062
2069
  // Extend the environment variables with any additional custom configuration.
2063
2070
  if (Is.function(options?.extendEnvVars)) {
2064
- console.info("Extending Environment Variables");
2071
+ CLIDisplay.task("Extending Environment Variables");
2065
2072
  await options.extendEnvVars(envVars);
2066
2073
  }
2067
2074
  // Build the engine configuration from the environment variables.
@@ -2070,19 +2077,19 @@ async function buildConfiguration(processEnv, options, serverInfo) {
2070
2077
  // Merge any custom configuration provided in the options.
2071
2078
  if (Is.arrayValue(options?.configFilenames)) {
2072
2079
  for (const configFile of options.configFilenames) {
2073
- console.info("Loading Configuration File:", configFile);
2080
+ CLIDisplay.value("Loading Configuration File", configFile);
2074
2081
  const configFilePath = path.resolve(path.join(options.executionDirectory ?? "", configFile));
2075
2082
  const config = await loadJsonFile(configFilePath);
2076
2083
  Object.assign(engineServerConfig, config);
2077
2084
  }
2078
2085
  }
2079
2086
  if (Is.objectValue(options?.config)) {
2080
- console.info("Merging Custom Configuration");
2087
+ CLIDisplay.task("Merging Custom Configuration");
2081
2088
  Object.assign(engineServerConfig, options.config);
2082
2089
  }
2083
2090
  // Merge any custom configuration provided in the options.
2084
2091
  if (Is.function(options?.extendConfig)) {
2085
- console.info("Extending Configuration");
2092
+ CLIDisplay.task("Extending Configuration");
2086
2093
  await options.extendConfig(envVars, engineServerConfig);
2087
2094
  }
2088
2095
  const nodeEngineConfig = await extensionsConfiguration(envVars, engineServerConfig);
@@ -26,6 +26,7 @@ export declare function extensionsInitialiseEngineServer(envVars: INodeEnvironme
26
26
  /**
27
27
  * Handles the shutdown of the extensions.
28
28
  * @param envVars The environment variables for the node.
29
+ * @param engineCore The engine core instance.
29
30
  * @returns Nothing.
30
31
  */
31
- export declare function shutdownExtensions(envVars: INodeEnvironmentVariables): Promise<void>;
32
+ export declare function shutdownExtensions(envVars: INodeEnvironmentVariables, engineCore: IEngineCore): Promise<void>;
@@ -20,3 +20,8 @@ export type NodeExtensionInitialiseEngineMethod = (engineCore: IEngineCore) => P
20
20
  * @param engineServer The engine server instance.
21
21
  */
22
22
  export type NodeExtensionInitialiseEngineServerMethod = (engineCore: IEngineCore, engineServer: IEngineServer) => Promise<void>;
23
+ /**
24
+ * The type for the shutdown method of an extension module.
25
+ * This is called when the engine is shutting down.
26
+ */
27
+ export type NodeExtensionShutdownMethod = () => Promise<void>;
package/docs/changelog.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # @twin.org/node-core - Changelog
2
2
 
3
+ ## [0.0.2-next.18](https://github.com/twinfoundation/node/compare/node-core-v0.0.2-next.17...node-core-v0.0.2-next.18) (2025-10-03)
4
+
5
+
6
+ ### Features
7
+
8
+ * improve error handling for extensions and logging ([bd84fde](https://github.com/twinfoundation/node/commit/bd84fde67e5a9db6ac66730397992401ca52e88c))
9
+
3
10
  ## [0.0.2-next.17](https://github.com/twinfoundation/node/compare/node-core-v0.0.2-next.16...node-core-v0.0.2-next.17) (2025-10-02)
4
11
 
5
12
 
@@ -1,6 +1,6 @@
1
1
  # Function: shutdownExtensions()
2
2
 
3
- > **shutdownExtensions**(`envVars`): `Promise`\<`void`\>
3
+ > **shutdownExtensions**(`envVars`, `engineCore`): `Promise`\<`void`\>
4
4
 
5
5
  Handles the shutdown of the extensions.
6
6
 
@@ -12,6 +12,12 @@ Handles the shutdown of the extensions.
12
12
 
13
13
  The environment variables for the node.
14
14
 
15
+ ### engineCore
16
+
17
+ `IEngineCore`
18
+
19
+ The engine core instance.
20
+
15
21
  ## Returns
16
22
 
17
23
  `Promise`\<`void`\>
@@ -13,6 +13,7 @@
13
13
  - [NodeExtensionInitialiseMethod](type-aliases/NodeExtensionInitialiseMethod.md)
14
14
  - [NodeExtensionInitialiseEngineMethod](type-aliases/NodeExtensionInitialiseEngineMethod.md)
15
15
  - [NodeExtensionInitialiseEngineServerMethod](type-aliases/NodeExtensionInitialiseEngineServerMethod.md)
16
+ - [NodeExtensionShutdownMethod](type-aliases/NodeExtensionShutdownMethod.md)
16
17
  - [NodeFeatures](type-aliases/NodeFeatures.md)
17
18
 
18
19
  ## Variables
@@ -0,0 +1,10 @@
1
+ # Type Alias: NodeExtensionShutdownMethod()
2
+
3
+ > **NodeExtensionShutdownMethod** = () => `Promise`\<`void`\>
4
+
5
+ The type for the shutdown method of an extension module.
6
+ This is called when the engine is shutting down.
7
+
8
+ ## Returns
9
+
10
+ `Promise`\<`void`\>
package/locales/en.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "error": {
3
3
  "node": {
4
- "storageFileRootNotSet": "{storageFileRoot} is not set, the server will not start without it, please set it in the shell or create a .env file"
4
+ "storageFileRootNotSet": "{storageFileRoot} is not set, the server will not start without it, please set it in the shell or create a .env file",
5
+ "extensionLoadingError": "Failed to load extension \"{extension}\""
5
6
  }
6
7
  },
7
8
  "node": {
@@ -31,6 +32,12 @@
31
32
  "existingUserProfile": "User profile already exists \"{identity}\"",
32
33
  "nodeIdentity": "Node identity \"{identity}\"",
33
34
  "nodeAdminUserEmail": "Node Admin User Email \"{email}\"",
34
- "nodeAdminUserPassword": "Node Admin User Password \"{password}\""
35
+ "nodeAdminUserPassword": "Node Admin User Password \"{password}\"",
36
+ "extensionLoading": "Loading Extension",
37
+ "extensionInitialisingEngine": "Initialising engine for extension \"{extension}\"",
38
+ "extensionInitialisingEngineServer": "Initialising engine server for extension \"{extension}\"",
39
+ "extensionShutdown": "Shutdown extension \"{extension}\"",
40
+ "extendingEngine": "Extending Engine",
41
+ "extendingEngineServer": "Extending Engine Server"
35
42
  }
36
43
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@twin.org/node-core",
3
- "version": "0.0.2-next.17",
3
+ "version": "0.0.2-next.18",
4
4
  "description": "TWIN Node Core for serving APIs using the specified configuration",
5
5
  "repository": {
6
6
  "type": "git",
@@ -15,6 +15,7 @@
15
15
  },
16
16
  "dependencies": {
17
17
  "@twin.org/api-auth-entity-storage-service": "next",
18
+ "@twin.org/cli-core": "next",
18
19
  "@twin.org/core": "next",
19
20
  "@twin.org/crypto": "next",
20
21
  "@twin.org/data-space-connector-models": "next",