@salesforce/core 3.30.14 → 3.31.7

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.
Files changed (153) hide show
  1. package/LICENSE.txt +11 -11
  2. package/README.md +222 -227
  3. package/lib/config/aliasesConfig.d.ts +12 -12
  4. package/lib/config/aliasesConfig.js +27 -27
  5. package/lib/config/authInfoConfig.d.ts +19 -19
  6. package/lib/config/authInfoConfig.js +34 -34
  7. package/lib/config/config.d.ts +311 -311
  8. package/lib/config/config.js +574 -574
  9. package/lib/config/configAggregator.d.ts +232 -232
  10. package/lib/config/configAggregator.js +379 -379
  11. package/lib/config/configFile.d.ts +199 -199
  12. package/lib/config/configFile.js +340 -340
  13. package/lib/config/configGroup.d.ts +141 -141
  14. package/lib/config/configGroup.js +224 -224
  15. package/lib/config/configStore.d.ts +241 -241
  16. package/lib/config/configStore.js +352 -352
  17. package/lib/config/envVars.d.ts +101 -101
  18. package/lib/config/envVars.js +456 -456
  19. package/lib/config/orgUsersConfig.d.ts +31 -31
  20. package/lib/config/orgUsersConfig.js +41 -41
  21. package/lib/config/sandboxOrgConfig.d.ts +37 -37
  22. package/lib/config/sandboxOrgConfig.js +50 -50
  23. package/lib/config/sandboxProcessCache.d.ts +16 -16
  24. package/lib/config/sandboxProcessCache.js +37 -37
  25. package/lib/config/tokensConfig.d.ts +10 -10
  26. package/lib/config/tokensConfig.js +28 -28
  27. package/lib/config/ttlConfig.d.ts +34 -34
  28. package/lib/config/ttlConfig.js +54 -54
  29. package/lib/crypto/crypto.d.ts +54 -54
  30. package/lib/crypto/crypto.js +220 -220
  31. package/lib/crypto/keyChain.d.ts +8 -8
  32. package/lib/crypto/keyChain.js +61 -61
  33. package/lib/crypto/keyChainImpl.d.ts +116 -116
  34. package/lib/crypto/keyChainImpl.js +486 -486
  35. package/lib/crypto/secureBuffer.d.ts +46 -46
  36. package/lib/crypto/secureBuffer.js +82 -82
  37. package/lib/deviceOauthService.d.ts +71 -71
  38. package/lib/deviceOauthService.js +191 -191
  39. package/lib/exported.d.ts +38 -38
  40. package/lib/exported.js +118 -118
  41. package/lib/global.d.ts +70 -70
  42. package/lib/global.js +109 -109
  43. package/lib/lifecycleEvents.d.ts +93 -93
  44. package/lib/lifecycleEvents.js +188 -188
  45. package/lib/logger.d.ts +381 -381
  46. package/lib/logger.js +734 -734
  47. package/lib/messages.d.ts +291 -291
  48. package/lib/messages.js +543 -543
  49. package/lib/org/authInfo.d.ts +344 -344
  50. package/lib/org/authInfo.js +892 -892
  51. package/lib/org/authRemover.d.ts +88 -88
  52. package/lib/org/authRemover.js +182 -182
  53. package/lib/org/connection.d.ts +197 -197
  54. package/lib/org/connection.js +395 -395
  55. package/lib/org/index.d.ts +6 -6
  56. package/lib/org/index.js +28 -28
  57. package/lib/org/org.d.ts +558 -558
  58. package/lib/org/org.js +1267 -1267
  59. package/lib/org/orgConfigProperties.d.ts +69 -69
  60. package/lib/org/orgConfigProperties.js +136 -136
  61. package/lib/org/permissionSetAssignment.d.ts +35 -35
  62. package/lib/org/permissionSetAssignment.js +125 -125
  63. package/lib/org/scratchOrgCache.d.ts +20 -20
  64. package/lib/org/scratchOrgCache.js +32 -32
  65. package/lib/org/scratchOrgCreate.d.ts +54 -54
  66. package/lib/org/scratchOrgCreate.js +216 -216
  67. package/lib/org/scratchOrgErrorCodes.d.ts +10 -10
  68. package/lib/org/scratchOrgErrorCodes.js +88 -88
  69. package/lib/org/scratchOrgFeatureDeprecation.d.ts +26 -26
  70. package/lib/org/scratchOrgFeatureDeprecation.js +109 -109
  71. package/lib/org/scratchOrgInfoApi.d.ts +68 -68
  72. package/lib/org/scratchOrgInfoApi.js +413 -413
  73. package/lib/org/scratchOrgInfoGenerator.d.ts +64 -64
  74. package/lib/org/scratchOrgInfoGenerator.js +241 -241
  75. package/lib/org/scratchOrgLifecycleEvents.d.ts +10 -10
  76. package/lib/org/scratchOrgLifecycleEvents.js +40 -40
  77. package/lib/org/scratchOrgSettingsGenerator.d.ts +78 -78
  78. package/lib/org/scratchOrgSettingsGenerator.js +276 -276
  79. package/lib/org/scratchOrgTypes.d.ts +43 -43
  80. package/lib/org/scratchOrgTypes.js +8 -8
  81. package/lib/org/user.d.ts +187 -187
  82. package/lib/org/user.js +448 -448
  83. package/lib/schema/printer.d.ts +79 -79
  84. package/lib/schema/printer.js +260 -260
  85. package/lib/schema/validator.d.ts +70 -70
  86. package/lib/schema/validator.js +169 -169
  87. package/lib/sfError.d.ts +73 -73
  88. package/lib/sfError.js +136 -136
  89. package/lib/sfProject.d.ts +357 -357
  90. package/lib/sfProject.js +671 -671
  91. package/lib/stateAggregator/accessors/aliasAccessor.d.ts +98 -98
  92. package/lib/stateAggregator/accessors/aliasAccessor.js +145 -145
  93. package/lib/stateAggregator/accessors/orgAccessor.d.ts +101 -101
  94. package/lib/stateAggregator/accessors/orgAccessor.js +240 -240
  95. package/lib/stateAggregator/accessors/sandboxAccessor.d.ts +8 -8
  96. package/lib/stateAggregator/accessors/sandboxAccessor.js +27 -27
  97. package/lib/stateAggregator/accessors/tokenAccessor.d.ts +63 -63
  98. package/lib/stateAggregator/accessors/tokenAccessor.js +79 -79
  99. package/lib/stateAggregator/index.d.ts +4 -4
  100. package/lib/stateAggregator/index.js +26 -26
  101. package/lib/stateAggregator/stateAggregator.d.ts +25 -25
  102. package/lib/stateAggregator/stateAggregator.js +45 -45
  103. package/lib/status/myDomainResolver.d.ts +66 -66
  104. package/lib/status/myDomainResolver.js +124 -124
  105. package/lib/status/pollingClient.d.ts +85 -85
  106. package/lib/status/pollingClient.js +115 -115
  107. package/lib/status/streamingClient.d.ts +244 -244
  108. package/lib/status/streamingClient.js +436 -436
  109. package/lib/status/types.d.ts +89 -89
  110. package/lib/status/types.js +17 -17
  111. package/lib/testSetup.d.ts +553 -530
  112. package/lib/testSetup.js +871 -727
  113. package/lib/util/cache.d.ts +11 -11
  114. package/lib/util/cache.js +69 -69
  115. package/lib/util/checkLightningDomain.d.ts +1 -1
  116. package/lib/util/checkLightningDomain.js +28 -28
  117. package/lib/util/directoryWriter.d.ts +12 -12
  118. package/lib/util/directoryWriter.js +53 -53
  119. package/lib/util/getJwtAudienceUrl.d.ts +4 -4
  120. package/lib/util/getJwtAudienceUrl.js +18 -18
  121. package/lib/util/internal.d.ts +58 -58
  122. package/lib/util/internal.js +118 -118
  123. package/lib/util/jsonXmlTools.d.ts +14 -14
  124. package/lib/util/jsonXmlTools.js +38 -38
  125. package/lib/util/mapKeys.d.ts +14 -14
  126. package/lib/util/mapKeys.js +51 -51
  127. package/lib/util/sfdc.d.ts +52 -52
  128. package/lib/util/sfdc.js +85 -85
  129. package/lib/util/sfdcUrl.d.ts +72 -72
  130. package/lib/util/sfdcUrl.js +215 -215
  131. package/lib/util/structuredWriter.d.ts +9 -9
  132. package/lib/util/structuredWriter.js +2 -2
  133. package/lib/util/zipWriter.d.ts +16 -16
  134. package/lib/util/zipWriter.js +67 -67
  135. package/lib/webOAuthServer.d.ts +156 -156
  136. package/lib/webOAuthServer.js +388 -388
  137. package/messages/auth.md +37 -37
  138. package/messages/config.md +156 -156
  139. package/messages/connection.md +30 -30
  140. package/messages/core.json +20 -20
  141. package/messages/core.md +67 -67
  142. package/messages/encryption.md +85 -85
  143. package/messages/envVars.md +303 -303
  144. package/messages/org.md +63 -63
  145. package/messages/permissionSetAssignment.md +31 -31
  146. package/messages/scratchOrgCreate.md +23 -23
  147. package/messages/scratchOrgErrorCodes.md +115 -115
  148. package/messages/scratchOrgFeatureDeprecation.md +11 -11
  149. package/messages/scratchOrgInfoApi.md +15 -15
  150. package/messages/scratchOrgInfoGenerator.md +23 -23
  151. package/messages/streaming.md +23 -23
  152. package/messages/user.md +35 -35
  153. package/package.json +97 -97
package/lib/messages.js CHANGED
@@ -1,544 +1,544 @@
1
- "use strict";
2
- /*
3
- * Copyright (c) 2020, salesforce.com, inc.
4
- * All rights reserved.
5
- * Licensed under the BSD 3-Clause license.
6
- * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
7
- */
8
- Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.Messages = void 0;
10
- const fs = require("fs");
11
- const os = require("os");
12
- const path = require("path");
13
- const util = require("util");
14
- const ts_types_1 = require("@salesforce/ts-types");
15
- const kit_1 = require("@salesforce/kit");
16
- const sfError_1 = require("./sfError");
17
- class Key {
18
- constructor(packageName, bundleName) {
19
- this.packageName = packageName;
20
- this.bundleName = bundleName;
21
- }
22
- toString() {
23
- return `${this.packageName}:${this.bundleName}`;
24
- }
25
- }
26
- const REGEXP_NO_CONTENT = /^\s*$/g;
27
- const REGEXP_NO_CONTENT_SECTION = /^#\s*/gm;
28
- const REGEXP_MD_IS_LIST_ROW = /^[*-]\s+|^ {2}/;
29
- const REGEXP_MD_LIST_ITEM = /^[*-]\s+/gm;
30
- const markdownLoader = (filePath, fileContents) => {
31
- const map = new Map();
32
- const hasContent = (lineItem) => !REGEXP_NO_CONTENT.exec(lineItem);
33
- // Filter out sections that don't have content
34
- const sections = fileContents.split(REGEXP_NO_CONTENT_SECTION).filter(hasContent);
35
- for (const section of sections) {
36
- const lines = section.split('\n');
37
- const firstLine = lines.shift();
38
- const rest = lines.join('\n').trim();
39
- if (firstLine && rest.length > 0) {
40
- const key = firstLine.trim();
41
- const nonEmptyLines = lines.filter((line) => !!line.trim());
42
- // If every entry in the value is a list item, then treat this as a list. Indented lines are part of the list.
43
- if (nonEmptyLines.every((line) => REGEXP_MD_IS_LIST_ROW.exec(line))) {
44
- const listItems = rest.split(REGEXP_MD_LIST_ITEM).filter(hasContent);
45
- const values = listItems.map((item) => item
46
- .split('\n')
47
- // new lines are ignored in markdown lists
48
- .filter((line) => !!line.trim())
49
- // trim off the indentation
50
- .map((line) => line.trim())
51
- // put it back together
52
- .join('\n'));
53
- map.set(key, values);
54
- }
55
- else {
56
- map.set(key, rest);
57
- }
58
- }
59
- else {
60
- // use error instead of SfError because messages.js should have no internal dependencies.
61
- throw new Error(`Invalid markdown message file: ${filePath}\nThe line "# <key>" must be immediately followed by the message on a new line.`);
62
- }
63
- }
64
- return map;
65
- };
66
- const jsAndJsonLoader = (filePath, fileContents) => {
67
- let json;
68
- try {
69
- json = JSON.parse(fileContents);
70
- if (!(0, ts_types_1.isObject)(json)) {
71
- // Bubble up
72
- throw new Error(`Unexpected token. Found returned content type '${typeof json}'.`);
73
- }
74
- }
75
- catch (err) {
76
- // Provide a nicer error message for a common JSON parse error; Unexpected token
77
- const error = err;
78
- if (error.message.startsWith('Unexpected token')) {
79
- const parseError = new Error(`Invalid JSON content in message file: ${filePath}\n${error.message}`);
80
- parseError.name = error.name;
81
- throw parseError;
82
- }
83
- throw err;
84
- }
85
- return new Map(Object.entries(json));
86
- };
87
- /**
88
- * The core message framework manages messages and allows them to be accessible by
89
- * all plugins and consumers of sfdx-core. It is set up to handle localization down
90
- * the road at no additional effort to the consumer. Messages can be used for
91
- * anything from user output (like the console), to error messages, to returned
92
- * data from a method.
93
- *
94
- * Messages are loaded from loader functions. The loader functions will only run
95
- * when a message is required. This prevents all messages from being loaded into memory at
96
- * application startup. The functions can load from memory, a file, or a server.
97
- *
98
- * In the beginning of your app or file, add the loader functions to be used later. If using
99
- * json or js files in a root messages directory (`<moduleRoot>/messages`), load the entire directory
100
- * automatically with {@link Messages.importMessagesDirectory}. Message files must be the following formates.
101
- *
102
- * A `.json` file:
103
- * ```json
104
- * {
105
- * "msgKey": "A message displayed in the user",
106
- * "msgGroup": {
107
- * "anotherMsgKey": "Another message displayed to the user"
108
- * },
109
- * "listOfMessage": ["message1", "message2"]
110
- * }
111
- * ```
112
- *
113
- * A `.js` file:
114
- * ```javascript
115
- * module.exports = {
116
- * msgKey: 'A message displayed in the user',
117
- * msgGroup: {
118
- * anotherMsgKey: 'Another message displayed to the user'
119
- * },
120
- * listOfMessage: ['message1', 'message2']
121
- * }
122
- * ```
123
- *
124
- * A `.md` file:
125
- * ```markdown
126
- * # msgKey
127
- * A message displayed in the user
128
- *
129
- * # msgGroup.anotherMsgKey
130
- * Another message displayed to the user
131
- *
132
- * # listOfMessage
133
- * - message1
134
- * - message2
135
- * ```
136
- *
137
- * The values support [util.format](https://nodejs.org/api/util.html#util_util_format_format_args) style strings
138
- * that apply the tokens passed into {@link Message.getMessage}
139
- *
140
- * **Note:** When running unit tests individually, you may see errors that the messages aren't found.
141
- * This is because `index.js` isn't loaded when tests run like they are when the package is required.
142
- * To allow tests to run, import the message directory in each test (it will only
143
- * do it once) or load the message file the test depends on individually.
144
- *
145
- * ```typescript
146
- * // Create loader functions for all files in the messages directory
147
- * Messages.importMessagesDirectory(__dirname);
148
- *
149
- * // Now you can use the messages from anywhere in your code or file.
150
- * // If using importMessageDirectory, the bundle name is the file name.
151
- * const messages: Messages = Messages.load(packageName, bundleName);
152
- *
153
- * // Messages now contains all the message in the bundleName file.
154
- * messages.getMessage('authInfoCreationError');
155
- * ```
156
- */
157
- class Messages {
158
- /**
159
- * Create a new messages bundle.
160
- *
161
- * **Note:** Use {Messages.load} unless you are writing your own loader function.
162
- *
163
- * @param bundleName The bundle name.
164
- * @param locale The locale.
165
- * @param messages The messages. Can not be modified once created.
166
- */
167
- constructor(bundleName, locale, messages) {
168
- this.messages = messages;
169
- this.bundleName = bundleName;
170
- this.locale = locale;
171
- }
172
- /**
173
- * Get the locale. This will always return 'en_US' but will return the
174
- * machine's locale in the future.
175
- */
176
- static getLocale() {
177
- return 'en_US';
178
- }
179
- /**
180
- * Set a custom loader function for a package and bundle that will be called on {@link Messages.load}.
181
- *
182
- * @param packageName The npm package name.
183
- * @param bundle The name of the bundle.
184
- * @param loader The loader function.
185
- */
186
- static setLoaderFunction(packageName, bundle, loader) {
187
- this.loaders.set(new Key(packageName, bundle).toString(), loader);
188
- }
189
- /**
190
- * Generate a file loading function. Use {@link Messages.importMessageFile} unless
191
- * overriding the bundleName is required, then manually pass the loader
192
- * function to {@link Messages.setLoaderFunction}.
193
- *
194
- * @param bundleName The name of the bundle.
195
- * @param filePath The messages file path.
196
- */
197
- static generateFileLoaderFunction(bundleName, filePath) {
198
- const ext = path.extname(filePath);
199
- if (!['.json', '.js', '.md'].includes(ext)) {
200
- throw new Error(`Only json, js and md message files are allowed, not ${ext}: ${filePath}`);
201
- }
202
- return (locale) => {
203
- let fileContents;
204
- let parser;
205
- if (ext === '.md') {
206
- fileContents = fs.readFileSync(filePath, 'utf-8');
207
- parser = markdownLoader;
208
- }
209
- else {
210
- // Anything can be returned by a js file, so stringify the results to ensure valid json is returned.
211
- fileContents = JSON.stringify(Messages.readFile(filePath));
212
- // If the file is empty, JSON.stringify will turn it into "" which will validate on parse.
213
- if (fileContents === 'null' || fileContents === '""')
214
- fileContents = '';
215
- parser = jsAndJsonLoader;
216
- }
217
- if (!fileContents || fileContents.trim().length === 0) {
218
- // messages.js should have no internal dependencies.
219
- const error = new Error(`Invalid message file: ${filePath}. No content.`);
220
- error.name = 'SfError';
221
- throw error;
222
- }
223
- const map = parser(filePath, fileContents);
224
- return new Messages(bundleName, locale, map);
225
- };
226
- }
227
- /**
228
- * Add a single message file to the list of loading functions using the file name as the bundle name.
229
- * The loader will only be added if the bundle name is not already taken.
230
- *
231
- * @param packageName The npm package name.
232
- * @param filePath The path of the file.
233
- */
234
- static importMessageFile(packageName, filePath) {
235
- const bundleName = path.basename(filePath, path.extname(filePath));
236
- if (!Messages.isCached(packageName, bundleName)) {
237
- this.setLoaderFunction(packageName, bundleName, Messages.generateFileLoaderFunction(bundleName, filePath));
238
- }
239
- }
240
- /**
241
- * Import all json and js files in a messages directory. Use the file name as the bundle key when
242
- * {@link Messages.load} is called. By default, we're assuming the moduleDirectoryPart is a
243
- * typescript project and will truncate to root path (where the package.json file is). If your messages
244
- * directory is in another spot or you are not using typescript, pass in false for truncateToProjectPath.
245
- *
246
- * ```
247
- * // e.g. If your message directory is in the project root, you would do:
248
- * Messages.importMessagesDirectory(__dirname);
249
- * ```
250
- *
251
- * @param moduleDirectoryPath The path to load the messages folder.
252
- * @param truncateToProjectPath Will look for the messages directory in the project root (where the package.json file is located).
253
- * i.e., the module is typescript and the messages folder is in the top level of the module directory.
254
- * @param packageName The npm package name. Figured out from the root directory's package.json.
255
- */
256
- static importMessagesDirectory(moduleDirectoryPath, truncateToProjectPath = true, packageName) {
257
- let moduleMessagesDirPath = moduleDirectoryPath;
258
- let projectRoot = moduleDirectoryPath;
259
- if (!path.isAbsolute(moduleDirectoryPath)) {
260
- throw new Error('Invalid module path. Relative URLs are not allowed.');
261
- }
262
- while (projectRoot.length >= 0) {
263
- try {
264
- fs.statSync(path.join(projectRoot, 'package.json'));
265
- break;
266
- }
267
- catch (err) {
268
- if (err.code !== 'ENOENT')
269
- throw err;
270
- projectRoot = projectRoot.substring(0, projectRoot.lastIndexOf(path.sep));
271
- }
272
- }
273
- if (truncateToProjectPath) {
274
- moduleMessagesDirPath = projectRoot;
275
- }
276
- if (!packageName) {
277
- const errMessage = `Invalid or missing package.json file at '${moduleMessagesDirPath}'. If not using a package.json, pass in a packageName.`;
278
- try {
279
- packageName = (0, ts_types_1.asString)((0, ts_types_1.ensureJsonMap)(Messages.readFile(path.join(moduleMessagesDirPath, 'package.json'))).name);
280
- if (!packageName) {
281
- throw new kit_1.NamedError('MissingPackageName', errMessage);
282
- }
283
- }
284
- catch (err) {
285
- throw new kit_1.NamedError('MissingPackageName', errMessage, err);
286
- }
287
- }
288
- moduleMessagesDirPath += `${path.sep}messages`;
289
- for (const file of fs.readdirSync(moduleMessagesDirPath)) {
290
- const filePath = path.join(moduleMessagesDirPath, file);
291
- const stat = fs.statSync(filePath);
292
- if (stat) {
293
- if (stat.isDirectory()) {
294
- // When we support other locales, load them from /messages/<local>/<bundleName>.json
295
- // Change generateFileLoaderFunction to handle loading locales.
296
- }
297
- else if (stat.isFile()) {
298
- this.importMessageFile(packageName, filePath);
299
- }
300
- }
301
- }
302
- }
303
- /**
304
- * Load messages for a given package and bundle. If the bundle is not already cached, use the loader function
305
- * created from {@link Messages.setLoaderFunction} or {@link Messages.importMessagesDirectory}.
306
- *
307
- * **NOTE: Use {@link Messages.load} instead for safe message validation and usage.**
308
- *
309
- * ```typescript
310
- * Messages.importMessagesDirectory(__dirname);
311
- * const messages = Messages.load('packageName', 'bundleName');
312
- * ```
313
- *
314
- * @param packageName The name of the npm package.
315
- * @param bundleName The name of the bundle to load.
316
- */
317
- static loadMessages(packageName, bundleName) {
318
- const key = new Key(packageName, bundleName);
319
- let messages;
320
- if (this.isCached(packageName, bundleName)) {
321
- messages = this.bundles.get(key.toString());
322
- }
323
- else if (this.loaders.has(key.toString())) {
324
- const loader = this.loaders.get(key.toString());
325
- if (loader) {
326
- messages = loader(Messages.getLocale());
327
- this.bundles.set(key.toString(), messages);
328
- messages = this.bundles.get(key.toString());
329
- }
330
- }
331
- if (messages) {
332
- return messages;
333
- }
334
- // Don't use messages inside messages
335
- throw new kit_1.NamedError('MissingBundleError', `Missing bundle ${key.toString()} for locale ${Messages.getLocale()}.`);
336
- }
337
- /**
338
- * Load messages for a given package and bundle. If the bundle is not already cached, use the loader function
339
- * created from {@link Messages.setLoaderFunction} or {@link Messages.importMessagesDirectory}.
340
- *
341
- * The message keys that will be used must be passed in for validation. This prevents runtime errors if messages are used but not defined.
342
- *
343
- * **NOTE: This should be defined at the top of the file so validation is done at load time rather than runtime.**
344
- *
345
- * ```typescript
346
- * Messages.importMessagesDirectory(__dirname);
347
- * const messages = Messages.load('packageName', 'bundleName', [
348
- * 'messageKey1',
349
- * 'messageKey2',
350
- * ]);
351
- * ```
352
- *
353
- * @param packageName The name of the npm package.
354
- * @param bundleName The name of the bundle to load.
355
- * @param keys The message keys that will be used.
356
- */
357
- static load(packageName, bundleName, keys) {
358
- const key = new Key(packageName, bundleName);
359
- let messages;
360
- if (this.isCached(packageName, bundleName)) {
361
- messages = this.bundles.get(key.toString());
362
- }
363
- else if (this.loaders.has(key.toString())) {
364
- const loader = this.loaders.get(key.toString());
365
- if (loader) {
366
- messages = loader(Messages.getLocale());
367
- this.bundles.set(key.toString(), messages);
368
- messages = this.bundles.get(key.toString());
369
- }
370
- }
371
- if (messages) {
372
- // Type guard on key length, but do a runtime check.
373
- if (!keys || keys.length === 0) {
374
- throw new kit_1.NamedError('MissingKeysError', 'Can not load messages without providing the message keys that will be used.');
375
- }
376
- // Get all messages to validate they are actually present
377
- for (const messageKey of keys) {
378
- messages.getMessage(messageKey);
379
- }
380
- return messages;
381
- }
382
- // Don't use messages inside messages
383
- throw new kit_1.NamedError('MissingBundleError', `Missing bundle ${key.toString()} for locale ${Messages.getLocale()}.`);
384
- }
385
- /**
386
- * Check if a bundle already been loaded.
387
- *
388
- * @param packageName The npm package name.
389
- * @param bundleName The bundle name.
390
- */
391
- static isCached(packageName, bundleName) {
392
- return this.bundles.has(new Key(packageName, bundleName).toString());
393
- }
394
- /**
395
- * Get a message using a message key and use the tokens as values for tokenization.
396
- *
397
- * If the key happens to be an array of messages, it will combine with OS.EOL.
398
- *
399
- * @param key The key of the message.
400
- * @param tokens The values to substitute in the message.
401
- *
402
- * **See** https://nodejs.org/api/util.html#util_util_format_format_args
403
- */
404
- getMessage(key, tokens = []) {
405
- return this.getMessageWithMap(key, tokens, this.messages).join(os.EOL);
406
- }
407
- /**
408
- * Get messages using a message key and use the tokens as values for tokenization.
409
- *
410
- * This will return all messages if the key is an array in the messages file.
411
- *
412
- * ```json
413
- * {
414
- * "myKey": [ "message1", "message2" ]
415
- * }
416
- * ```
417
- *
418
- * ```markdown
419
- * # myKey
420
- * * message1
421
- * * message2
422
- * ```
423
- *
424
- * @param key The key of the messages.
425
- * @param tokens The values to substitute in the message.
426
- *
427
- * **See** https://nodejs.org/api/util.html#util_util_format_format_args
428
- */
429
- getMessages(key, tokens = []) {
430
- return this.getMessageWithMap(key, tokens, this.messages);
431
- }
432
- /**
433
- * Convenience method to create errors using message labels.
434
- *
435
- * `error.name` will be the upper-cased key, remove prefixed `error.` and will always end in Error.
436
- * `error.actions` will be loaded using `${key}.actions` if available.
437
- *
438
- * @param key The key of the error message.
439
- * @param tokens The error message tokens.
440
- * @param actionTokens The action messages tokens.
441
- * @param exitCodeOrCause The exit code which will be used by SfdxCommand or the underlying error that caused this error to be raised.
442
- * @param cause The underlying error that caused this error to be raised.
443
- */
444
- createError(key, tokens = [], actionTokens = [], exitCodeOrCause, cause) {
445
- const structuredMessage = this.formatMessageContents('error', key, tokens, actionTokens);
446
- return new sfError_1.SfError(structuredMessage.message, structuredMessage.name, structuredMessage.actions, exitCodeOrCause, cause);
447
- }
448
- /**
449
- * Convenience method to create warning using message labels.
450
- *
451
- * `warning.name` will be the upper-cased key, remove prefixed `warning.` and will always end in Warning.
452
- * `warning.actions` will be loaded using `${key}.actions` if available.
453
- *
454
- * @param key The key of the warning message.
455
- * @param tokens The warning message tokens.
456
- * @param actionTokens The action messages tokens.
457
- */
458
- createWarning(key, tokens = [], actionTokens = []) {
459
- return this.formatMessageContents('warning', key, tokens, actionTokens);
460
- }
461
- /**
462
- * Convenience method to create info using message labels.
463
- *
464
- * `info.name` will be the upper-cased key, remove prefixed `info.` and will always end in Info.
465
- * `info.actions` will be loaded using `${key}.actions` if available.
466
- *
467
- * @param key The key of the warning message.
468
- * @param tokens The warning message tokens.
469
- * @param actionTokens The action messages tokens.
470
- */
471
- createInfo(key, tokens = [], actionTokens = []) {
472
- return this.formatMessageContents('info', key, tokens, actionTokens);
473
- }
474
- /**
475
- * Formats message contents given a message type, key, tokens and actions tokens
476
- *
477
- * `<type>.name` will be the upper-cased key, remove prefixed `<type>.` and will always end in 'Error | Warning | Info.
478
- * `<type>.actions` will be loaded using `${key}.actions` if available.
479
- *
480
- * @param type The type of the message set must 'error' | 'warning' | 'info'.
481
- * @param key The key of the warning message.
482
- * @param tokens The warning message tokens.
483
- * @param actionTokens The action messages tokens.
484
- */
485
- formatMessageContents(type, key, tokens = [], actionTokens = []) {
486
- const label = (0, kit_1.upperFirst)(type);
487
- const labelRegExp = new RegExp(`${label}$`);
488
- const searchValue = type === 'error' ? /^error.*\./ : new RegExp(`^${type}.`);
489
- // Convert key to name:
490
- // 'myMessage' -> `MyMessageWarning`
491
- // 'myMessageError' -> `MyMessageError`
492
- // 'warning.myMessage' -> `MyMessageWarning`
493
- const name = `${(0, kit_1.upperFirst)(key.replace(searchValue, ''))}${labelRegExp.exec(key) ? '' : label}`;
494
- const message = this.getMessage(key, tokens);
495
- let actions;
496
- try {
497
- actions = this.getMessageWithMap(`${key}.actions`, actionTokens, this.messages);
498
- }
499
- catch (e) {
500
- /* just ignore if actions aren't found */
501
- }
502
- return { message, name, actions };
503
- }
504
- getMessageWithMap(key, tokens = [], map) {
505
- // Allow nested keys for better grouping
506
- const group = RegExp(/([a-zA-Z0-9_-]+)\.(.*)/).exec(key);
507
- if (group) {
508
- const parentKey = group[1];
509
- const childKey = group[2];
510
- const childObject = map.get(parentKey);
511
- if (childObject && (0, ts_types_1.isJsonMap)(childObject)) {
512
- const childMap = new Map(Object.entries(childObject));
513
- return this.getMessageWithMap(childKey, tokens, childMap);
514
- }
515
- }
516
- if (!map.has(key)) {
517
- // Don't use messages inside messages
518
- throw new kit_1.NamedError('MissingMessageError', `Missing message ${this.bundleName}:${key} for locale ${Messages.getLocale()}.`);
519
- }
520
- const msg = map.get(key);
521
- const messages = ((0, ts_types_1.isArray)(msg) ? msg : [msg]);
522
- return messages.map((message) => {
523
- (0, ts_types_1.ensureString)(message);
524
- return util.format(message, ...tokens);
525
- });
526
- }
527
- }
528
- exports.Messages = Messages;
529
- // It would be AWESOME to use Map<Key, Message> but js does an object instance comparison and doesn't let you
530
- // override valueOf or equals for the === operator, which map uses. So, Use Map<String, Message>
531
- // A map of loading functions to dynamically load messages when they need to be used
532
- Messages.loaders = new Map();
533
- // A map cache of messages bundles that have already been loaded
534
- Messages.bundles = new Map();
535
- /**
536
- * Internal readFile. Exposed for unit testing. Do not use util/fs.readFile as messages.js
537
- * should have no internal dependencies.
538
- *
539
- * @param filePath read file target.
540
- * @ignore
541
- */
542
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
543
- Messages.readFile = (filePath) => require(filePath);
1
+ "use strict";
2
+ /*
3
+ * Copyright (c) 2020, salesforce.com, inc.
4
+ * All rights reserved.
5
+ * Licensed under the BSD 3-Clause license.
6
+ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.Messages = void 0;
10
+ const fs = require("fs");
11
+ const os = require("os");
12
+ const path = require("path");
13
+ const util = require("util");
14
+ const ts_types_1 = require("@salesforce/ts-types");
15
+ const kit_1 = require("@salesforce/kit");
16
+ const sfError_1 = require("./sfError");
17
+ class Key {
18
+ constructor(packageName, bundleName) {
19
+ this.packageName = packageName;
20
+ this.bundleName = bundleName;
21
+ }
22
+ toString() {
23
+ return `${this.packageName}:${this.bundleName}`;
24
+ }
25
+ }
26
+ const REGEXP_NO_CONTENT = /^\s*$/g;
27
+ const REGEXP_NO_CONTENT_SECTION = /^#\s*/gm;
28
+ const REGEXP_MD_IS_LIST_ROW = /^[*-]\s+|^ {2}/;
29
+ const REGEXP_MD_LIST_ITEM = /^[*-]\s+/gm;
30
+ const markdownLoader = (filePath, fileContents) => {
31
+ const map = new Map();
32
+ const hasContent = (lineItem) => !REGEXP_NO_CONTENT.exec(lineItem);
33
+ // Filter out sections that don't have content
34
+ const sections = fileContents.split(REGEXP_NO_CONTENT_SECTION).filter(hasContent);
35
+ for (const section of sections) {
36
+ const lines = section.split('\n');
37
+ const firstLine = lines.shift();
38
+ const rest = lines.join('\n').trim();
39
+ if (firstLine && rest.length > 0) {
40
+ const key = firstLine.trim();
41
+ const nonEmptyLines = lines.filter((line) => !!line.trim());
42
+ // If every entry in the value is a list item, then treat this as a list. Indented lines are part of the list.
43
+ if (nonEmptyLines.every((line) => REGEXP_MD_IS_LIST_ROW.exec(line))) {
44
+ const listItems = rest.split(REGEXP_MD_LIST_ITEM).filter(hasContent);
45
+ const values = listItems.map((item) => item
46
+ .split('\n')
47
+ // new lines are ignored in markdown lists
48
+ .filter((line) => !!line.trim())
49
+ // trim off the indentation
50
+ .map((line) => line.trim())
51
+ // put it back together
52
+ .join('\n'));
53
+ map.set(key, values);
54
+ }
55
+ else {
56
+ map.set(key, rest);
57
+ }
58
+ }
59
+ else {
60
+ // use error instead of SfError because messages.js should have no internal dependencies.
61
+ throw new Error(`Invalid markdown message file: ${filePath}\nThe line "# <key>" must be immediately followed by the message on a new line.`);
62
+ }
63
+ }
64
+ return map;
65
+ };
66
+ const jsAndJsonLoader = (filePath, fileContents) => {
67
+ let json;
68
+ try {
69
+ json = JSON.parse(fileContents);
70
+ if (!(0, ts_types_1.isObject)(json)) {
71
+ // Bubble up
72
+ throw new Error(`Unexpected token. Found returned content type '${typeof json}'.`);
73
+ }
74
+ }
75
+ catch (err) {
76
+ // Provide a nicer error message for a common JSON parse error; Unexpected token
77
+ const error = err;
78
+ if (error.message.startsWith('Unexpected token')) {
79
+ const parseError = new Error(`Invalid JSON content in message file: ${filePath}\n${error.message}`);
80
+ parseError.name = error.name;
81
+ throw parseError;
82
+ }
83
+ throw err;
84
+ }
85
+ return new Map(Object.entries(json));
86
+ };
87
+ /**
88
+ * The core message framework manages messages and allows them to be accessible by
89
+ * all plugins and consumers of sfdx-core. It is set up to handle localization down
90
+ * the road at no additional effort to the consumer. Messages can be used for
91
+ * anything from user output (like the console), to error messages, to returned
92
+ * data from a method.
93
+ *
94
+ * Messages are loaded from loader functions. The loader functions will only run
95
+ * when a message is required. This prevents all messages from being loaded into memory at
96
+ * application startup. The functions can load from memory, a file, or a server.
97
+ *
98
+ * In the beginning of your app or file, add the loader functions to be used later. If using
99
+ * json or js files in a root messages directory (`<moduleRoot>/messages`), load the entire directory
100
+ * automatically with {@link Messages.importMessagesDirectory}. Message files must be the following formates.
101
+ *
102
+ * A `.json` file:
103
+ * ```json
104
+ * {
105
+ * "msgKey": "A message displayed in the user",
106
+ * "msgGroup": {
107
+ * "anotherMsgKey": "Another message displayed to the user"
108
+ * },
109
+ * "listOfMessage": ["message1", "message2"]
110
+ * }
111
+ * ```
112
+ *
113
+ * A `.js` file:
114
+ * ```javascript
115
+ * module.exports = {
116
+ * msgKey: 'A message displayed in the user',
117
+ * msgGroup: {
118
+ * anotherMsgKey: 'Another message displayed to the user'
119
+ * },
120
+ * listOfMessage: ['message1', 'message2']
121
+ * }
122
+ * ```
123
+ *
124
+ * A `.md` file:
125
+ * ```markdown
126
+ * # msgKey
127
+ * A message displayed in the user
128
+ *
129
+ * # msgGroup.anotherMsgKey
130
+ * Another message displayed to the user
131
+ *
132
+ * # listOfMessage
133
+ * - message1
134
+ * - message2
135
+ * ```
136
+ *
137
+ * The values support [util.format](https://nodejs.org/api/util.html#util_util_format_format_args) style strings
138
+ * that apply the tokens passed into {@link Message.getMessage}
139
+ *
140
+ * **Note:** When running unit tests individually, you may see errors that the messages aren't found.
141
+ * This is because `index.js` isn't loaded when tests run like they are when the package is required.
142
+ * To allow tests to run, import the message directory in each test (it will only
143
+ * do it once) or load the message file the test depends on individually.
144
+ *
145
+ * ```typescript
146
+ * // Create loader functions for all files in the messages directory
147
+ * Messages.importMessagesDirectory(__dirname);
148
+ *
149
+ * // Now you can use the messages from anywhere in your code or file.
150
+ * // If using importMessageDirectory, the bundle name is the file name.
151
+ * const messages: Messages = Messages.load(packageName, bundleName);
152
+ *
153
+ * // Messages now contains all the message in the bundleName file.
154
+ * messages.getMessage('authInfoCreationError');
155
+ * ```
156
+ */
157
+ class Messages {
158
+ /**
159
+ * Create a new messages bundle.
160
+ *
161
+ * **Note:** Use {Messages.load} unless you are writing your own loader function.
162
+ *
163
+ * @param bundleName The bundle name.
164
+ * @param locale The locale.
165
+ * @param messages The messages. Can not be modified once created.
166
+ */
167
+ constructor(bundleName, locale, messages) {
168
+ this.messages = messages;
169
+ this.bundleName = bundleName;
170
+ this.locale = locale;
171
+ }
172
+ /**
173
+ * Get the locale. This will always return 'en_US' but will return the
174
+ * machine's locale in the future.
175
+ */
176
+ static getLocale() {
177
+ return 'en_US';
178
+ }
179
+ /**
180
+ * Set a custom loader function for a package and bundle that will be called on {@link Messages.load}.
181
+ *
182
+ * @param packageName The npm package name.
183
+ * @param bundle The name of the bundle.
184
+ * @param loader The loader function.
185
+ */
186
+ static setLoaderFunction(packageName, bundle, loader) {
187
+ this.loaders.set(new Key(packageName, bundle).toString(), loader);
188
+ }
189
+ /**
190
+ * Generate a file loading function. Use {@link Messages.importMessageFile} unless
191
+ * overriding the bundleName is required, then manually pass the loader
192
+ * function to {@link Messages.setLoaderFunction}.
193
+ *
194
+ * @param bundleName The name of the bundle.
195
+ * @param filePath The messages file path.
196
+ */
197
+ static generateFileLoaderFunction(bundleName, filePath) {
198
+ const ext = path.extname(filePath);
199
+ if (!['.json', '.js', '.md'].includes(ext)) {
200
+ throw new Error(`Only json, js and md message files are allowed, not ${ext}: ${filePath}`);
201
+ }
202
+ return (locale) => {
203
+ let fileContents;
204
+ let parser;
205
+ if (ext === '.md') {
206
+ fileContents = fs.readFileSync(filePath, 'utf-8');
207
+ parser = markdownLoader;
208
+ }
209
+ else {
210
+ // Anything can be returned by a js file, so stringify the results to ensure valid json is returned.
211
+ fileContents = JSON.stringify(Messages.readFile(filePath));
212
+ // If the file is empty, JSON.stringify will turn it into "" which will validate on parse.
213
+ if (fileContents === 'null' || fileContents === '""')
214
+ fileContents = '';
215
+ parser = jsAndJsonLoader;
216
+ }
217
+ if (!fileContents || fileContents.trim().length === 0) {
218
+ // messages.js should have no internal dependencies.
219
+ const error = new Error(`Invalid message file: ${filePath}. No content.`);
220
+ error.name = 'SfError';
221
+ throw error;
222
+ }
223
+ const map = parser(filePath, fileContents);
224
+ return new Messages(bundleName, locale, map);
225
+ };
226
+ }
227
+ /**
228
+ * Add a single message file to the list of loading functions using the file name as the bundle name.
229
+ * The loader will only be added if the bundle name is not already taken.
230
+ *
231
+ * @param packageName The npm package name.
232
+ * @param filePath The path of the file.
233
+ */
234
+ static importMessageFile(packageName, filePath) {
235
+ const bundleName = path.basename(filePath, path.extname(filePath));
236
+ if (!Messages.isCached(packageName, bundleName)) {
237
+ this.setLoaderFunction(packageName, bundleName, Messages.generateFileLoaderFunction(bundleName, filePath));
238
+ }
239
+ }
240
+ /**
241
+ * Import all json and js files in a messages directory. Use the file name as the bundle key when
242
+ * {@link Messages.load} is called. By default, we're assuming the moduleDirectoryPart is a
243
+ * typescript project and will truncate to root path (where the package.json file is). If your messages
244
+ * directory is in another spot or you are not using typescript, pass in false for truncateToProjectPath.
245
+ *
246
+ * ```
247
+ * // e.g. If your message directory is in the project root, you would do:
248
+ * Messages.importMessagesDirectory(__dirname);
249
+ * ```
250
+ *
251
+ * @param moduleDirectoryPath The path to load the messages folder.
252
+ * @param truncateToProjectPath Will look for the messages directory in the project root (where the package.json file is located).
253
+ * i.e., the module is typescript and the messages folder is in the top level of the module directory.
254
+ * @param packageName The npm package name. Figured out from the root directory's package.json.
255
+ */
256
+ static importMessagesDirectory(moduleDirectoryPath, truncateToProjectPath = true, packageName) {
257
+ let moduleMessagesDirPath = moduleDirectoryPath;
258
+ let projectRoot = moduleDirectoryPath;
259
+ if (!path.isAbsolute(moduleDirectoryPath)) {
260
+ throw new Error('Invalid module path. Relative URLs are not allowed.');
261
+ }
262
+ while (projectRoot.length >= 0) {
263
+ try {
264
+ fs.statSync(path.join(projectRoot, 'package.json'));
265
+ break;
266
+ }
267
+ catch (err) {
268
+ if (err.code !== 'ENOENT')
269
+ throw err;
270
+ projectRoot = projectRoot.substring(0, projectRoot.lastIndexOf(path.sep));
271
+ }
272
+ }
273
+ if (truncateToProjectPath) {
274
+ moduleMessagesDirPath = projectRoot;
275
+ }
276
+ if (!packageName) {
277
+ const errMessage = `Invalid or missing package.json file at '${moduleMessagesDirPath}'. If not using a package.json, pass in a packageName.`;
278
+ try {
279
+ packageName = (0, ts_types_1.asString)((0, ts_types_1.ensureJsonMap)(Messages.readFile(path.join(moduleMessagesDirPath, 'package.json'))).name);
280
+ if (!packageName) {
281
+ throw new kit_1.NamedError('MissingPackageName', errMessage);
282
+ }
283
+ }
284
+ catch (err) {
285
+ throw new kit_1.NamedError('MissingPackageName', errMessage, err);
286
+ }
287
+ }
288
+ moduleMessagesDirPath += `${path.sep}messages`;
289
+ for (const file of fs.readdirSync(moduleMessagesDirPath)) {
290
+ const filePath = path.join(moduleMessagesDirPath, file);
291
+ const stat = fs.statSync(filePath);
292
+ if (stat) {
293
+ if (stat.isDirectory()) {
294
+ // When we support other locales, load them from /messages/<local>/<bundleName>.json
295
+ // Change generateFileLoaderFunction to handle loading locales.
296
+ }
297
+ else if (stat.isFile()) {
298
+ this.importMessageFile(packageName, filePath);
299
+ }
300
+ }
301
+ }
302
+ }
303
+ /**
304
+ * Load messages for a given package and bundle. If the bundle is not already cached, use the loader function
305
+ * created from {@link Messages.setLoaderFunction} or {@link Messages.importMessagesDirectory}.
306
+ *
307
+ * **NOTE: Use {@link Messages.load} instead for safe message validation and usage.**
308
+ *
309
+ * ```typescript
310
+ * Messages.importMessagesDirectory(__dirname);
311
+ * const messages = Messages.load('packageName', 'bundleName');
312
+ * ```
313
+ *
314
+ * @param packageName The name of the npm package.
315
+ * @param bundleName The name of the bundle to load.
316
+ */
317
+ static loadMessages(packageName, bundleName) {
318
+ const key = new Key(packageName, bundleName);
319
+ let messages;
320
+ if (this.isCached(packageName, bundleName)) {
321
+ messages = this.bundles.get(key.toString());
322
+ }
323
+ else if (this.loaders.has(key.toString())) {
324
+ const loader = this.loaders.get(key.toString());
325
+ if (loader) {
326
+ messages = loader(Messages.getLocale());
327
+ this.bundles.set(key.toString(), messages);
328
+ messages = this.bundles.get(key.toString());
329
+ }
330
+ }
331
+ if (messages) {
332
+ return messages;
333
+ }
334
+ // Don't use messages inside messages
335
+ throw new kit_1.NamedError('MissingBundleError', `Missing bundle ${key.toString()} for locale ${Messages.getLocale()}.`);
336
+ }
337
+ /**
338
+ * Load messages for a given package and bundle. If the bundle is not already cached, use the loader function
339
+ * created from {@link Messages.setLoaderFunction} or {@link Messages.importMessagesDirectory}.
340
+ *
341
+ * The message keys that will be used must be passed in for validation. This prevents runtime errors if messages are used but not defined.
342
+ *
343
+ * **NOTE: This should be defined at the top of the file so validation is done at load time rather than runtime.**
344
+ *
345
+ * ```typescript
346
+ * Messages.importMessagesDirectory(__dirname);
347
+ * const messages = Messages.load('packageName', 'bundleName', [
348
+ * 'messageKey1',
349
+ * 'messageKey2',
350
+ * ]);
351
+ * ```
352
+ *
353
+ * @param packageName The name of the npm package.
354
+ * @param bundleName The name of the bundle to load.
355
+ * @param keys The message keys that will be used.
356
+ */
357
+ static load(packageName, bundleName, keys) {
358
+ const key = new Key(packageName, bundleName);
359
+ let messages;
360
+ if (this.isCached(packageName, bundleName)) {
361
+ messages = this.bundles.get(key.toString());
362
+ }
363
+ else if (this.loaders.has(key.toString())) {
364
+ const loader = this.loaders.get(key.toString());
365
+ if (loader) {
366
+ messages = loader(Messages.getLocale());
367
+ this.bundles.set(key.toString(), messages);
368
+ messages = this.bundles.get(key.toString());
369
+ }
370
+ }
371
+ if (messages) {
372
+ // Type guard on key length, but do a runtime check.
373
+ if (!keys || keys.length === 0) {
374
+ throw new kit_1.NamedError('MissingKeysError', 'Can not load messages without providing the message keys that will be used.');
375
+ }
376
+ // Get all messages to validate they are actually present
377
+ for (const messageKey of keys) {
378
+ messages.getMessage(messageKey);
379
+ }
380
+ return messages;
381
+ }
382
+ // Don't use messages inside messages
383
+ throw new kit_1.NamedError('MissingBundleError', `Missing bundle ${key.toString()} for locale ${Messages.getLocale()}.`);
384
+ }
385
+ /**
386
+ * Check if a bundle already been loaded.
387
+ *
388
+ * @param packageName The npm package name.
389
+ * @param bundleName The bundle name.
390
+ */
391
+ static isCached(packageName, bundleName) {
392
+ return this.bundles.has(new Key(packageName, bundleName).toString());
393
+ }
394
+ /**
395
+ * Get a message using a message key and use the tokens as values for tokenization.
396
+ *
397
+ * If the key happens to be an array of messages, it will combine with OS.EOL.
398
+ *
399
+ * @param key The key of the message.
400
+ * @param tokens The values to substitute in the message.
401
+ *
402
+ * **See** https://nodejs.org/api/util.html#util_util_format_format_args
403
+ */
404
+ getMessage(key, tokens = []) {
405
+ return this.getMessageWithMap(key, tokens, this.messages).join(os.EOL);
406
+ }
407
+ /**
408
+ * Get messages using a message key and use the tokens as values for tokenization.
409
+ *
410
+ * This will return all messages if the key is an array in the messages file.
411
+ *
412
+ * ```json
413
+ * {
414
+ * "myKey": [ "message1", "message2" ]
415
+ * }
416
+ * ```
417
+ *
418
+ * ```markdown
419
+ * # myKey
420
+ * * message1
421
+ * * message2
422
+ * ```
423
+ *
424
+ * @param key The key of the messages.
425
+ * @param tokens The values to substitute in the message.
426
+ *
427
+ * **See** https://nodejs.org/api/util.html#util_util_format_format_args
428
+ */
429
+ getMessages(key, tokens = []) {
430
+ return this.getMessageWithMap(key, tokens, this.messages);
431
+ }
432
+ /**
433
+ * Convenience method to create errors using message labels.
434
+ *
435
+ * `error.name` will be the upper-cased key, remove prefixed `error.` and will always end in Error.
436
+ * `error.actions` will be loaded using `${key}.actions` if available.
437
+ *
438
+ * @param key The key of the error message.
439
+ * @param tokens The error message tokens.
440
+ * @param actionTokens The action messages tokens.
441
+ * @param exitCodeOrCause The exit code which will be used by SfdxCommand or the underlying error that caused this error to be raised.
442
+ * @param cause The underlying error that caused this error to be raised.
443
+ */
444
+ createError(key, tokens = [], actionTokens = [], exitCodeOrCause, cause) {
445
+ const structuredMessage = this.formatMessageContents('error', key, tokens, actionTokens);
446
+ return new sfError_1.SfError(structuredMessage.message, structuredMessage.name, structuredMessage.actions, exitCodeOrCause, cause);
447
+ }
448
+ /**
449
+ * Convenience method to create warning using message labels.
450
+ *
451
+ * `warning.name` will be the upper-cased key, remove prefixed `warning.` and will always end in Warning.
452
+ * `warning.actions` will be loaded using `${key}.actions` if available.
453
+ *
454
+ * @param key The key of the warning message.
455
+ * @param tokens The warning message tokens.
456
+ * @param actionTokens The action messages tokens.
457
+ */
458
+ createWarning(key, tokens = [], actionTokens = []) {
459
+ return this.formatMessageContents('warning', key, tokens, actionTokens);
460
+ }
461
+ /**
462
+ * Convenience method to create info using message labels.
463
+ *
464
+ * `info.name` will be the upper-cased key, remove prefixed `info.` and will always end in Info.
465
+ * `info.actions` will be loaded using `${key}.actions` if available.
466
+ *
467
+ * @param key The key of the warning message.
468
+ * @param tokens The warning message tokens.
469
+ * @param actionTokens The action messages tokens.
470
+ */
471
+ createInfo(key, tokens = [], actionTokens = []) {
472
+ return this.formatMessageContents('info', key, tokens, actionTokens);
473
+ }
474
+ /**
475
+ * Formats message contents given a message type, key, tokens and actions tokens
476
+ *
477
+ * `<type>.name` will be the upper-cased key, remove prefixed `<type>.` and will always end in 'Error | Warning | Info.
478
+ * `<type>.actions` will be loaded using `${key}.actions` if available.
479
+ *
480
+ * @param type The type of the message set must 'error' | 'warning' | 'info'.
481
+ * @param key The key of the warning message.
482
+ * @param tokens The warning message tokens.
483
+ * @param actionTokens The action messages tokens.
484
+ */
485
+ formatMessageContents(type, key, tokens = [], actionTokens = []) {
486
+ const label = (0, kit_1.upperFirst)(type);
487
+ const labelRegExp = new RegExp(`${label}$`);
488
+ const searchValue = type === 'error' ? /^error.*\./ : new RegExp(`^${type}.`);
489
+ // Convert key to name:
490
+ // 'myMessage' -> `MyMessageWarning`
491
+ // 'myMessageError' -> `MyMessageError`
492
+ // 'warning.myMessage' -> `MyMessageWarning`
493
+ const name = `${(0, kit_1.upperFirst)(key.replace(searchValue, ''))}${labelRegExp.exec(key) ? '' : label}`;
494
+ const message = this.getMessage(key, tokens);
495
+ let actions;
496
+ try {
497
+ actions = this.getMessageWithMap(`${key}.actions`, actionTokens, this.messages);
498
+ }
499
+ catch (e) {
500
+ /* just ignore if actions aren't found */
501
+ }
502
+ return { message, name, actions };
503
+ }
504
+ getMessageWithMap(key, tokens = [], map) {
505
+ // Allow nested keys for better grouping
506
+ const group = RegExp(/([a-zA-Z0-9_-]+)\.(.*)/).exec(key);
507
+ if (group) {
508
+ const parentKey = group[1];
509
+ const childKey = group[2];
510
+ const childObject = map.get(parentKey);
511
+ if (childObject && (0, ts_types_1.isJsonMap)(childObject)) {
512
+ const childMap = new Map(Object.entries(childObject));
513
+ return this.getMessageWithMap(childKey, tokens, childMap);
514
+ }
515
+ }
516
+ if (!map.has(key)) {
517
+ // Don't use messages inside messages
518
+ throw new kit_1.NamedError('MissingMessageError', `Missing message ${this.bundleName}:${key} for locale ${Messages.getLocale()}.`);
519
+ }
520
+ const msg = map.get(key);
521
+ const messages = ((0, ts_types_1.isArray)(msg) ? msg : [msg]);
522
+ return messages.map((message) => {
523
+ (0, ts_types_1.ensureString)(message);
524
+ return util.format(message, ...tokens);
525
+ });
526
+ }
527
+ }
528
+ exports.Messages = Messages;
529
+ // It would be AWESOME to use Map<Key, Message> but js does an object instance comparison and doesn't let you
530
+ // override valueOf or equals for the === operator, which map uses. So, Use Map<String, Message>
531
+ // A map of loading functions to dynamically load messages when they need to be used
532
+ Messages.loaders = new Map();
533
+ // A map cache of messages bundles that have already been loaded
534
+ Messages.bundles = new Map();
535
+ /**
536
+ * Internal readFile. Exposed for unit testing. Do not use util/fs.readFile as messages.js
537
+ * should have no internal dependencies.
538
+ *
539
+ * @param filePath read file target.
540
+ * @ignore
541
+ */
542
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
543
+ Messages.readFile = (filePath) => require(filePath);
544
544
  //# sourceMappingURL=messages.js.map