@sap-ux/create 1.3.27 → 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.
- package/README.md +4 -2
- package/dist/cli/add/system.js +18 -8
- package/dist/cli/get/system.js +7 -2
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +4 -1
- package/dist/cli/remove/system.js +7 -3
- package/dist/cli/update/system.js +51 -13
- package/dist/cli/utils/system-connection.d.ts +26 -20
- package/dist/cli/utils/system-connection.js +91 -27
- package/dist/cli/utils/system-lookup.d.ts +17 -0
- package/dist/cli/utils/system-lookup.js +94 -0
- package/dist/cli/utils/system-prompts.d.ts +20 -11
- package/dist/cli/utils/system-prompts.js +108 -87
- package/dist/i18n.d.ts +15 -0
- package/dist/i18n.js +41 -0
- package/dist/index.js +2 -1
- package/dist/translations/ux-create.i18n.json +68 -0
- package/package.json +16 -15
|
@@ -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:
|
|
30
|
-
authenticationType:
|
|
31
|
-
connectionType:
|
|
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) ? '
|
|
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
|
-
|
|
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
|
|
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 '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
153
|
+
message: t('systemPrompts.prompts.connectionType'),
|
|
181
154
|
choices: Object.values(ConnectionType).map((type) => ({ title: type, value: type }))
|
|
182
155
|
});
|
|
183
156
|
}
|
|
184
|
-
|
|
185
|
-
|
|
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: '
|
|
174
|
+
message: t('systemPrompts.prompts.username')
|
|
189
175
|
});
|
|
190
176
|
}
|
|
191
|
-
if (partial.password === undefined) {
|
|
192
|
-
|
|
177
|
+
if (shouldPromptCredentials && partial.password === undefined) {
|
|
178
|
+
credentialQuestions.push({
|
|
193
179
|
type: 'password',
|
|
194
180
|
name: 'password',
|
|
195
|
-
message: '
|
|
181
|
+
message: t('systemPrompts.prompts.password')
|
|
196
182
|
});
|
|
197
183
|
}
|
|
198
|
-
const
|
|
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:
|
|
205
|
-
connectionType: partial.connectionType || answers.connectionType,
|
|
206
|
-
username: partial.username ?? (
|
|
207
|
-
password: partial.password ?? (
|
|
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: '
|
|
210
|
+
message: t('systemPrompts.prompts.systemUrl'),
|
|
225
211
|
validate: validateUrlField
|
|
226
212
|
});
|
|
227
213
|
}
|
|
228
|
-
if (
|
|
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: '
|
|
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: '
|
|
240
|
+
message: t('systemPrompts.updateFields.selectPrompt'),
|
|
253
241
|
choices: [
|
|
254
|
-
{
|
|
255
|
-
|
|
256
|
-
|
|
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('
|
|
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: '
|
|
296
|
+
message: t('systemPrompts.updateFields.newNamePrompt'),
|
|
281
297
|
initial: existing.name,
|
|
282
|
-
validate: (value) =>
|
|
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: '
|
|
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: '
|
|
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
|
-
|
|
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:
|
|
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(
|
|
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
|
+
}
|