@sap-ux/create 1.3.26 → 1.4.2

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.
@@ -0,0 +1,94 @@
1
+ import prompts from 'prompts';
2
+ import { BackendSystemKey as BackendSystemKeyClass, ConnectionType } from '@sap-ux/store';
3
+ import { getLogger } from '../../tracing/index.js';
4
+ import { t } from '../../i18n.js';
5
+ /**
6
+ * Finds a backend system by URL with smart matching when client is not specified or doesn't match.
7
+ *
8
+ * Logic:
9
+ * 1. Find all systems with the same URL (across all connection types).
10
+ * 2. If exactly one system found, use it automatically.
11
+ * 3. If multiple systems found, try exact match first, then prompt user to select if no exact match.
12
+ * 4. If none found, return undefined.
13
+ *
14
+ * @param url - URL of the backend system
15
+ * @param client - optional SAP client
16
+ * @param service - backend system service
17
+ * @returns the matched system, or undefined if not found or user cancelled
18
+ */
19
+ export async function findSystemByUrl(url, client, service) {
20
+ const logger = getLogger();
21
+ // Normalize URL for comparison (remove trailing slash)
22
+ const normalizedUrl = url.trim().replace(/\/$/, '');
23
+ // Search all systems with this URL (across all connection types)
24
+ // Explicitly specify all connection types to bypass the default 'abap_catalog' filter
25
+ logger.debug(`Searching for systems with URL: ${normalizedUrl}`);
26
+ const allSystems = await service.getAll({
27
+ backendSystemFilter: {
28
+ connectionType: Object.values(ConnectionType)
29
+ }
30
+ });
31
+ const matches = allSystems.filter((s) => {
32
+ const systemUrl = s.url.trim().replace(/\/$/, '');
33
+ return systemUrl === normalizedUrl;
34
+ });
35
+ if (matches.length === 0) {
36
+ logger.debug(`No systems found with URL: ${normalizedUrl}`);
37
+ return undefined;
38
+ }
39
+ if (matches.length === 1) {
40
+ logger.debug(`Found single system with URL ${normalizedUrl}: ${matches[0].name}`);
41
+ return matches[0];
42
+ }
43
+ // Multiple matches - only try exact match if client was explicitly provided
44
+ if (client !== undefined) {
45
+ const exactKey = new BackendSystemKeyClass({ url: normalizedUrl, client });
46
+ const exactMatch = matches.find((s) => {
47
+ const systemClient = s.client || undefined;
48
+ return systemClient === client;
49
+ });
50
+ if (exactMatch) {
51
+ logger.debug(`Found exact match for ${exactKey.getId()}: ${exactMatch.name}`);
52
+ return exactMatch;
53
+ }
54
+ }
55
+ // No exact match or client not provided - prompt user to select from multiple matches
56
+ return await promptToSelectSystem(matches);
57
+ }
58
+ /**
59
+ * Prompts the user to select a system from multiple matches.
60
+ *
61
+ * @param systems - list of matching systems
62
+ * @returns the selected system, or undefined if cancelled
63
+ */
64
+ async function promptToSelectSystem(systems) {
65
+ const logger = getLogger();
66
+ logger.info(t('systemLookup.multipleSystemsFound', { count: systems.length }));
67
+ systems.forEach((s, index) => {
68
+ const clientInfo = s.client
69
+ ? ` ${t('systemLookup.clientInfo', { client: s.client })}`
70
+ : ` ${t('systemLookup.noClient')}`;
71
+ logger.info(`${index + 1}. ${s.name}${clientInfo}`);
72
+ });
73
+ const answer = await prompts({
74
+ type: 'select',
75
+ name: 'index',
76
+ message: t('systemLookup.selectSystemPrompt'),
77
+ choices: systems.map((s, index) => {
78
+ const clientLabel = s.client
79
+ ? ` ${t('systemLookup.clientInfo', { client: s.client })}`
80
+ : ` ${t('systemLookup.noClient')}`;
81
+ return {
82
+ title: `${s.name}${clientLabel}`,
83
+ value: index
84
+ };
85
+ }),
86
+ initial: 0
87
+ });
88
+ if (answer.index == null || answer.index < 0 || answer.index >= systems.length) {
89
+ logger.debug('User cancelled system selection or invalid index');
90
+ return undefined;
91
+ }
92
+ return systems[answer.index];
93
+ }
94
+ //# sourceMappingURL=system-lookup.js.map
@@ -1,16 +1,24 @@
1
1
  import type { BackendSystem } from '@sap-ux/store';
2
+ import { SystemType, AuthenticationType, ConnectionType } from '@sap-ux/store';
3
+ /**
4
+ * Error thrown when user cancels credential clearing operation.
5
+ */
6
+ export declare class ClearCredentialsCancelledError extends Error {
7
+ constructor();
8
+ }
2
9
  /**
3
10
  * Prompts for complete system configuration, filling in any missing fields.
4
11
  *
5
12
  * @param partial - Partial system configuration with some fields already provided
6
- * @param partial.name
7
- * @param partial.url
8
- * @param partial.client
9
- * @param partial.systemType
10
- * @param partial.authenticationType
11
- * @param partial.connectionType
12
- * @param partial.username
13
- * @param partial.password
13
+ * @param partial.name - System display name
14
+ * @param partial.url - System URL
15
+ * @param partial.client - SAP client (optional)
16
+ * @param partial.systemType - System type
17
+ * @param partial.authenticationType - Authentication type
18
+ * @param partial.connectionType - Connection type
19
+ * @param partial.username - Username for basic auth
20
+ * @param partial.password - Password for basic auth
21
+ * @param partial.skipCredentialsPrompt - skip credential prompts entirely
14
22
  * @returns Complete system configuration with all required fields
15
23
  */
16
24
  export declare function promptForSystemConfig(partial: {
@@ -22,13 +30,14 @@ export declare function promptForSystemConfig(partial: {
22
30
  connectionType?: string;
23
31
  username?: string;
24
32
  password?: string;
33
+ skipCredentialsPrompt?: boolean;
25
34
  }): Promise<{
26
35
  name: string;
27
36
  url: string;
28
37
  client?: string;
29
- systemType: string;
30
- authenticationType: string;
31
- connectionType: string;
38
+ systemType: SystemType;
39
+ authenticationType: AuthenticationType;
40
+ connectionType: ConnectionType;
32
41
  username?: string;
33
42
  password?: string;
34
43
  }>;
@@ -1,6 +1,16 @@
1
1
  import prompts from 'prompts';
2
2
  import { SystemType, AuthenticationType, ConnectionType, isSystemNameInUse } from '@sap-ux/store';
3
- import { validateClient } from '@sap-ux/project-input-validator';
3
+ import { validateClient, validateUrl } from '@sap-ux/project-input-validator';
4
+ import { t } from '../../i18n.js';
5
+ /**
6
+ * Error thrown when user cancels credential clearing operation.
7
+ */
8
+ export class ClearCredentialsCancelledError extends Error {
9
+ constructor() {
10
+ super('Clear credentials cancelled');
11
+ this.name = 'ClearCredentialsCancelledError';
12
+ }
13
+ }
4
14
  /**
5
15
  * Checks if a string is empty or contains only whitespace.
6
16
  *
@@ -10,21 +20,6 @@ import { validateClient } from '@sap-ux/project-input-validator';
10
20
  function isEmptyString(value) {
11
21
  return !value || !/\S/.test(value);
12
22
  }
13
- /**
14
- * Checks if a string is a valid URL.
15
- *
16
- * @param value - The value to check
17
- * @returns true if valid URL, false otherwise
18
- */
19
- function isValidUrl(value) {
20
- try {
21
- const url = new URL(value);
22
- return !!url.protocol && !!url.host;
23
- }
24
- catch {
25
- return false;
26
- }
27
- }
28
23
  /**
29
24
  * Validates that a string is not empty after trimming whitespace.
30
25
  *
@@ -32,7 +27,7 @@ function isValidUrl(value) {
32
27
  * @returns True if valid, error message otherwise
33
28
  */
34
29
  function validateNonEmpty(value) {
35
- return isEmptyString(value) ? 'This field is required and cannot be empty' : true;
30
+ return isEmptyString(value) ? t('systemPrompts.validation.fieldRequired') : true;
36
31
  }
37
32
  /**
38
33
  * Validates that a URL is in correct format.
@@ -45,7 +40,11 @@ function validateUrlField(value) {
45
40
  if (nonEmptyCheck !== true) {
46
41
  return nonEmptyCheck;
47
42
  }
48
- return isValidUrl(value) ? true : 'Please enter a valid URL (e.g., https://my-system.example.com)';
43
+ const result = validateUrl(value);
44
+ if (result === true) {
45
+ return true;
46
+ }
47
+ return typeof result === 'string' ? result : t('systemPrompts.validation.invalidUrl');
49
48
  }
50
49
  /**
51
50
  * Validates that a client number is in correct format (empty or 3-digit number).
@@ -80,55 +79,29 @@ async function validateSystemNameUniqueness(value, excludeSystem) {
80
79
  }
81
80
  }
82
81
  if (isTaken) {
83
- return `A system with the name '${value}' already exists. Please choose a different name.`;
84
- }
85
- return true;
86
- }
87
- catch (error) {
88
- // Catch and convert service errors to validation messages to prevent duplicate names
89
- console.error('Error checking system name uniqueness:', error);
90
- return 'Unable to check system name uniqueness. Please try again.';
91
- }
92
- }
93
- /**
94
- * Validates that a system name is unique when updating (excluding the current system).
95
- *
96
- * @param value - The system name to validate
97
- * @param currentSystem - The system being updated (to exclude from uniqueness check)
98
- * @returns True if valid, error message otherwise
99
- */
100
- async function validateSystemNameUniquenessForUpdate(value, currentSystem) {
101
- const nonEmptyCheck = validateNonEmpty(value);
102
- if (nonEmptyCheck !== true) {
103
- return nonEmptyCheck;
104
- }
105
- try {
106
- const isTaken = await isSystemNameInUse(value);
107
- // Allow keeping the same name (case-insensitive)
108
- const isSameName = currentSystem.name.trim().toLowerCase() === value.trim().toLowerCase();
109
- if (isTaken && !isSameName) {
110
- return `A system with the name '${value}' already exists. Please choose a different name.`;
82
+ return t('systemPrompts.validation.systemNameExists', { name: value });
111
83
  }
112
84
  return true;
113
85
  }
114
86
  catch (error) {
115
87
  // Catch and convert service errors to validation messages to prevent duplicate names
116
88
  console.error('Error checking system name uniqueness:', error);
117
- return 'Unable to check system name uniqueness. Please try again.';
89
+ return t('systemPrompts.validation.checkNameFailed');
118
90
  }
119
91
  }
120
92
  /**
121
93
  * Prompts for complete system configuration, filling in any missing fields.
122
94
  *
123
95
  * @param partial - Partial system configuration with some fields already provided
124
- * @param partial.name
125
- * @param partial.url
126
- * @param partial.client
127
- * @param partial.systemType
128
- * @param partial.authenticationType
129
- * @param partial.connectionType
130
- * @param partial.username
131
- * @param partial.password
96
+ * @param partial.name - System display name
97
+ * @param partial.url - System URL
98
+ * @param partial.client - SAP client (optional)
99
+ * @param partial.systemType - System type
100
+ * @param partial.authenticationType - Authentication type
101
+ * @param partial.connectionType - Connection type
102
+ * @param partial.username - Username for basic auth
103
+ * @param partial.password - Password for basic auth
104
+ * @param partial.skipCredentialsPrompt - skip credential prompts entirely
132
105
  * @returns Complete system configuration with all required fields
133
106
  */
134
107
  export async function promptForSystemConfig(partial) {
@@ -137,7 +110,7 @@ export async function promptForSystemConfig(partial) {
137
110
  questions.push({
138
111
  type: 'text',
139
112
  name: 'name',
140
- message: 'System name (display name):',
113
+ message: t('systemPrompts.prompts.systemName'),
141
114
  validate: (value) => validateSystemNameUniqueness(value)
142
115
  });
143
116
  }
@@ -145,7 +118,7 @@ export async function promptForSystemConfig(partial) {
145
118
  questions.push({
146
119
  type: 'text',
147
120
  name: 'url',
148
- message: 'System URL:',
121
+ message: t('systemPrompts.prompts.systemUrl'),
149
122
  validate: validateUrlField
150
123
  });
151
124
  }
@@ -153,7 +126,7 @@ export async function promptForSystemConfig(partial) {
153
126
  questions.push({
154
127
  type: 'text',
155
128
  name: 'client',
156
- message: 'SAP client (optional, press Enter to skip):',
129
+ message: t('systemPrompts.prompts.sapClient'),
157
130
  validate: validateClientField
158
131
  });
159
132
  }
@@ -161,7 +134,7 @@ export async function promptForSystemConfig(partial) {
161
134
  questions.push({
162
135
  type: 'select',
163
136
  name: 'systemType',
164
- message: 'System type:',
137
+ message: t('systemPrompts.prompts.systemType'),
165
138
  choices: Object.values(SystemType).map((type) => ({ title: type, value: type }))
166
139
  });
167
140
  }
@@ -169,7 +142,7 @@ export async function promptForSystemConfig(partial) {
169
142
  questions.push({
170
143
  type: 'select',
171
144
  name: 'authenticationType',
172
- message: 'Authentication type:',
145
+ message: t('systemPrompts.prompts.authenticationType'),
173
146
  choices: Object.values(AuthenticationType).map((type) => ({ title: type, value: type }))
174
147
  });
175
148
  }
@@ -177,34 +150,47 @@ export async function promptForSystemConfig(partial) {
177
150
  questions.push({
178
151
  type: 'select',
179
152
  name: 'connectionType',
180
- message: 'Connection type:',
153
+ message: t('systemPrompts.prompts.connectionType'),
181
154
  choices: Object.values(ConnectionType).map((type) => ({ title: type, value: type }))
182
155
  });
183
156
  }
184
- if (partial.username === undefined) {
185
- questions.push({
157
+ // Prompt for basic questions first
158
+ const answers = questions.length > 0 ? await prompts(questions) : {};
159
+ // Determine final authenticationType (from partial or answers)
160
+ const finalAuthType = partial.authenticationType || answers.authenticationType;
161
+ // Only prompt for credentials if:
162
+ // 1. skipCredentialsPrompt flag is NOT set, AND
163
+ // 2. authenticationType is 'basic' (other types don't use username/password)
164
+ const shouldPromptCredentials = !partial.skipCredentialsPrompt && finalAuthType === 'basic';
165
+ // Display informational message for re-entrance ticket authentication
166
+ if (finalAuthType === 'reentranceTicket') {
167
+ console.log(t('systemPrompts.prompts.reentranceTicketNote'));
168
+ }
169
+ const credentialQuestions = [];
170
+ if (shouldPromptCredentials && partial.username === undefined) {
171
+ credentialQuestions.push({
186
172
  type: 'text',
187
173
  name: 'username',
188
- message: 'Username (optional, press Enter to skip):'
174
+ message: t('systemPrompts.prompts.username')
189
175
  });
190
176
  }
191
- if (partial.password === undefined) {
192
- questions.push({
177
+ if (shouldPromptCredentials && partial.password === undefined) {
178
+ credentialQuestions.push({
193
179
  type: 'password',
194
180
  name: 'password',
195
- message: 'Password (optional, press Enter to skip):'
181
+ message: t('systemPrompts.prompts.password')
196
182
  });
197
183
  }
198
- const answers = questions.length > 0 ? await prompts(questions) : {};
184
+ const credentialAnswers = credentialQuestions.length > 0 ? await prompts(credentialQuestions) : {};
199
185
  return {
200
186
  name: partial.name || answers.name,
201
187
  url: partial.url || answers.url,
202
188
  client: partial.client ?? (answers.client || undefined),
203
- systemType: partial.systemType || answers.systemType,
204
- authenticationType: partial.authenticationType || answers.authenticationType,
205
- connectionType: partial.connectionType || answers.connectionType,
206
- username: partial.username ?? (answers.username || undefined),
207
- password: partial.password ?? (answers.password || undefined)
189
+ systemType: (partial.systemType || answers.systemType),
190
+ authenticationType: finalAuthType,
191
+ connectionType: (partial.connectionType || answers.connectionType),
192
+ username: partial.username ?? (credentialAnswers?.username || undefined),
193
+ password: partial.password ?? (credentialAnswers?.password || undefined)
208
194
  };
209
195
  }
210
196
  /**
@@ -221,15 +207,17 @@ export async function promptForSystemIdentifier(partial) {
221
207
  questions.push({
222
208
  type: 'text',
223
209
  name: 'url',
224
- message: 'System URL:',
210
+ message: t('systemPrompts.prompts.systemUrl'),
225
211
  validate: validateUrlField
226
212
  });
227
213
  }
228
- if (partial.client === undefined) {
214
+ // Only prompt for client if URL is also being prompted (fully interactive mode)
215
+ // When URL is provided via flag, skip client prompt - let smart lookup handle multiple matches
216
+ if (!partial.url && partial.client === undefined) {
229
217
  questions.push({
230
218
  type: 'text',
231
219
  name: 'client',
232
- message: 'SAP client (optional, press Enter to skip):',
220
+ message: t('systemPrompts.prompts.sapClient'),
233
221
  validate: validateClientField
234
222
  });
235
223
  }
@@ -249,16 +237,25 @@ export async function promptForUpdateFields(existing) {
249
237
  const answer = await prompts({
250
238
  type: 'multiselect',
251
239
  name: 'fields',
252
- message: 'Select fields to update:',
240
+ message: t('systemPrompts.updateFields.selectPrompt'),
253
241
  choices: [
254
- { title: `Name (current: ${existing.name})`, value: 'name' },
255
- { title: `Username (current: ${existing.username || '(none)'})`, value: 'username' },
256
- { title: 'Password', value: 'password' }
242
+ {
243
+ title: t('systemPrompts.updateFields.nameLabel', { name: existing.name }),
244
+ value: 'name'
245
+ },
246
+ {
247
+ title: t('systemPrompts.updateFields.usernameLabel', {
248
+ username: existing.username || t('systemPrompts.updateFields.usernameNone')
249
+ }),
250
+ value: 'username'
251
+ },
252
+ { title: t('systemPrompts.updateFields.passwordLabel'), value: 'password' },
253
+ { title: t('systemPrompts.updateFields.clearCredentialsLabel'), value: 'clearCredentials' }
257
254
  ],
258
255
  min: 1
259
256
  });
260
257
  if (!answer.fields || answer.fields.length === 0) {
261
- throw new Error('At least one field must be selected');
258
+ throw new Error(t('systemPrompts.updateFields.minOneRequired'));
262
259
  }
263
260
  return answer.fields;
264
261
  }
@@ -270,6 +267,25 @@ export async function promptForUpdateFields(existing) {
270
267
  * @returns Object with new values for the specified fields
271
268
  */
272
269
  export async function promptForFieldUpdates(fields, existing) {
270
+ // Track if clearCredentials was requested
271
+ const clearCredentialsRequested = fields.includes('clearCredentials');
272
+ // Handle clearCredentials separately - it doesn't need a prompt, only confirmation
273
+ if (clearCredentialsRequested) {
274
+ const answer = await prompts({
275
+ type: 'confirm',
276
+ name: 'confirmClear',
277
+ message: t('systemPrompts.updateFields.clearCredentialsConfirm'),
278
+ initial: false
279
+ });
280
+ if (!answer.confirmClear) {
281
+ throw new ClearCredentialsCancelledError();
282
+ }
283
+ // Remove clearCredentials from fields to process
284
+ fields = fields.filter((f) => f !== 'clearCredentials');
285
+ if (fields.length === 0) {
286
+ return { clearCredentials: true };
287
+ }
288
+ }
273
289
  const questions = fields
274
290
  .map((field) => {
275
291
  switch (field) {
@@ -277,15 +293,15 @@ export async function promptForFieldUpdates(fields, existing) {
277
293
  return {
278
294
  type: 'text',
279
295
  name: 'name',
280
- message: 'New system name:',
296
+ message: t('systemPrompts.updateFields.newNamePrompt'),
281
297
  initial: existing.name,
282
- validate: (value) => validateSystemNameUniquenessForUpdate(value, existing)
298
+ validate: (value) => validateSystemNameUniqueness(value, existing)
283
299
  };
284
300
  case 'username':
285
301
  return {
286
302
  type: 'text',
287
303
  name: 'username',
288
- message: 'New username:',
304
+ message: t('systemPrompts.updateFields.newUsernamePrompt'),
289
305
  initial: existing.username || '',
290
306
  validate: validateNonEmpty
291
307
  };
@@ -293,7 +309,7 @@ export async function promptForFieldUpdates(fields, existing) {
293
309
  return {
294
310
  type: 'password',
295
311
  name: 'password',
296
- message: 'New password:',
312
+ message: t('systemPrompts.updateFields.newPasswordPrompt'),
297
313
  validate: validateNonEmpty
298
314
  };
299
315
  default:
@@ -301,7 +317,12 @@ export async function promptForFieldUpdates(fields, existing) {
301
317
  }
302
318
  })
303
319
  .filter((q) => q !== null);
304
- return await prompts(questions);
320
+ const result = await prompts(questions);
321
+ // Add clearCredentials flag if it was originally selected
322
+ if (clearCredentialsRequested) {
323
+ result.clearCredentials = true;
324
+ }
325
+ return result;
305
326
  }
306
327
  /**
307
328
  * Prompts for confirmation before removing a system.
@@ -313,7 +334,7 @@ export async function promptForRemoveConfirmation(systemName) {
313
334
  const answer = await prompts({
314
335
  type: 'confirm',
315
336
  name: 'confirm',
316
- message: `Are you sure you want to remove system '${systemName}'?`,
337
+ message: t('systemPrompts.removeConfirmation.prompt', { systemName }),
317
338
  initial: false
318
339
  });
319
340
  return answer.confirm === true;
package/dist/i18n.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ import type { i18n as i18nNext, TOptions } from 'i18next';
2
+ export declare const i18n: i18nNext;
3
+ /**
4
+ * Initialize i18next with the translations for this module.
5
+ */
6
+ export declare function initI18n(): Promise<void>;
7
+ /**
8
+ * Helper function facading the call to i18next. Unless a namespace option is provided, the local namespace will be used.
9
+ *
10
+ * @param key i18n key
11
+ * @param options additional options
12
+ * @returns {string} localized string stored for the given key
13
+ */
14
+ export declare function t(key: string, options?: TOptions): string;
15
+ //# sourceMappingURL=i18n.d.ts.map
package/dist/i18n.js ADDED
@@ -0,0 +1,41 @@
1
+ import i18next from 'i18next';
2
+ import translations from './translations/ux-create.i18n.json' with { type: 'json' };
3
+ const NS = 'ux-create';
4
+ export const i18n = i18next.createInstance();
5
+ let isInitialized = false;
6
+ /**
7
+ * Initialize i18next with the translations for this module.
8
+ */
9
+ export async function initI18n() {
10
+ await i18n.init({
11
+ resources: {
12
+ en: {
13
+ [NS]: translations
14
+ }
15
+ },
16
+ lng: 'en',
17
+ fallbackLng: 'en',
18
+ defaultNS: NS,
19
+ ns: [NS],
20
+ interpolation: { escapeValue: false }
21
+ });
22
+ isInitialized = true;
23
+ }
24
+ /**
25
+ * Helper function facading the call to i18next. Unless a namespace option is provided, the local namespace will be used.
26
+ *
27
+ * @param key i18n key
28
+ * @param options additional options
29
+ * @returns {string} localized string stored for the given key
30
+ */
31
+ export function t(key, options) {
32
+ if (!isInitialized) {
33
+ console.warn(`[i18n] t() called before initI18n() - returning key: ${key}`);
34
+ return key;
35
+ }
36
+ if (!options?.ns) {
37
+ options = Object.assign(options ?? {}, { ns: NS });
38
+ }
39
+ return i18n.t(key, options);
40
+ }
41
+ //# sourceMappingURL=i18n.js.map
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  import { handleCreateFioriCommand } from './cli/index.js';
3
- handleCreateFioriCommand(process.argv);
3
+ // Use top-level await since handleCreateFioriCommand is now async (for i18n init)
4
+ await handleCreateFioriCommand(process.argv);
4
5
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,68 @@
1
+ {
2
+ "systemLookup": {
3
+ "multipleSystemsFound_one": "{{count}} system found with this URL:",
4
+ "multipleSystemsFound_other": "{{count}} systems found with this URL:",
5
+ "selectSystemPrompt": "Which system do you want to use?",
6
+ "clientInfo": "(Client: {{client}})",
7
+ "noClient": "(No Client)"
8
+ },
9
+ "systemPrompts": {
10
+ "validation": {
11
+ "fieldRequired": "This is a required field and can't be empty.",
12
+ "invalidUrl": "Please enter a valid URL, for example https://my-system.example.com",
13
+ "systemNameExists": "A system with the name '{{name}}' already exists. Please choose a different name.",
14
+ "checkNameFailed": "Unable to check if the system name is unique. Please try again."
15
+ },
16
+ "prompts": {
17
+ "systemName": "System Name: ",
18
+ "systemUrl": "System URL: ",
19
+ "sapClient": "SAP Client (Optional - Press Enter to Skip): ",
20
+ "systemType": "System Type: ",
21
+ "authenticationType": "Authentication Type: ",
22
+ "connectionType": "Connection Type: ",
23
+ "username": "Username (Optional - Press Enter to Skip): ",
24
+ "password": "Password (Optional - Press Enter to Skip): ",
25
+ "reentranceTicketNote": "\nNote: Re-entrance ticket authentication will open a browser tab when the system is first used.\n"
26
+ },
27
+ "updateFields": {
28
+ "selectPrompt": "Select Fields to Update:",
29
+ "nameLabel": "Name (Existing: {{name}})",
30
+ "usernameLabel": "Username (Existing: {{username}})",
31
+ "usernameNone": "(None)",
32
+ "passwordLabel": "Password",
33
+ "clearCredentialsLabel": "Clear Stored Credentials",
34
+ "clearCredentialsConfirm": "Are you sure you want to clear all stored credentials?",
35
+ "minOneRequired": "You must select at least one field.",
36
+ "newNamePrompt": "New System Name:",
37
+ "newUsernamePrompt": "New Username:",
38
+ "newPasswordPrompt": "New Password:"
39
+ },
40
+ "removeConfirmation": {
41
+ "prompt": "Are you sure you want to remove system '{{systemName}}'?"
42
+ }
43
+ },
44
+ "systemConnection": {
45
+ "invalidUrl": "Invalid URL: {{url}}",
46
+ "skippingCheck": "Skipping connection check (--skip-connection-validation flag provided)",
47
+ "verifying": "Verifying connection to the back-end system...",
48
+ "connectionSuccessful": "✓ Connection established",
49
+ "connectionFailed": "Connection check failed: {{error}}",
50
+ "unknownError": "Unknown error",
51
+ "saveAnywayPrompt": "Connection check failed. Save system anyway?",
52
+ "catalogServicesFound_one": "{{count}} catalog service found",
53
+ "catalogServicesFound_other": "{{count}} catalog services found",
54
+ "metadataRequestSuccessful": "✓ Metadata requested",
55
+ "unexpectedStatus": "Unexpected response status: {{status}}"
56
+ },
57
+ "systemActions": {
58
+ "systemNotAdded": "System wasn't added",
59
+ "systemNotAddedInvalidConfig": "The system wasn't added due to an invalid system configuration. Check the error logs for more details.",
60
+ "systemNotAddedDuplicate": "The system wasn't added due to a duplicate system configuration. Check the error logs for more details.",
61
+ "systemNotSaved": "System wasn't saved",
62
+ "systemNotUpdated": "System wasn't updated",
63
+ "operationCancelled": "Operation canceled",
64
+ "systemAdded": "System '{{name}}' added",
65
+ "systemUpdated": "System '{{name}}' updated",
66
+ "systemRemoved": "System '{{name}}' removed"
67
+ }
68
+ }