diff --git a/doc/api/process.md b/doc/api/process.md index 7a109dd6abfe2d..78bdcd3f0582ae 100644 --- a/doc/api/process.md +++ b/doc/api/process.md @@ -1643,8 +1643,10 @@ Will generate output similar to: ares: '1.10.0-DEV', modules: '43', icu: '55.1', - openssl: '1.0.1k' -} + openssl: '1.0.1k', + unicode: '8.0', + cldr: '29.0', + tz: '2016b' } ``` ## Exit Codes diff --git a/lib/internal/bootstrap_node.js b/lib/internal/bootstrap_node.js index c8adb39e6b7b26..4e1e2aa9018b5a 100644 --- a/lib/internal/bootstrap_node.js +++ b/lib/internal/bootstrap_node.js @@ -26,6 +26,8 @@ // do this good and early, since it handles errors. setupProcessFatal(); + setupProcessICUVersions(); + setupGlobalVariables(); if (!process._noBrowserGlobals) { setupGlobalTimeouts(); @@ -310,6 +312,36 @@ }; } + function setupProcessICUVersions() { + const icu = process.binding('config').hasIntl ? + process.binding('icu') : undefined; + if (!icu) return; // no Intl/ICU: nothing to add here. + // With no argument, getVersion() returns a comma separated list + // of possible types. + const versionTypes = icu.getVersion().split(','); + versionTypes.forEach((name) => { + // Copied from module.js:addBuiltinLibsToObject + Object.defineProperty(process.versions, name, { + configurable: true, + enumerable: true, + get: () => { + // With an argument, getVersion(type) returns + // the actual version string. + const version = icu.getVersion(name); + // Replace the current getter with a new + // property. + delete process.versions[name]; + Object.defineProperty(process.versions, name, { + value: version, + writable: false, + enumerable: true + }); + return version; + } + }); + }); + } + function tryGetCwd(path) { var threw = true; var cwd; diff --git a/lib/util.js b/lib/util.js index 95c69248e07282..37008b2d176062 100644 --- a/lib/util.js +++ b/lib/util.js @@ -1049,3 +1049,7 @@ exports._exceptionWithHostPort = function(err, } return ex; }; + +// process.versions needs a custom function as some values are lazy-evaluated. +process.versions[exports.inspect.custom] = + (depth) => exports.format(JSON.parse(JSON.stringify(process.versions))); diff --git a/src/node.cc b/src/node.cc index d02ecf1a1cdd6c..58c739facfd03d 100644 --- a/src/node.cc +++ b/src/node.cc @@ -3032,9 +3032,7 @@ void SetupProcessObject(Environment* env, FIXED_ONE_BYTE_STRING(env->isolate(), ARES_VERSION_STR)); #if defined(NODE_HAVE_I18N_SUPPORT) && defined(U_ICU_VERSION) - READONLY_PROPERTY(versions, - "icu", - OneByteString(env->isolate(), U_ICU_VERSION)); + // ICU-related versions are now handled on the js side, see bootstrap_node.js if (icu_data_dir != nullptr) { // Did the user attempt (via env var or parameter) to set an ICU path? diff --git a/src/node_i18n.cc b/src/node_i18n.cc index 7ac50423b2db2c..282575b3c3cc5d 100644 --- a/src/node_i18n.cc +++ b/src/node_i18n.cc @@ -31,14 +31,18 @@ #include "util-inl.h" #include "v8.h" +#include #include #include #include #include -#include #include #include #include +#include +#include +#include +#include #ifdef NODE_HAVE_SMALL_ICU /* if this is defined, we have a 'secondary' entry point. @@ -339,6 +343,67 @@ static void ICUErrorName(const FunctionCallbackInfo& args) { v8::NewStringType::kNormal).ToLocalChecked()); } +#define TYPE_ICU "icu" +#define TYPE_UNICODE "unicode" +#define TYPE_CLDR "cldr" +#define TYPE_TZ "tz" + +/** + * This is the workhorse function that deals with the actual version info. + * Get an ICU version. + * @param type the type of version to get. One of VERSION_TYPES + * @param buf optional buffer for result + * @param status ICU error status. If failure, assume result is undefined. + * @return version number, or NULL. May or may not be buf. + */ +static const char* GetVersion(const char* type, + char buf[U_MAX_VERSION_STRING_LENGTH], + UErrorCode* status) { + if (!strcmp(type, TYPE_ICU)) { + return U_ICU_VERSION; + } else if (!strcmp(type, TYPE_UNICODE)) { + return U_UNICODE_VERSION; + } else if (!strcmp(type, TYPE_TZ)) { + return TimeZone::getTZDataVersion(*status); + } else if (!strcmp(type, TYPE_CLDR)) { + UVersionInfo versionArray; + ulocdata_getCLDRVersion(versionArray, status); + if (U_SUCCESS(*status)) { + u_versionToString(versionArray, buf); + return buf; + } + } + // Fall through - unknown type or error case + return nullptr; +} + +static void GetVersion(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + if ( args.Length() == 0 ) { + // With no args - return a comma-separated list of allowed values + args.GetReturnValue().Set( + String::NewFromUtf8(env->isolate(), + TYPE_ICU "," + TYPE_UNICODE "," + TYPE_CLDR "," + TYPE_TZ)); + } else { + CHECK_GE(args.Length(), 1); + CHECK(args[0]->IsString()); + Utf8Value val(env->isolate(), args[0]); + UErrorCode status = U_ZERO_ERROR; + char buf[U_MAX_VERSION_STRING_LENGTH] = ""; // Possible output buffer. + const char* versionString = GetVersion(*val, buf, &status); + + if (U_SUCCESS(status) && versionString) { + // Success. + args.GetReturnValue().Set( + String::NewFromUtf8(env->isolate(), + versionString)); + } + } +} + bool InitializeICUDirectory(const char* icu_data_path) { if (icu_data_path != nullptr) { flag_icu_data_dir = true; @@ -558,6 +623,7 @@ void Init(Local target, env->SetMethod(target, "toUnicode", ToUnicode); env->SetMethod(target, "toASCII", ToASCII); env->SetMethod(target, "getStringWidth", GetStringWidth); + env->SetMethod(target, "getVersion", GetVersion); // One-shot converters env->SetMethod(target, "icuErrName", ICUErrorName); diff --git a/test/parallel/test-process-versions.js b/test/parallel/test-process-versions.js index 0b86cfd7343df1..181e3ecef2bdb0 100644 --- a/test/parallel/test-process-versions.js +++ b/test/parallel/test-process-versions.js @@ -11,6 +11,9 @@ if (common.hasCrypto) { if (common.hasIntl) { expected_keys.push('icu'); + expected_keys.push('cldr'); + expected_keys.push('tz'); + expected_keys.push('unicode'); } expected_keys.sort();