@sap-ux/create 1.0.61 → 1.1.0
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 +13 -4
- package/dist/cli/add/system.js +122 -37
- package/dist/cli/remove/system.js +19 -5
- package/dist/cli/update/system.js +116 -27
- package/dist/cli/utils/system-connection.d.ts +46 -0
- package/dist/cli/utils/system-connection.js +63 -0
- package/dist/cli/utils/system-prompts.d.ts +72 -0
- package/dist/cli/utils/system-prompts.js +321 -0
- package/package.json +21 -19
package/README.md
CHANGED
|
@@ -288,15 +288,18 @@ Example:
|
|
|
288
288
|
|
|
289
289
|
`npx --yes @sap-ux/create@latest add system --name "My System" --url https://my-sap.example.com --client 100 --username myuser`
|
|
290
290
|
|
|
291
|
+
`npx --yes @sap-ux/create@latest add system` (interactive mode)
|
|
292
|
+
|
|
291
293
|
Options:
|
|
292
|
-
- `--name <string>`
|
|
293
|
-
- `--url <string>`
|
|
294
|
+
- `--name <string>` - Display name for the system
|
|
295
|
+
- `--url <string>` - URL of the backend system
|
|
294
296
|
- `--client <string>` - SAP client number (optional)
|
|
295
297
|
- `--type <string>` - System type (AbapCloud | OnPrem | Generic) _(default: `OnPrem`)_
|
|
296
298
|
- `--auth <string>` - Authentication type (basic | reentranceTicket | oauth2 | oauth2ClientCredential) _(default: `basic`)_
|
|
297
299
|
- `--connection-type <string>` - Connection type (abap_catalog | generic_host | odata_service) _(default: `abap_catalog`)_
|
|
298
300
|
- `--username <string>` - Username for basic authentication
|
|
299
301
|
- `--password <string>` - To avoid plain-text credentials in the shell's history, pass an env reference: --password env:MY_VAR
|
|
302
|
+
- `--skip-check` - Skip connection verification before saving
|
|
300
303
|
|
|
301
304
|
--------------------------------
|
|
302
305
|
|
|
@@ -398,9 +401,12 @@ Example:
|
|
|
398
401
|
|
|
399
402
|
`npx --yes @sap-ux/create@latest remove system --url https://my-sap.example.com --client 100`
|
|
400
403
|
|
|
404
|
+
`npx --yes @sap-ux/create@latest remove system` (interactive mode)
|
|
405
|
+
|
|
401
406
|
Options:
|
|
402
|
-
- `--url <string>`
|
|
407
|
+
- `--url <string>` - URL of the backend system to remove
|
|
403
408
|
- `--client <string>` - SAP client number (optional)
|
|
409
|
+
- `--force` - Skip confirmation prompt
|
|
404
410
|
|
|
405
411
|
--------------------------------
|
|
406
412
|
|
|
@@ -426,13 +432,16 @@ Example:
|
|
|
426
432
|
|
|
427
433
|
`npx --yes @sap-ux/create@latest update system --url https://my-sap.example.com --client 100 --username newuser`
|
|
428
434
|
|
|
435
|
+
`npx --yes @sap-ux/create@latest update system` (interactive mode)
|
|
436
|
+
|
|
429
437
|
Options:
|
|
430
|
-
- `--url <string>`
|
|
438
|
+
- `--url <string>` - URL of the backend system to update
|
|
431
439
|
- `--client <string>` - SAP client number to identify the system (optional)
|
|
432
440
|
- `--name <string>` - New display name for the system
|
|
433
441
|
- `--username <string>` - New username
|
|
434
442
|
- `--password <string>` - To avoid plain-text credentials in the shell's history, pass an env reference: --password env:MY_VAR
|
|
435
443
|
- `--clear-credentials` - Remove stored credentials from the system
|
|
444
|
+
- `--skip-check` - Skip connection verification before saving
|
|
436
445
|
|
|
437
446
|
--------------------------------
|
|
438
447
|
|
package/dist/cli/add/system.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { isAppStudio } from '@sap-ux/btp-utils';
|
|
2
|
-
import { getService, BackendSystem, BackendSystemKey, SystemType, AuthenticationType, ConnectionType } from '@sap-ux/store';
|
|
2
|
+
import { getService, BackendSystem, BackendSystemKey, SystemType, AuthenticationType, ConnectionType, isSystemNameInUse } from '@sap-ux/store';
|
|
3
3
|
import { replaceEnvVariables } from '@sap-ux/ui5-config';
|
|
4
4
|
import { config as loadEnvConfig } from 'dotenv';
|
|
5
|
+
import { validateClient } from '@sap-ux/project-input-validator';
|
|
5
6
|
import { getLogger } from '../../tracing/index.js';
|
|
7
|
+
import { promptForSystemConfig } from '../utils/system-prompts.js';
|
|
8
|
+
import { checkConnectionOrPrompt } from '../utils/system-connection.js';
|
|
6
9
|
/**
|
|
7
10
|
* Add the "add system" subcommand to a passed command.
|
|
8
11
|
* Adds a new backend system to the saved systems store (~/.fioritools).
|
|
@@ -18,15 +21,17 @@ export function addSystemAddCommand(cmd) {
|
|
|
18
21
|
Connection types: \`${Object.values(ConnectionType).join('`, `')}\`\n
|
|
19
22
|
Example:
|
|
20
23
|
\`npx --yes @sap-ux/create@latest add system --name "My System" --url https://my-sap.example.com\`
|
|
21
|
-
\`npx --yes @sap-ux/create@latest add system --name "My System" --url https://my-sap.example.com --client 100 --username myuser
|
|
22
|
-
|
|
23
|
-
.
|
|
24
|
+
\`npx --yes @sap-ux/create@latest add system --name "My System" --url https://my-sap.example.com --client 100 --username myuser\`
|
|
25
|
+
\`npx --yes @sap-ux/create@latest add system\` (interactive mode)`)
|
|
26
|
+
.option('--name <string>', 'Display name for the system')
|
|
27
|
+
.option('--url <string>', 'URL of the backend system')
|
|
24
28
|
.option('--client <string>', 'SAP client number (optional)')
|
|
25
29
|
.option('--type <string>', `System type (${Object.values(SystemType).join(' | ')})`, SystemType.AbapOnPrem)
|
|
26
30
|
.option('--auth <string>', `Authentication type (${Object.values(AuthenticationType).join(' | ')})`, AuthenticationType.Basic)
|
|
27
31
|
.option('--connection-type <string>', `Connection type (${Object.values(ConnectionType).join(' | ')})`, ConnectionType.AbapCatalog)
|
|
28
32
|
.option('--username <string>', 'Username for basic authentication')
|
|
29
33
|
.option('--password <string>', "To avoid plain-text credentials in the shell's history, pass an env reference: --password env:MY_VAR")
|
|
34
|
+
.option('--skip-check', 'Skip connection verification before saving')
|
|
30
35
|
.action(async (options) => {
|
|
31
36
|
loadEnvConfig();
|
|
32
37
|
await addSystem({
|
|
@@ -37,10 +42,89 @@ Example:
|
|
|
37
42
|
authenticationType: options.auth,
|
|
38
43
|
connectionType: options.connectionType,
|
|
39
44
|
username: options.username,
|
|
40
|
-
password: options.password
|
|
45
|
+
password: options.password,
|
|
46
|
+
skipCheck: !!options.skipCheck
|
|
41
47
|
});
|
|
42
48
|
});
|
|
43
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Validates the system configuration fields.
|
|
52
|
+
*
|
|
53
|
+
* @param config - system configuration to validate
|
|
54
|
+
* @param config.name - display name
|
|
55
|
+
* @param config.url - URL of the backend system
|
|
56
|
+
* @param config.client - optional SAP client
|
|
57
|
+
* @param config.systemType - system type
|
|
58
|
+
* @param config.authenticationType - authentication type
|
|
59
|
+
* @param config.connectionType - connection type
|
|
60
|
+
* @param logger - logger instance
|
|
61
|
+
* @returns true if valid, false otherwise
|
|
62
|
+
*/
|
|
63
|
+
function validateSystemConfig(config, logger) {
|
|
64
|
+
if (!config.name || !config.url || !config.systemType || !config.authenticationType || !config.connectionType) {
|
|
65
|
+
logger.error('Missing required fields. System was not added.');
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
if (config.name.trim().length === 0) {
|
|
69
|
+
logger.error('System name cannot be empty or whitespace-only.');
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
new URL(config.url);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
logger.error(`Invalid URL: '${config.url}'`);
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
if (config.client !== undefined && config.client !== '') {
|
|
80
|
+
const clientValidation = validateClient(config.client);
|
|
81
|
+
if (clientValidation !== true) {
|
|
82
|
+
logger.error(`Invalid client '${config.client}'. Leave this field empty or enter a value between 000-999.`);
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const validSystemTypes = Object.values(SystemType);
|
|
87
|
+
if (!validSystemTypes.includes(config.systemType)) {
|
|
88
|
+
logger.error(`Invalid system type '${config.systemType}'. Valid values: ${validSystemTypes.join(', ')}`);
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
const validAuthTypes = Object.values(AuthenticationType);
|
|
92
|
+
if (!validAuthTypes.includes(config.authenticationType)) {
|
|
93
|
+
logger.error(`Invalid auth type '${config.authenticationType}'. Valid values: ${validAuthTypes.join(', ')}`);
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
const validConnectionTypes = Object.values(ConnectionType);
|
|
97
|
+
if (!validConnectionTypes.includes(config.connectionType)) {
|
|
98
|
+
logger.error(`Invalid connection type '${config.connectionType}'. Valid values: ${validConnectionTypes.join(', ')}`);
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Checks for duplicate systems in the store.
|
|
105
|
+
*
|
|
106
|
+
* @param config - system configuration
|
|
107
|
+
* @param config.name - system display name
|
|
108
|
+
* @param config.url - system URL
|
|
109
|
+
* @param config.client - optional SAP client
|
|
110
|
+
* @param service - backend system service
|
|
111
|
+
* @param logger - logger instance
|
|
112
|
+
* @returns true if no duplicates found, false otherwise
|
|
113
|
+
*/
|
|
114
|
+
async function checkForDuplicates(config, service, logger) {
|
|
115
|
+
const existingSystem = await service.read(new BackendSystemKey({ url: config.url, client: config.client }));
|
|
116
|
+
if (existingSystem) {
|
|
117
|
+
const clientSuffix = config.client ? ` (client ${config.client})` : '';
|
|
118
|
+
logger.error(`System '${config.url}'${clientSuffix} already exists. Use 'update system' to update it.`);
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
const nameExists = await isSystemNameInUse(config.name);
|
|
122
|
+
if (nameExists) {
|
|
123
|
+
logger.error(`A system with the name '${config.name}' already exists. Please choose a different name.`);
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
44
128
|
/**
|
|
45
129
|
* Adds a backend system to the saved systems store.
|
|
46
130
|
*
|
|
@@ -53,6 +137,7 @@ Example:
|
|
|
53
137
|
* @param params.connectionType - connection type
|
|
54
138
|
* @param params.username - optional username for basic auth
|
|
55
139
|
* @param params.password - optional password for basic auth
|
|
140
|
+
* @param params.skipCheck - skip connection verification
|
|
56
141
|
*/
|
|
57
142
|
async function addSystem(params) {
|
|
58
143
|
const logger = getLogger();
|
|
@@ -61,37 +146,7 @@ async function addSystem(params) {
|
|
|
61
146
|
logger.error('System management using the CLI is not supported in SAP Business Application Studio. Use the built-in system management instead.');
|
|
62
147
|
return;
|
|
63
148
|
}
|
|
64
|
-
|
|
65
|
-
new URL(params.url);
|
|
66
|
-
}
|
|
67
|
-
catch {
|
|
68
|
-
logger.error(`Invalid URL: '${params.url}'`);
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
const validSystemTypes = Object.values(SystemType);
|
|
72
|
-
if (!validSystemTypes.includes(params.systemType)) {
|
|
73
|
-
logger.error(`Invalid system type '${params.systemType}'. Valid values: ${validSystemTypes.join(', ')}`);
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
const validAuthTypes = Object.values(AuthenticationType);
|
|
77
|
-
if (!validAuthTypes.includes(params.authenticationType)) {
|
|
78
|
-
logger.error(`Invalid auth type '${params.authenticationType}'. Valid values: ${validAuthTypes.join(', ')}`);
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
const validConnectionTypes = Object.values(ConnectionType);
|
|
82
|
-
if (!validConnectionTypes.includes(params.connectionType)) {
|
|
83
|
-
logger.error(`Invalid connection type '${params.connectionType}'. Valid values: ${validConnectionTypes.join(', ')}`);
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
const service = await getService({ entityName: 'system' });
|
|
87
|
-
const existingSystem = await service.read(new BackendSystemKey({ url: params.url, client: params.client }));
|
|
88
|
-
if (existingSystem) {
|
|
89
|
-
const clientSuffix = params.client ? ` (client ${params.client})` : '';
|
|
90
|
-
logger.error(`System '${params.url}'${clientSuffix} already exists. Use 'update system' to update it.`);
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
replaceEnvVariables(params);
|
|
94
|
-
const system = new BackendSystem({
|
|
149
|
+
const config = await promptForSystemConfig({
|
|
95
150
|
name: params.name,
|
|
96
151
|
url: params.url,
|
|
97
152
|
client: params.client,
|
|
@@ -101,8 +156,38 @@ async function addSystem(params) {
|
|
|
101
156
|
username: params.username,
|
|
102
157
|
password: params.password
|
|
103
158
|
});
|
|
159
|
+
replaceEnvVariables(config);
|
|
160
|
+
if (!validateSystemConfig(config, logger)) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const service = await getService({ entityName: 'system' });
|
|
164
|
+
if (!(await checkForDuplicates(config, service, logger))) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const shouldSave = await checkConnectionOrPrompt({
|
|
168
|
+
url: config.url,
|
|
169
|
+
client: config.client,
|
|
170
|
+
systemType: config.systemType,
|
|
171
|
+
authenticationType: config.authenticationType,
|
|
172
|
+
username: config.username,
|
|
173
|
+
password: config.password
|
|
174
|
+
}, params.skipCheck || false);
|
|
175
|
+
if (!shouldSave) {
|
|
176
|
+
logger.info('System was not saved.');
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const system = new BackendSystem({
|
|
180
|
+
name: config.name,
|
|
181
|
+
url: config.url,
|
|
182
|
+
client: config.client,
|
|
183
|
+
systemType: config.systemType,
|
|
184
|
+
authenticationType: config.authenticationType,
|
|
185
|
+
connectionType: config.connectionType,
|
|
186
|
+
username: config.username,
|
|
187
|
+
password: config.password
|
|
188
|
+
});
|
|
104
189
|
await service.write(system);
|
|
105
|
-
logger.info(`System '${
|
|
190
|
+
logger.info(`System '${config.name}' added.`);
|
|
106
191
|
}
|
|
107
192
|
catch (error) {
|
|
108
193
|
logger.error(error.message);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isAppStudio } from '@sap-ux/btp-utils';
|
|
2
2
|
import { getService, BackendSystemKey } from '@sap-ux/store';
|
|
3
3
|
import { getLogger } from '../../tracing/index.js';
|
|
4
|
+
import { promptForSystemIdentifier, promptForRemoveConfirmation } from '../utils/system-prompts.js';
|
|
4
5
|
/**
|
|
5
6
|
* Add the "remove system" subcommand to a passed command.
|
|
6
7
|
* Removes a saved backend system from the store (~/.fioritools) and deletes its credentials from the OS keychain.
|
|
@@ -12,11 +13,13 @@ export function addSystemRemoveCommand(cmd) {
|
|
|
12
13
|
.description(`Remove a saved back-end system from the saved system store (\`~/.fioritools\`). Also deletes any stored credentials in the OS keychain.\n
|
|
13
14
|
Example:
|
|
14
15
|
\`npx --yes @sap-ux/create@latest remove system --url https://my-sap.example.com\`
|
|
15
|
-
\`npx --yes @sap-ux/create@latest remove system --url https://my-sap.example.com --client 100
|
|
16
|
-
|
|
16
|
+
\`npx --yes @sap-ux/create@latest remove system --url https://my-sap.example.com --client 100\`
|
|
17
|
+
\`npx --yes @sap-ux/create@latest remove system\` (interactive mode)`)
|
|
18
|
+
.option('--url <string>', 'URL of the backend system to remove')
|
|
17
19
|
.option('--client <string>', 'SAP client number (optional)')
|
|
20
|
+
.option('--force', 'Skip confirmation prompt')
|
|
18
21
|
.action(async (options) => {
|
|
19
|
-
await removeSystem(options.url, options.client);
|
|
22
|
+
await removeSystem(options.url, options.client, !!options.force);
|
|
20
23
|
});
|
|
21
24
|
}
|
|
22
25
|
/**
|
|
@@ -24,21 +27,32 @@ Example:
|
|
|
24
27
|
*
|
|
25
28
|
* @param url - URL of the system to remove
|
|
26
29
|
* @param client - optional SAP client
|
|
30
|
+
* @param force - skip confirmation prompt
|
|
27
31
|
*/
|
|
28
|
-
async function removeSystem(url, client) {
|
|
32
|
+
async function removeSystem(url, client, force) {
|
|
29
33
|
const logger = getLogger();
|
|
30
34
|
try {
|
|
31
35
|
if (isAppStudio()) {
|
|
32
36
|
logger.error('System management using the CLI is not supported in SAP Business Application Studio. Use the built-in system management instead.');
|
|
33
37
|
return;
|
|
34
38
|
}
|
|
39
|
+
// Prompt for system identifier if not provided
|
|
40
|
+
const identifier = await promptForSystemIdentifier({ url, client });
|
|
35
41
|
const service = await getService({ entityName: 'system' });
|
|
36
|
-
const key = new BackendSystemKey({ url, client });
|
|
42
|
+
const key = new BackendSystemKey({ url: identifier.url, client: identifier.client });
|
|
37
43
|
const system = await service.read(key);
|
|
38
44
|
if (!system) {
|
|
39
45
|
logger.error(`System not found: ${key.getId()}`);
|
|
40
46
|
return;
|
|
41
47
|
}
|
|
48
|
+
// Prompt for confirmation unless --force
|
|
49
|
+
if (!force) {
|
|
50
|
+
const confirmed = await promptForRemoveConfirmation(system.name);
|
|
51
|
+
if (!confirmed) {
|
|
52
|
+
logger.info('Remove cancelled.');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
42
56
|
const deleted = await service.delete(system);
|
|
43
57
|
if (deleted) {
|
|
44
58
|
logger.info(`System '${system.name}' removed.`);
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { isAppStudio } from '@sap-ux/btp-utils';
|
|
2
|
-
import { getService, BackendSystemKey } from '@sap-ux/store';
|
|
2
|
+
import { getService, BackendSystemKey, isSystemNameInUse } from '@sap-ux/store';
|
|
3
3
|
import { replaceEnvVariables } from '@sap-ux/ui5-config';
|
|
4
4
|
import { config as loadEnvConfig } from 'dotenv';
|
|
5
5
|
import { getLogger } from '../../tracing/index.js';
|
|
6
|
+
import { promptForSystemIdentifier, promptForUpdateFields, promptForFieldUpdates } from '../utils/system-prompts.js';
|
|
7
|
+
import { checkConnectionOrPrompt } from '../utils/system-connection.js';
|
|
6
8
|
/**
|
|
7
9
|
* Add the "update system" subcommand to a passed command.
|
|
8
10
|
* Updates an existing backend system in the saved systems store (~/.fioritools).
|
|
@@ -16,13 +18,15 @@ export function addSystemUpdateCommand(cmd) {
|
|
|
16
18
|
|
|
17
19
|
Example:
|
|
18
20
|
\`npx --yes @sap-ux/create@latest update system --url https://my-sap.example.com --name "New Name"\`
|
|
19
|
-
\`npx --yes @sap-ux/create@latest update system --url https://my-sap.example.com --client 100 --username newuser
|
|
20
|
-
|
|
21
|
+
\`npx --yes @sap-ux/create@latest update system --url https://my-sap.example.com --client 100 --username newuser\`
|
|
22
|
+
\`npx --yes @sap-ux/create@latest update system\` (interactive mode)`)
|
|
23
|
+
.option('--url <string>', 'URL of the backend system to update')
|
|
21
24
|
.option('--client <string>', 'SAP client number to identify the system (optional)')
|
|
22
25
|
.option('--name <string>', 'New display name for the system')
|
|
23
26
|
.option('--username <string>', 'New username')
|
|
24
27
|
.option('--password <string>', "To avoid plain-text credentials in the shell's history, pass an env reference: --password env:MY_VAR")
|
|
25
28
|
.option('--clear-credentials', 'Remove stored credentials from the system')
|
|
29
|
+
.option('--skip-check', 'Skip connection verification before saving')
|
|
26
30
|
.action(async (options) => {
|
|
27
31
|
loadEnvConfig();
|
|
28
32
|
await updateSystem({
|
|
@@ -31,10 +35,100 @@ Example:
|
|
|
31
35
|
name: options.name,
|
|
32
36
|
username: options.username,
|
|
33
37
|
password: options.password,
|
|
34
|
-
clearCredentials: !!options.clearCredentials
|
|
38
|
+
clearCredentials: !!options.clearCredentials,
|
|
39
|
+
skipCheck: !!options.skipCheck
|
|
35
40
|
});
|
|
36
41
|
});
|
|
37
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Validates and builds patch record from explicit parameters.
|
|
45
|
+
*
|
|
46
|
+
* @param params - update parameters
|
|
47
|
+
* @param params.name - new display name
|
|
48
|
+
* @param params.username - new username
|
|
49
|
+
* @param params.password - new password
|
|
50
|
+
* @param params.clearCredentials - whether to clear credentials
|
|
51
|
+
* @param existing - existing system
|
|
52
|
+
* @param logger - logger instance
|
|
53
|
+
* @returns patch record or null if validation fails
|
|
54
|
+
*/
|
|
55
|
+
async function buildPatchFromParams(params, existing, logger) {
|
|
56
|
+
const patchRecord = {};
|
|
57
|
+
if (params.name !== undefined) {
|
|
58
|
+
if (params.name.trim().length === 0) {
|
|
59
|
+
logger.error('System name cannot be empty or whitespace-only.');
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const nameExists = await isSystemNameInUse(params.name);
|
|
63
|
+
// Allow keeping the same name (case-insensitive comparison)
|
|
64
|
+
const isSameName = existing.name.trim().toLowerCase() === params.name.trim().toLowerCase();
|
|
65
|
+
if (nameExists && !isSameName) {
|
|
66
|
+
logger.error(`A system with the name '${params.name}' already exists. Please choose a different name.`);
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
patchRecord.name = params.name;
|
|
70
|
+
}
|
|
71
|
+
if (params.clearCredentials) {
|
|
72
|
+
patchRecord.username = '';
|
|
73
|
+
patchRecord.password = '';
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
if (params.username !== undefined) {
|
|
77
|
+
patchRecord.username = params.username;
|
|
78
|
+
}
|
|
79
|
+
if (params.password !== undefined) {
|
|
80
|
+
patchRecord.password = params.password;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return patchRecord;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Determines the patch to apply to the system.
|
|
87
|
+
*
|
|
88
|
+
* @param params - update parameters
|
|
89
|
+
* @param params.name - new display name
|
|
90
|
+
* @param params.username - new username
|
|
91
|
+
* @param params.password - new password
|
|
92
|
+
* @param params.clearCredentials - whether to clear credentials
|
|
93
|
+
* @param existing - existing system
|
|
94
|
+
* @param logger - logger instance
|
|
95
|
+
* @returns patch record or null if cancelled/failed
|
|
96
|
+
*/
|
|
97
|
+
async function determinePatch(params, existing, logger) {
|
|
98
|
+
const hasExplicitUpdates = params.name !== undefined ||
|
|
99
|
+
params.username !== undefined ||
|
|
100
|
+
params.password !== undefined ||
|
|
101
|
+
params.clearCredentials;
|
|
102
|
+
if (hasExplicitUpdates) {
|
|
103
|
+
return await buildPatchFromParams(params, existing, logger);
|
|
104
|
+
}
|
|
105
|
+
const fieldsToUpdate = await promptForUpdateFields(existing);
|
|
106
|
+
return await promptForFieldUpdates(fieldsToUpdate, existing);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Verifies connection if credentials are being updated.
|
|
110
|
+
*
|
|
111
|
+
* @param patch - patch to apply
|
|
112
|
+
* @param existing - existing system
|
|
113
|
+
* @param params - update parameters
|
|
114
|
+
* @param params.clearCredentials - whether credentials are being cleared
|
|
115
|
+
* @param params.skipCheck - whether to skip connection check
|
|
116
|
+
* @returns true if should proceed, false otherwise
|
|
117
|
+
*/
|
|
118
|
+
async function verifyCredentialsUpdate(patch, existing, params) {
|
|
119
|
+
const updatingCredentials = patch.username !== undefined || patch.password !== undefined;
|
|
120
|
+
if (!updatingCredentials || params.clearCredentials) {
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
return await checkConnectionOrPrompt({
|
|
124
|
+
url: existing.url,
|
|
125
|
+
client: existing.client,
|
|
126
|
+
systemType: existing.systemType,
|
|
127
|
+
authenticationType: existing.authenticationType || 'basic',
|
|
128
|
+
username: patch.username ?? existing.username,
|
|
129
|
+
password: patch.password ?? existing.password
|
|
130
|
+
}, params.skipCheck || false);
|
|
131
|
+
}
|
|
38
132
|
/**
|
|
39
133
|
* Updates a backend system in the saved systems store.
|
|
40
134
|
*
|
|
@@ -45,6 +139,7 @@ Example:
|
|
|
45
139
|
* @param params.username - optional new username
|
|
46
140
|
* @param params.password - optional new password
|
|
47
141
|
* @param params.clearCredentials - if true, clears stored credentials
|
|
142
|
+
* @param params.skipCheck - skip connection verification
|
|
48
143
|
*/
|
|
49
144
|
async function updateSystem(params) {
|
|
50
145
|
const logger = getLogger();
|
|
@@ -53,33 +148,29 @@ async function updateSystem(params) {
|
|
|
53
148
|
logger.error('System management using the CLI is not supported in SAP Business Application Studio. Use the built-in system management instead.');
|
|
54
149
|
return;
|
|
55
150
|
}
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
151
|
+
const identifier = await promptForSystemIdentifier({
|
|
152
|
+
url: params.url,
|
|
153
|
+
client: params.client
|
|
154
|
+
});
|
|
155
|
+
const service = await getService({ entityName: 'system' });
|
|
156
|
+
const key = new BackendSystemKey({ url: identifier.url, client: identifier.client });
|
|
157
|
+
const existing = await service.read(key);
|
|
158
|
+
if (!existing) {
|
|
159
|
+
logger.error(`System not found: ${key.getId()}`);
|
|
160
|
+
return;
|
|
64
161
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
if (params.password !== undefined) {
|
|
70
|
-
patchRecord.password = params.password;
|
|
71
|
-
}
|
|
162
|
+
const patchRecord = await determinePatch(params, existing, logger);
|
|
163
|
+
if (!patchRecord) {
|
|
164
|
+
return;
|
|
72
165
|
}
|
|
73
|
-
|
|
166
|
+
replaceEnvVariables(patchRecord);
|
|
74
167
|
if (!Object.keys(patchRecord).length) {
|
|
75
168
|
logger.error('No fields to update. Provide at least one of: --name, --username, --password, --clear-credentials');
|
|
76
169
|
return;
|
|
77
170
|
}
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (!existing) {
|
|
82
|
-
logger.error(`System not found: ${key.getId()}`);
|
|
171
|
+
const patch = patchRecord;
|
|
172
|
+
if (!(await verifyCredentialsUpdate(patch, existing, params))) {
|
|
173
|
+
logger.info('System was not updated.');
|
|
83
174
|
return;
|
|
84
175
|
}
|
|
85
176
|
await service.partialUpdate(key, patch);
|
|
@@ -87,8 +178,6 @@ async function updateSystem(params) {
|
|
|
87
178
|
}
|
|
88
179
|
catch (error) {
|
|
89
180
|
logger.error(error.message);
|
|
90
|
-
// Log the full error object (including stack trace) at debug level so it
|
|
91
|
-
// is visible when --verbose / debug logging is enabled.
|
|
92
181
|
logger.debug(error);
|
|
93
182
|
}
|
|
94
183
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks connection to a backend system.
|
|
3
|
+
*
|
|
4
|
+
* @param config - System configuration to test
|
|
5
|
+
* @param config.url
|
|
6
|
+
* @param config.client
|
|
7
|
+
* @param config.systemType
|
|
8
|
+
* @param config.authenticationType
|
|
9
|
+
* @param config.username
|
|
10
|
+
* @param config.password
|
|
11
|
+
* @returns Connection check result with success status and optional error message
|
|
12
|
+
*/
|
|
13
|
+
export declare function checkSystemConnection(config: {
|
|
14
|
+
url: string;
|
|
15
|
+
client?: string;
|
|
16
|
+
systemType: string;
|
|
17
|
+
authenticationType: string;
|
|
18
|
+
username?: string;
|
|
19
|
+
password?: string;
|
|
20
|
+
}): Promise<{
|
|
21
|
+
success: boolean;
|
|
22
|
+
error?: string;
|
|
23
|
+
}>;
|
|
24
|
+
/**
|
|
25
|
+
* Checks connection to a backend system, or prompts user whether to save anyway if check fails.
|
|
26
|
+
* If skipCheck is true, always returns true without checking.
|
|
27
|
+
*
|
|
28
|
+
* @param config - System configuration to test
|
|
29
|
+
* @param config.url
|
|
30
|
+
* @param config.client
|
|
31
|
+
* @param config.systemType
|
|
32
|
+
* @param config.authenticationType
|
|
33
|
+
* @param config.username
|
|
34
|
+
* @param config.password
|
|
35
|
+
* @param skipCheck - If true, skip the connection check
|
|
36
|
+
* @returns True if connection succeeded or user chose to save anyway, false if user chose not to save
|
|
37
|
+
*/
|
|
38
|
+
export declare function checkConnectionOrPrompt(config: {
|
|
39
|
+
url: string;
|
|
40
|
+
client?: string;
|
|
41
|
+
systemType: string;
|
|
42
|
+
authenticationType: string;
|
|
43
|
+
username?: string;
|
|
44
|
+
password?: string;
|
|
45
|
+
}, skipCheck: boolean): Promise<boolean>;
|
|
46
|
+
//# sourceMappingURL=system-connection.d.ts.map
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import prompts from 'prompts';
|
|
2
|
+
import { getLogger } from '../../tracing/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Checks connection to a backend system.
|
|
5
|
+
*
|
|
6
|
+
* @param config - System configuration to test
|
|
7
|
+
* @param config.url
|
|
8
|
+
* @param config.client
|
|
9
|
+
* @param config.systemType
|
|
10
|
+
* @param config.authenticationType
|
|
11
|
+
* @param config.username
|
|
12
|
+
* @param config.password
|
|
13
|
+
* @returns Connection check result with success status and optional error message
|
|
14
|
+
*/
|
|
15
|
+
export async function checkSystemConnection(config) {
|
|
16
|
+
// Basic URL validation
|
|
17
|
+
try {
|
|
18
|
+
new URL(config.url);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return { success: false, error: `Invalid URL: ${config.url}` };
|
|
22
|
+
}
|
|
23
|
+
// For now, we just validate the URL format
|
|
24
|
+
// A real implementation would attempt to connect to the backend
|
|
25
|
+
// using the provided credentials and check if the system is reachable
|
|
26
|
+
return { success: true };
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Checks connection to a backend system, or prompts user whether to save anyway if check fails.
|
|
30
|
+
* If skipCheck is true, always returns true without checking.
|
|
31
|
+
*
|
|
32
|
+
* @param config - System configuration to test
|
|
33
|
+
* @param config.url
|
|
34
|
+
* @param config.client
|
|
35
|
+
* @param config.systemType
|
|
36
|
+
* @param config.authenticationType
|
|
37
|
+
* @param config.username
|
|
38
|
+
* @param config.password
|
|
39
|
+
* @param skipCheck - If true, skip the connection check
|
|
40
|
+
* @returns True if connection succeeded or user chose to save anyway, false if user chose not to save
|
|
41
|
+
*/
|
|
42
|
+
export async function checkConnectionOrPrompt(config, skipCheck) {
|
|
43
|
+
const logger = getLogger();
|
|
44
|
+
if (skipCheck) {
|
|
45
|
+
logger.info('Skipping connection check (--skip-check flag provided)');
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
logger.info('Verifying connection to backend system...');
|
|
49
|
+
const result = await checkSystemConnection(config);
|
|
50
|
+
if (result.success) {
|
|
51
|
+
logger.info('✓ Connection successful');
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
logger.warn(`Connection check failed: ${result.error || 'Unknown error'}`);
|
|
55
|
+
const answer = await prompts({
|
|
56
|
+
type: 'confirm',
|
|
57
|
+
name: 'saveAnyway',
|
|
58
|
+
message: 'Connection check failed. Save system anyway?',
|
|
59
|
+
initial: false
|
|
60
|
+
});
|
|
61
|
+
return answer.saveAnyway === true;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=system-connection.js.map
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { BackendSystem } from '@sap-ux/store';
|
|
2
|
+
/**
|
|
3
|
+
* Prompts for complete system configuration, filling in any missing fields.
|
|
4
|
+
*
|
|
5
|
+
* @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
|
|
14
|
+
* @returns Complete system configuration with all required fields
|
|
15
|
+
*/
|
|
16
|
+
export declare function promptForSystemConfig(partial: {
|
|
17
|
+
name?: string;
|
|
18
|
+
url?: string;
|
|
19
|
+
client?: string;
|
|
20
|
+
systemType?: string;
|
|
21
|
+
authenticationType?: string;
|
|
22
|
+
connectionType?: string;
|
|
23
|
+
username?: string;
|
|
24
|
+
password?: string;
|
|
25
|
+
}): Promise<{
|
|
26
|
+
name: string;
|
|
27
|
+
url: string;
|
|
28
|
+
client?: string;
|
|
29
|
+
systemType: string;
|
|
30
|
+
authenticationType: string;
|
|
31
|
+
connectionType: string;
|
|
32
|
+
username?: string;
|
|
33
|
+
password?: string;
|
|
34
|
+
}>;
|
|
35
|
+
/**
|
|
36
|
+
* Prompts for system identifier (URL and optional client).
|
|
37
|
+
*
|
|
38
|
+
* @param partial - Partial identifier with some fields already provided
|
|
39
|
+
* @param partial.url
|
|
40
|
+
* @param partial.client
|
|
41
|
+
* @returns System identifier with URL and optional client
|
|
42
|
+
*/
|
|
43
|
+
export declare function promptForSystemIdentifier(partial: {
|
|
44
|
+
url?: string;
|
|
45
|
+
client?: string;
|
|
46
|
+
}): Promise<{
|
|
47
|
+
url: string;
|
|
48
|
+
client?: string;
|
|
49
|
+
}>;
|
|
50
|
+
/**
|
|
51
|
+
* Prompts user to select which fields they want to update.
|
|
52
|
+
*
|
|
53
|
+
* @param existing - Existing system configuration
|
|
54
|
+
* @returns Array of field names to update
|
|
55
|
+
*/
|
|
56
|
+
export declare function promptForUpdateFields(existing: BackendSystem): Promise<string[]>;
|
|
57
|
+
/**
|
|
58
|
+
* Prompts for new values for the specified fields.
|
|
59
|
+
*
|
|
60
|
+
* @param fields - Array of field names to prompt for
|
|
61
|
+
* @param existing - Existing system configuration
|
|
62
|
+
* @returns Object with new values for the specified fields
|
|
63
|
+
*/
|
|
64
|
+
export declare function promptForFieldUpdates(fields: string[], existing: BackendSystem): Promise<Record<string, unknown>>;
|
|
65
|
+
/**
|
|
66
|
+
* Prompts for confirmation before removing a system.
|
|
67
|
+
*
|
|
68
|
+
* @param systemName - Name or identifier of the system to remove
|
|
69
|
+
* @returns True if user confirms, false otherwise
|
|
70
|
+
*/
|
|
71
|
+
export declare function promptForRemoveConfirmation(systemName: string): Promise<boolean>;
|
|
72
|
+
//# sourceMappingURL=system-prompts.d.ts.map
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import prompts from 'prompts';
|
|
2
|
+
import { SystemType, AuthenticationType, ConnectionType, isSystemNameInUse } from '@sap-ux/store';
|
|
3
|
+
import { validateClient } from '@sap-ux/project-input-validator';
|
|
4
|
+
/**
|
|
5
|
+
* Checks if a string is empty or contains only whitespace.
|
|
6
|
+
*
|
|
7
|
+
* @param value - The value to check
|
|
8
|
+
* @returns true if empty, false otherwise
|
|
9
|
+
*/
|
|
10
|
+
function isEmptyString(value) {
|
|
11
|
+
return !value || !/\S/.test(value);
|
|
12
|
+
}
|
|
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
|
+
/**
|
|
29
|
+
* Validates that a string is not empty after trimming whitespace.
|
|
30
|
+
*
|
|
31
|
+
* @param value - The value to validate
|
|
32
|
+
* @returns True if valid, error message otherwise
|
|
33
|
+
*/
|
|
34
|
+
function validateNonEmpty(value) {
|
|
35
|
+
return isEmptyString(value) ? 'This field is required and cannot be empty' : true;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Validates that a URL is in correct format.
|
|
39
|
+
*
|
|
40
|
+
* @param value - The URL to validate
|
|
41
|
+
* @returns True if valid, error message otherwise
|
|
42
|
+
*/
|
|
43
|
+
function validateUrlField(value) {
|
|
44
|
+
const nonEmptyCheck = validateNonEmpty(value);
|
|
45
|
+
if (nonEmptyCheck !== true) {
|
|
46
|
+
return nonEmptyCheck;
|
|
47
|
+
}
|
|
48
|
+
return isValidUrl(value) ? true : 'Please enter a valid URL (e.g., https://my-system.example.com)';
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Validates that a client number is in correct format (empty or 3-digit number).
|
|
52
|
+
*
|
|
53
|
+
* @param value - The client number to validate
|
|
54
|
+
* @returns True if valid, error message otherwise
|
|
55
|
+
*/
|
|
56
|
+
function validateClientField(value) {
|
|
57
|
+
const result = validateClient(value);
|
|
58
|
+
// validateClient returns true | string
|
|
59
|
+
return result === true ? true : String(result);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Validates that a system name is unique (not already in use).
|
|
63
|
+
*
|
|
64
|
+
* @param value - The system name to validate
|
|
65
|
+
* @param excludeSystem - Optional system to exclude from check (for updates)
|
|
66
|
+
* @returns True if valid, error message otherwise
|
|
67
|
+
*/
|
|
68
|
+
async function validateSystemNameUniqueness(value, excludeSystem) {
|
|
69
|
+
const nonEmptyCheck = validateNonEmpty(value);
|
|
70
|
+
if (nonEmptyCheck !== true) {
|
|
71
|
+
return nonEmptyCheck;
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
const isTaken = await isSystemNameInUse(value);
|
|
75
|
+
// Allow keeping the same name when updating (case-insensitive)
|
|
76
|
+
if (excludeSystem && isTaken) {
|
|
77
|
+
const isSameName = excludeSystem.name.trim().toLowerCase() === value.trim().toLowerCase();
|
|
78
|
+
if (isSameName) {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
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.`;
|
|
111
|
+
}
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
// Catch and convert service errors to validation messages to prevent duplicate names
|
|
116
|
+
console.error('Error checking system name uniqueness:', error);
|
|
117
|
+
return 'Unable to check system name uniqueness. Please try again.';
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Prompts for complete system configuration, filling in any missing fields.
|
|
122
|
+
*
|
|
123
|
+
* @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
|
|
132
|
+
* @returns Complete system configuration with all required fields
|
|
133
|
+
*/
|
|
134
|
+
export async function promptForSystemConfig(partial) {
|
|
135
|
+
const questions = [];
|
|
136
|
+
if (!partial.name) {
|
|
137
|
+
questions.push({
|
|
138
|
+
type: 'text',
|
|
139
|
+
name: 'name',
|
|
140
|
+
message: 'System name (display name):',
|
|
141
|
+
validate: (value) => validateSystemNameUniqueness(value)
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
if (!partial.url) {
|
|
145
|
+
questions.push({
|
|
146
|
+
type: 'text',
|
|
147
|
+
name: 'url',
|
|
148
|
+
message: 'System URL:',
|
|
149
|
+
validate: validateUrlField
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
if (partial.client === undefined) {
|
|
153
|
+
questions.push({
|
|
154
|
+
type: 'text',
|
|
155
|
+
name: 'client',
|
|
156
|
+
message: 'SAP client (optional, press Enter to skip):',
|
|
157
|
+
validate: validateClientField
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
if (!partial.systemType) {
|
|
161
|
+
questions.push({
|
|
162
|
+
type: 'select',
|
|
163
|
+
name: 'systemType',
|
|
164
|
+
message: 'System type:',
|
|
165
|
+
choices: Object.values(SystemType).map((type) => ({ title: type, value: type }))
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
if (!partial.authenticationType) {
|
|
169
|
+
questions.push({
|
|
170
|
+
type: 'select',
|
|
171
|
+
name: 'authenticationType',
|
|
172
|
+
message: 'Authentication type:',
|
|
173
|
+
choices: Object.values(AuthenticationType).map((type) => ({ title: type, value: type }))
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
if (!partial.connectionType) {
|
|
177
|
+
questions.push({
|
|
178
|
+
type: 'select',
|
|
179
|
+
name: 'connectionType',
|
|
180
|
+
message: 'Connection type:',
|
|
181
|
+
choices: Object.values(ConnectionType).map((type) => ({ title: type, value: type }))
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
if (partial.username === undefined) {
|
|
185
|
+
questions.push({
|
|
186
|
+
type: 'text',
|
|
187
|
+
name: 'username',
|
|
188
|
+
message: 'Username (optional, press Enter to skip):'
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
if (partial.password === undefined) {
|
|
192
|
+
questions.push({
|
|
193
|
+
type: 'password',
|
|
194
|
+
name: 'password',
|
|
195
|
+
message: 'Password (optional, press Enter to skip):'
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
const answers = questions.length > 0 ? await prompts(questions) : {};
|
|
199
|
+
return {
|
|
200
|
+
name: partial.name || answers.name,
|
|
201
|
+
url: partial.url || answers.url,
|
|
202
|
+
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)
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Prompts for system identifier (URL and optional client).
|
|
212
|
+
*
|
|
213
|
+
* @param partial - Partial identifier with some fields already provided
|
|
214
|
+
* @param partial.url
|
|
215
|
+
* @param partial.client
|
|
216
|
+
* @returns System identifier with URL and optional client
|
|
217
|
+
*/
|
|
218
|
+
export async function promptForSystemIdentifier(partial) {
|
|
219
|
+
const questions = [];
|
|
220
|
+
if (!partial.url) {
|
|
221
|
+
questions.push({
|
|
222
|
+
type: 'text',
|
|
223
|
+
name: 'url',
|
|
224
|
+
message: 'System URL:',
|
|
225
|
+
validate: validateUrlField
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
if (partial.client === undefined) {
|
|
229
|
+
questions.push({
|
|
230
|
+
type: 'text',
|
|
231
|
+
name: 'client',
|
|
232
|
+
message: 'SAP client (optional, press Enter to skip):',
|
|
233
|
+
validate: validateClientField
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
const answers = questions.length > 0 ? await prompts(questions) : {};
|
|
237
|
+
return {
|
|
238
|
+
url: partial.url || answers.url,
|
|
239
|
+
client: partial.client ?? (answers.client || undefined)
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Prompts user to select which fields they want to update.
|
|
244
|
+
*
|
|
245
|
+
* @param existing - Existing system configuration
|
|
246
|
+
* @returns Array of field names to update
|
|
247
|
+
*/
|
|
248
|
+
export async function promptForUpdateFields(existing) {
|
|
249
|
+
const answer = await prompts({
|
|
250
|
+
type: 'multiselect',
|
|
251
|
+
name: 'fields',
|
|
252
|
+
message: 'Select fields to update:',
|
|
253
|
+
choices: [
|
|
254
|
+
{ title: `Name (current: ${existing.name})`, value: 'name' },
|
|
255
|
+
{ title: `Username (current: ${existing.username || '(none)'})`, value: 'username' },
|
|
256
|
+
{ title: 'Password', value: 'password' }
|
|
257
|
+
],
|
|
258
|
+
min: 1
|
|
259
|
+
});
|
|
260
|
+
if (!answer.fields || answer.fields.length === 0) {
|
|
261
|
+
throw new Error('At least one field must be selected');
|
|
262
|
+
}
|
|
263
|
+
return answer.fields;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Prompts for new values for the specified fields.
|
|
267
|
+
*
|
|
268
|
+
* @param fields - Array of field names to prompt for
|
|
269
|
+
* @param existing - Existing system configuration
|
|
270
|
+
* @returns Object with new values for the specified fields
|
|
271
|
+
*/
|
|
272
|
+
export async function promptForFieldUpdates(fields, existing) {
|
|
273
|
+
const questions = fields
|
|
274
|
+
.map((field) => {
|
|
275
|
+
switch (field) {
|
|
276
|
+
case 'name':
|
|
277
|
+
return {
|
|
278
|
+
type: 'text',
|
|
279
|
+
name: 'name',
|
|
280
|
+
message: 'New system name:',
|
|
281
|
+
initial: existing.name,
|
|
282
|
+
validate: (value) => validateSystemNameUniquenessForUpdate(value, existing)
|
|
283
|
+
};
|
|
284
|
+
case 'username':
|
|
285
|
+
return {
|
|
286
|
+
type: 'text',
|
|
287
|
+
name: 'username',
|
|
288
|
+
message: 'New username:',
|
|
289
|
+
initial: existing.username || '',
|
|
290
|
+
validate: validateNonEmpty
|
|
291
|
+
};
|
|
292
|
+
case 'password':
|
|
293
|
+
return {
|
|
294
|
+
type: 'password',
|
|
295
|
+
name: 'password',
|
|
296
|
+
message: 'New password:',
|
|
297
|
+
validate: validateNonEmpty
|
|
298
|
+
};
|
|
299
|
+
default:
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
})
|
|
303
|
+
.filter((q) => q !== null);
|
|
304
|
+
return await prompts(questions);
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Prompts for confirmation before removing a system.
|
|
308
|
+
*
|
|
309
|
+
* @param systemName - Name or identifier of the system to remove
|
|
310
|
+
* @returns True if user confirms, false otherwise
|
|
311
|
+
*/
|
|
312
|
+
export async function promptForRemoveConfirmation(systemName) {
|
|
313
|
+
const answer = await prompts({
|
|
314
|
+
type: 'confirm',
|
|
315
|
+
name: 'confirm',
|
|
316
|
+
message: `Are you sure you want to remove system '${systemName}'?`,
|
|
317
|
+
initial: false
|
|
318
|
+
});
|
|
319
|
+
return answer.confirm === true;
|
|
320
|
+
}
|
|
321
|
+
//# sourceMappingURL=system-prompts.js.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sap-ux/create",
|
|
3
3
|
"description": "SAP Fiori tools module to add or remove features",
|
|
4
|
-
"version": "1.0
|
|
4
|
+
"version": "1.1.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "https://github.com/SAP/open-ux-tools.git",
|
|
@@ -33,23 +33,25 @@
|
|
|
33
33
|
"mem-fs": "2.1.0",
|
|
34
34
|
"mem-fs-editor": "9.4.0",
|
|
35
35
|
"prompts": "2.4.2",
|
|
36
|
-
"@sap-ux/abap-deploy-config-inquirer": "2.0.
|
|
37
|
-
"@sap-ux/abap-deploy-config-writer": "1.0.
|
|
38
|
-
"@sap-ux/adp-tooling": "1.0.
|
|
39
|
-
"@sap-ux/btp-utils": "2.0.
|
|
40
|
-
"@sap-ux/app-config-writer": "1.1.
|
|
41
|
-
"@sap-ux/cap-config-writer": "1.0.
|
|
36
|
+
"@sap-ux/abap-deploy-config-inquirer": "2.0.32",
|
|
37
|
+
"@sap-ux/abap-deploy-config-writer": "1.0.19",
|
|
38
|
+
"@sap-ux/adp-tooling": "1.0.39",
|
|
39
|
+
"@sap-ux/btp-utils": "2.0.6",
|
|
40
|
+
"@sap-ux/app-config-writer": "1.1.14",
|
|
41
|
+
"@sap-ux/cap-config-writer": "1.0.27",
|
|
42
|
+
"@sap-ux/inquirer-common": "1.0.28",
|
|
42
43
|
"@sap-ux/logger": "1.0.3",
|
|
43
|
-
"@sap-ux/mockserver-config-writer": "1.0.
|
|
44
|
-
"@sap-ux/odata-service-writer": "1.0.
|
|
45
|
-
"@sap-ux/preview-middleware": "1.0.
|
|
46
|
-
"@sap-ux/project-access": "2.1.
|
|
47
|
-
"@sap-ux/
|
|
48
|
-
"@sap-ux/
|
|
49
|
-
"@sap-ux/
|
|
50
|
-
"@sap-ux/
|
|
51
|
-
"@sap-ux/
|
|
52
|
-
"@sap-ux/
|
|
44
|
+
"@sap-ux/mockserver-config-writer": "1.0.14",
|
|
45
|
+
"@sap-ux/odata-service-writer": "1.0.16",
|
|
46
|
+
"@sap-ux/preview-middleware": "1.0.49",
|
|
47
|
+
"@sap-ux/project-access": "2.1.9",
|
|
48
|
+
"@sap-ux/project-input-validator": "1.0.14",
|
|
49
|
+
"@sap-ux/system-access": "1.0.10",
|
|
50
|
+
"@sap-ux/store": "2.0.6",
|
|
51
|
+
"@sap-ux/ui5-config": "1.0.6",
|
|
52
|
+
"@sap-ux/flp-config-inquirer": "1.0.39",
|
|
53
|
+
"@sap-ux/nodejs-utils": "1.0.9",
|
|
54
|
+
"@sap-ux/axios-extension": "2.0.8"
|
|
53
55
|
},
|
|
54
56
|
"devDependencies": {
|
|
55
57
|
"@jest/globals": "30.4.1",
|
|
@@ -58,8 +60,8 @@
|
|
|
58
60
|
"@types/mem-fs": "1.1.2",
|
|
59
61
|
"@types/mem-fs-editor": "7.0.1",
|
|
60
62
|
"@types/prompts": "2.4.9",
|
|
61
|
-
"@sap-ux/inquirer-common": "1.0.
|
|
62
|
-
"@sap-ux/store": "2.0.
|
|
63
|
+
"@sap-ux/inquirer-common": "1.0.28",
|
|
64
|
+
"@sap-ux/store": "2.0.6"
|
|
63
65
|
},
|
|
64
66
|
"scripts": {
|
|
65
67
|
"build": "tsc --build && node ./scripts/generate-readme.js && node ./scripts/generate-skill.js",
|