@sap-ux/create 1.4.18 → 1.5.1

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 CHANGED
@@ -581,3 +581,18 @@ Options:
581
581
  - `--client <string>` - SAP client number (optional).
582
582
  - `--json` - Output as JSON, which is useful for automation and MCP integrations.
583
583
 
584
+ --------------------------------
585
+
586
+ ## [`migrate`](#migrate)
587
+
588
+ Migrate legacy WebIDE Fiori project to modern Fiori tools format. In BAS, a destination is required. Outside BAS, hostname and client are required; destination is optional.
589
+
590
+ Options:
591
+ - `-d, --destination <name>` - SAP System destination name (required in BAS)
592
+ - `-s, --sap-system-name <name>` - SAP System name (alias for destination)
593
+ - `-H, --hostname <host>` - Backend hostname (required outside BAS unless resolved from a saved system)
594
+ - `-c, --client <client>` - SAP Client (required outside BAS unless resolved from destination or saved system)
595
+ - `-u, --ui5-version <version>` - UI5 version (defaults to source project version)
596
+ - `-f, --force` - Force migration even if project is already a Fiori tools project
597
+ - `-n, --skip-install` - Skip the `npm install` step after migration
598
+
@@ -48,7 +48,9 @@ async function addCdsPluginUi5(basePath, simulate, skipInstall) {
48
48
  }
49
49
  else {
50
50
  logger.debug('Running npm install command');
51
- runNpmInstallCommand(basePath);
51
+ runNpmInstallCommand(basePath, [], { logger }).catch(() => {
52
+ // Error already logged by runNpmInstallCommand
53
+ });
52
54
  }
53
55
  }
54
56
  }
@@ -48,7 +48,9 @@ async function addEslintConfig(basePath, simulate, config, skipInstall = false)
48
48
  }
49
49
  else {
50
50
  logger.info(`Executing \`npm install\`.`);
51
- runNpmInstallCommand(basePath, undefined, { logger });
51
+ runNpmInstallCommand(basePath, undefined, { logger }).catch(() => {
52
+ // Error already logged by runNpmInstallCommand
53
+ });
52
54
  }
53
55
  }
54
56
  }
@@ -62,7 +62,11 @@ async function addMockserverConfig(basePath, simulate, skipInstall, interactive)
62
62
  }
63
63
  else {
64
64
  logger.debug('Running npm install command');
65
- runNpmInstallCommand(basePath, ['--save-dev', '@sap-ux/ui5-middleware-fe-mockserver']);
65
+ runNpmInstallCommand(basePath, ['--save-dev', '@sap-ux/ui5-middleware-fe-mockserver'], {
66
+ logger
67
+ }).catch(() => {
68
+ // Error already logged by runNpmInstallCommand
69
+ });
66
70
  }
67
71
  }
68
72
  }
@@ -60,7 +60,9 @@ async function convertEslintConfig(basePath, simulate, config, skipInstall = fal
60
60
  });
61
61
  logger.info('npm uninstall completed successfully.');
62
62
  logger.info(`Executing \`npm install\`.`);
63
- runNpmInstallCommand(basePath, undefined, { logger });
63
+ runNpmInstallCommand(basePath, undefined, { logger }).catch(() => {
64
+ // Error already logged by runNpmInstallCommand
65
+ });
64
66
  }
65
67
  catch (error) {
66
68
  logger.error(`npm command failed. '${error.message}'`);
@@ -57,7 +57,7 @@ async function generateAdaptationProject(basePath, defaults, useDefaults, simula
57
57
  if (!simulate) {
58
58
  await new Promise((resolve) => fs.commit(resolve));
59
59
  if (!skipInstall) {
60
- runNpmInstallCommand(basePath);
60
+ await runNpmInstallCommand(basePath);
61
61
  logger.info('Executed npm install');
62
62
  }
63
63
  }
@@ -1,8 +1,8 @@
1
1
  import { isAppStudio } from '@sap-ux/btp-utils';
2
- import { getService, BackendSystemKey } from '@sap-ux/store';
3
- import { NullTransport, ToolsLogger } from '@sap-ux/logger';
2
+ import { BackendSystemKey } from '@sap-ux/store';
4
3
  import { getLogger } from '../../tracing/index.js';
5
4
  import { findSystemByUrl } from '../utils/system-lookup.js';
5
+ import { getSystemService, toPublicView } from '../utils/system-store.js';
6
6
  /**
7
7
  * Add the "get system" subcommand to a passed command.
8
8
  * Retrieves details of a saved backend system identified by its URL and optional client.
@@ -38,14 +38,7 @@ async function getSystem(url, client, asJson) {
38
38
  logger.error('System management using the CLI is not supported in SAP Business Application Studio. Use the built-in system management instead.');
39
39
  return;
40
40
  }
41
- // When emitting JSON, pass a silent logger to getService so that
42
- // @sap-ux/store diagnostic messages do not appear on stdout before the
43
- // JSON payload and break piped consumers.
44
- const storeLogger = asJson ? new ToolsLogger({ transports: [new NullTransport()] }) : logger;
45
- const service = await getService({
46
- entityName: 'system',
47
- logger: storeLogger
48
- });
41
+ const service = await getSystemService(asJson);
49
42
  // Use smart lookup to handle client mismatch scenarios
50
43
  const system = await findSystemByUrl(url, client, service);
51
44
  if (!system) {
@@ -53,16 +46,7 @@ async function getSystem(url, client, asJson) {
53
46
  logger.error(`System not found: ${key.getId()}`);
54
47
  return;
55
48
  }
56
- const publicView = {
57
- name: system.name,
58
- url: system.url,
59
- client: system.client,
60
- systemType: system.systemType,
61
- authenticationType: system.authenticationType,
62
- connectionType: system.connectionType,
63
- userDisplayName: system.userDisplayName,
64
- hasSensitiveData: system.hasSensitiveData
65
- };
49
+ const publicView = toPublicView(system);
66
50
  if (asJson) {
67
51
  // Use process.stdout.write so the output is pure JSON with no logger
68
52
  // prefixes or timestamps — required for machine consumption and piping.
package/dist/cli/index.js CHANGED
@@ -13,6 +13,7 @@ import { getConvertCommands } from './convert/index.js';
13
13
  import { getListCommands } from './list/index.js';
14
14
  import { getGetCommands } from './get/index.js';
15
15
  import { getUpdateCommands } from './update/index.js';
16
+ import { getMigrateCommand } from './migrate/index.js';
16
17
  /*
17
18
  * We've chosen 'commander' over 'minimist' and 'yargs' for this CLI implementation. Reasons:
18
19
  * (if it still up: https://npmtrends.com/commander-vs-minimist-vs-yargs)
@@ -34,7 +35,7 @@ export async function handleCreateFioriCommand(argv) {
34
35
  // Initialize i18n before executing commands
35
36
  await initI18n();
36
37
  const program = getCommanderProgram();
37
- program.parse(argv);
38
+ await program.parseAsync(argv);
38
39
  }
39
40
  catch (error) {
40
41
  // Commander report meaningful error messages, output only if log level is set to debug
@@ -116,6 +117,8 @@ function getCommanderProgram() {
116
117
  Usage: \`npx --yes @sap-ux/create@latest get [subcommand] [options]\`
117
118
  The available subcommands are: ${getFeatureSummary(getCommands.commands)}\n`);
118
119
  program.addCommand(getCommands);
120
+ // Handler for create-fiori migrate ..
121
+ program.addCommand(getMigrateCommand());
119
122
  return program;
120
123
  }
121
124
  /**
@@ -1,7 +1,6 @@
1
1
  import { isAppStudio } from '@sap-ux/btp-utils';
2
- import { getService } from '@sap-ux/store';
3
- import { NullTransport, ToolsLogger } from '@sap-ux/logger';
4
2
  import { getLogger } from '../../tracing/index.js';
3
+ import { getSystemService, toPublicView } from '../utils/system-store.js';
5
4
  /**
6
5
  * Add the "list system" subcommand to a passed command.
7
6
  * Lists all backend systems saved in the store (~/.fioritools).
@@ -33,14 +32,7 @@ async function listSystems(asJson) {
33
32
  logger.error('System management using the CLI is not supported in SAP Business Application Studio. Use the built-in system management instead.');
34
33
  return;
35
34
  }
36
- // When emitting JSON, pass a silent logger to getService so that
37
- // @sap-ux/store diagnostic messages (e.g. "Using KeyStoreManager…") do
38
- // not appear on stdout before the JSON payload and break piped consumers.
39
- const storeLogger = asJson ? new ToolsLogger({ transports: [new NullTransport()] }) : logger;
40
- const service = await getService({
41
- entityName: 'system',
42
- logger: storeLogger
43
- });
35
+ const service = await getSystemService(asJson);
44
36
  // Pass no filter so all systems are returned regardless of connection type
45
37
  const systems = await service.getAll();
46
38
  if (asJson) {
@@ -65,24 +57,6 @@ async function listSystems(asJson) {
65
57
  logger.debug(error);
66
58
  }
67
59
  }
68
- /**
69
- * Returns a view of a system with only non-sensitive fields.
70
- *
71
- * @param system - the backend system
72
- * @returns non-sensitive system data
73
- */
74
- function toPublicView(system) {
75
- return {
76
- name: system.name,
77
- url: system.url,
78
- client: system.client,
79
- systemType: system.systemType,
80
- authenticationType: system.authenticationType,
81
- connectionType: system.connectionType,
82
- userDisplayName: system.userDisplayName,
83
- hasSensitiveData: system.hasSensitiveData
84
- };
85
- }
86
60
  /**
87
61
  * Prints a single system to the logger in human-readable format.
88
62
  *
@@ -0,0 +1,8 @@
1
+ import { Command } from 'commander';
2
+ /**
3
+ * Returns the 'migrate' command for the commander program.
4
+ *
5
+ * @returns - commander command
6
+ */
7
+ export declare function getMigrateCommand(): Command;
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,618 @@
1
+ import { Command } from 'commander';
2
+ import { resolve, basename } from 'node:path';
3
+ import { existsSync, readFileSync } from 'node:fs';
4
+ import prompts from 'prompts';
5
+ import { ProjectAccess, ProjectMigrator, initI18n as initMigrationI18n } from '@sap-ux/fiori-migration-writer';
6
+ import { DestinationProxyType, isAppStudio, listDestinations } from '@sap-ux/btp-utils';
7
+ import { isInternalFeaturesSettingEnabled } from '@sap-ux/feature-toggle';
8
+ import { getService } from '@sap-ux/store';
9
+ import { execNpmCommand } from '@sap-ux/project-access';
10
+ import { validateClient as validateClientInput, validateSpecialChars as validateSpecialCharsInput } from '@sap-ux/project-input-validator';
11
+ import { getLogger } from '../../tracing/index.js';
12
+ import { findSystemByUrl } from '../utils/system-lookup.js';
13
+ import { t } from '../../i18n.js';
14
+ import { writeMigrationLog, getMigrationLogsDirectory } from './traceability.js';
15
+ /**
16
+ * Helper to convert validator result (boolean | string) to throw-on-error format.
17
+ * Project input validators return true or error message; this converts to exception.
18
+ *
19
+ * @param validatorResult - result from @sap-ux/project-input-validator function
20
+ * @param value - the validated value to return on success
21
+ * @returns validated value
22
+ * @throws Error if validation failed
23
+ */
24
+ function enforceValidation(validatorResult, value) {
25
+ if (validatorResult === true) {
26
+ return value;
27
+ }
28
+ // If not true, must be false or an error message string
29
+ throw new Error(typeof validatorResult === 'string' ? validatorResult : 'Validation failed');
30
+ }
31
+ /**
32
+ * Validate a path to prevent directory traversal attacks.
33
+ *
34
+ * @param path - path to validate
35
+ * @returns validated absolute path
36
+ * @throws Error if path contains unsafe characters or doesn't exist
37
+ */
38
+ function validatePath(path) {
39
+ const resolved = resolve(path);
40
+ // Reject control characters and shell metacharacters
41
+ if (/[\0\r\n`$|&;<>]/.test(resolved)) {
42
+ throw new Error(t('migrate.validation.pathUnsafe'));
43
+ }
44
+ // Ensure it's an existing directory
45
+ if (!existsSync(resolved)) {
46
+ throw new Error(t('migrate.validation.pathNotExists', { path }));
47
+ }
48
+ return resolved;
49
+ }
50
+ /**
51
+ * Validate a destination/system name to prevent injection attacks.
52
+ * Uses @sap-ux/project-input-validator for consistent validation.
53
+ *
54
+ * @param destination - destination name to validate
55
+ * @returns validated destination
56
+ * @throws Error if destination contains invalid characters
57
+ */
58
+ function validateDestination(destination) {
59
+ // Allow alphanumeric, underscores, hyphens (typical SAP destination/system names)
60
+ return enforceValidation(validateSpecialCharsInput(destination, '^[a-zA-Z0-9_-]+$'), destination);
61
+ }
62
+ /**
63
+ * Validate a hostname to prevent URL injection.
64
+ *
65
+ * @param hostname - hostname to validate
66
+ * @returns validated hostname
67
+ * @throws Error if hostname contains unsafe characters
68
+ */
69
+ function validateHostname(hostname) {
70
+ const strippedHostname = hostname.replace(/^https?:\/\//i, '');
71
+ // RFC-compliant hostname with an optional port.
72
+ // Simplified: alphanumeric + hyphens, no leading/trailing hyphens, optional port
73
+ if (!/^[a-zA-Z\d]([a-zA-Z\d-]*[a-zA-Z\d])?(\.[a-zA-Z\d]([a-zA-Z\d-]*[a-zA-Z\d])?)*(:\d+)?$/.test(strippedHostname)) {
74
+ throw new Error(t('migrate.validation.hostnameInvalid'));
75
+ }
76
+ return strippedHostname;
77
+ }
78
+ function getHostnameFromUrl(url) {
79
+ try {
80
+ return new URL(url).host;
81
+ }
82
+ catch {
83
+ return undefined;
84
+ }
85
+ }
86
+ /**
87
+ * Validate a SAP client number.
88
+ * Uses @sap-ux/project-input-validator for consistent validation.
89
+ *
90
+ * @param client - client number to validate
91
+ * @returns validated client
92
+ * @throws Error if client is not a 3-digit number
93
+ */
94
+ function validateClient(client) {
95
+ // SAP clients are 3-digit numbers (000-999)
96
+ return enforceValidation(validateClientInput(client), client);
97
+ }
98
+ /**
99
+ * Validate a UI5 version string to prevent URL injection.
100
+ *
101
+ * @param version - version string to validate
102
+ * @returns validated version
103
+ * @throws Error if version contains unsafe characters
104
+ */
105
+ function validateUI5Version(version) {
106
+ // Allow semantic version format: digits, dots, optional snapshot suffix (case-insensitive)
107
+ if (!/^\d+\.\d+\.\d+(-snapshot)?$/i.test(version)) {
108
+ throw new Error(t('migrate.validation.ui5VersionInvalid'));
109
+ }
110
+ return version;
111
+ }
112
+ /**
113
+ * Helper to create a required text validation function.
114
+ *
115
+ * @param fieldName - name of the field for error message
116
+ * @returns validation function
117
+ */
118
+ function createRequiredValidator(fieldName) {
119
+ return (value) => (value ? true : t('migrate.validation.fieldRequired', { fieldName }));
120
+ }
121
+ /**
122
+ * Helper to prompt for a required text input.
123
+ *
124
+ * @param name - prompt name
125
+ * @param message - prompt message
126
+ * @param fieldName - field name for validation error
127
+ * @returns user input
128
+ */
129
+ async function promptRequiredText(name, message, fieldName) {
130
+ const response = await prompts({
131
+ type: 'text',
132
+ name,
133
+ message,
134
+ validate: createRequiredValidator(fieldName)
135
+ });
136
+ if (response[name] === undefined) {
137
+ throw new Error(t('migrate.validation.operationCancelled'));
138
+ }
139
+ return response[name];
140
+ }
141
+ /**
142
+ * Helper to prompt for confirmation.
143
+ *
144
+ * @param name - prompt name
145
+ * @param message - prompt message
146
+ * @param initial - initial value
147
+ * @returns confirmation response
148
+ */
149
+ async function promptConfirm(name, message, initial) {
150
+ const response = await prompts({ type: 'confirm', name, message, initial });
151
+ if (response[name] === undefined) {
152
+ throw new Error(t('migrate.validation.operationCancelled'));
153
+ }
154
+ return response[name];
155
+ }
156
+ /**
157
+ * Returns the 'migrate' command for the commander program.
158
+ *
159
+ * @returns - commander command
160
+ */
161
+ export function getMigrateCommand() {
162
+ return new Command('migrate')
163
+ .argument('[project-path]')
164
+ .description(t('migrate.command.description'))
165
+ .option('-d, --destination <name>', t('migrate.command.options.destination'))
166
+ .option('-s, --sap-system-name <name>', t('migrate.command.options.sapSystemName'))
167
+ .option('-H, --hostname <host>', t('migrate.command.options.hostname'))
168
+ .option('-c, --client <client>', t('migrate.command.options.client'))
169
+ .option('-u, --ui5-version <version>', t('migrate.command.options.ui5Version'))
170
+ .option('-f, --force', t('migrate.command.options.force'))
171
+ .option('-n, --skip-install', t('migrate.command.options.skipInstall'))
172
+ .action(async (projectPath, options) => {
173
+ try {
174
+ await migrate(projectPath, options);
175
+ }
176
+ catch (error) {
177
+ const logger = getLogger();
178
+ logger.error(error instanceof Error ? error.message : String(error));
179
+ process.exit(1);
180
+ }
181
+ });
182
+ }
183
+ /**
184
+ * Get project path from user or use provided path.
185
+ *
186
+ * @param projectPath - optional project path from command line
187
+ * @returns resolved project path
188
+ */
189
+ async function getProjectPath(projectPath) {
190
+ let resolvedPath = projectPath ? validatePath(projectPath) : process.cwd();
191
+ if (!projectPath) {
192
+ const confirmPath = await promptConfirm('confirmPath', t('migrate.prompts.confirmCurrentDir', { path: resolvedPath }), true);
193
+ if (!confirmPath) {
194
+ const customPath = await promptRequiredText('customPath', t('migrate.prompts.enterPath'), t('migrate.prompts.enterPathField'));
195
+ resolvedPath = validatePath(customPath);
196
+ }
197
+ }
198
+ return resolvedPath;
199
+ }
200
+ /**
201
+ * Check if project needs force flag for migration.
202
+ * Uses package.json devDependencies to detect Fiori tools projects.
203
+ *
204
+ * @param resolvedPath - project path
205
+ * @param force - force flag from options
206
+ * @returns true if migration should proceed, false otherwise
207
+ */
208
+ async function checkForceRequired(resolvedPath, force) {
209
+ const logger = getLogger();
210
+ // Check for Fiori tools indicators in package.json
211
+ let isToolsProject = false;
212
+ try {
213
+ const packageJsonPath = resolve(resolvedPath, 'package.json');
214
+ if (existsSync(packageJsonPath)) {
215
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
216
+ const devDeps = packageJson.devDependencies || {};
217
+ // Fiori tools projects have @sap/ux-* dev dependencies
218
+ // Note: @ui5/* packages like @ui5/cli are generic build tools, not Fiori-specific
219
+ isToolsProject = Object.keys(devDeps).some((dep) => dep.startsWith('@sap/ux-'));
220
+ }
221
+ }
222
+ catch {
223
+ // If we can't read package.json, assume not a tools project
224
+ }
225
+ if (isToolsProject && !force) {
226
+ logger.warn(t('migrate.prompts.alreadyMigrated'));
227
+ const confirmForce = await promptConfirm('confirmForce', t('migrate.prompts.forceConfirm'), false);
228
+ if (!confirmForce) {
229
+ logger.info(t('migrate.prompts.cancelled'));
230
+ return false;
231
+ }
232
+ }
233
+ return true;
234
+ }
235
+ /**
236
+ * Get destination or hostname from user or options.
237
+ *
238
+ * @param options - command options
239
+ * @returns object with destination and hostname
240
+ */
241
+ async function getDestinationOrHostname(options) {
242
+ const appStudio = isAppStudio();
243
+ let destination = options.destination ?? options.sapSystemName;
244
+ let hostname = options.hostname ? validateHostname(options.hostname) : undefined;
245
+ // Validate destination if provided via CLI
246
+ if (destination) {
247
+ destination = validateDestination(destination);
248
+ }
249
+ if (appStudio) {
250
+ if (!destination) {
251
+ destination = validateDestination(await promptRequiredText('dest', t('migrate.prompts.enterBASDestination'), t('migrate.prompts.destinationField')));
252
+ }
253
+ }
254
+ else if (!hostname && !destination) {
255
+ const useDestination = await promptConfirm('useDestination', t('migrate.prompts.useDestination'), true);
256
+ if (useDestination) {
257
+ const dest = await promptRequiredText('dest', t('migrate.prompts.enterBTPDestination'), t('migrate.prompts.destinationField'));
258
+ destination = validateDestination(dest);
259
+ }
260
+ if (!destination) {
261
+ const host = await promptRequiredText('host', t('migrate.prompts.enterHostname'), t('migrate.prompts.hostnameField'));
262
+ hostname = validateHostname(host);
263
+ }
264
+ }
265
+ else if (!hostname && destination) {
266
+ // Outside BAS with destination but no hostname - prompt for hostname
267
+ const host = await promptRequiredText('host', t('migrate.prompts.enterHostname'), t('migrate.prompts.hostnameField'));
268
+ hostname = validateHostname(host);
269
+ }
270
+ return { destination, hostname };
271
+ }
272
+ /**
273
+ * Get optional client parameter.
274
+ *
275
+ * @param optionClient - client from command options
276
+ * @returns client value or undefined
277
+ */
278
+ async function getClient(optionClient) {
279
+ if (optionClient) {
280
+ return validateClient(optionClient);
281
+ }
282
+ if (!isAppStudio()) {
283
+ return validateClient(await promptRequiredText('clientValue', t('migrate.prompts.enterClient'), t('migrate.prompts.clientField')));
284
+ }
285
+ const response = await prompts({
286
+ type: 'text',
287
+ name: 'clientValue',
288
+ message: t('migrate.prompts.enterClientOptional'),
289
+ initial: ''
290
+ });
291
+ const client = response.clientValue;
292
+ return client ? validateClient(client) : undefined;
293
+ }
294
+ /**
295
+ * Get UI5 version parameter.
296
+ *
297
+ * @param optionVersion - UI5 version from command options
298
+ * @returns UI5 version or undefined
299
+ */
300
+ async function getUI5Version(optionVersion) {
301
+ if (optionVersion) {
302
+ return validateUI5Version(optionVersion);
303
+ }
304
+ // Non-interactive: use project default
305
+ if (!process.stdin.isTTY) {
306
+ return undefined;
307
+ }
308
+ const response = await prompts({
309
+ type: 'text',
310
+ name: 'version',
311
+ message: t('migrate.prompts.enterUI5Version'),
312
+ initial: ''
313
+ });
314
+ const version = response.version;
315
+ return version ? validateUI5Version(version) : undefined;
316
+ }
317
+ /**
318
+ * Resolve a legacy project's backend to a saved system or BAS destination.
319
+ *
320
+ * @param projectPath - legacy project root
321
+ * @returns matched system, if available
322
+ */
323
+ async function findMigrationSystem(projectPath) {
324
+ const logger = getLogger();
325
+ try {
326
+ const { projectInfo } = await ProjectAccess.getProjectInfo(projectPath);
327
+ if (isAppStudio()) {
328
+ return await findBASDestination(projectInfo.destination, projectInfo.hostname);
329
+ }
330
+ if (!projectInfo.hostname) {
331
+ return undefined;
332
+ }
333
+ const service = await getService({ entityName: 'system', logger });
334
+ const savedSystem = await findSystemByUrl(projectInfo.hostname, projectInfo.sapClient || undefined, service);
335
+ return savedSystem ? { ...savedSystem, scp: false } : undefined;
336
+ }
337
+ catch (error) {
338
+ logger.debug(`Unable to resolve a migration system: ${error.message}`);
339
+ return undefined;
340
+ }
341
+ }
342
+ /**
343
+ * Match the legacy project backend to a BAS destination.
344
+ *
345
+ * @param destinationName - destination parsed from the legacy project
346
+ * @param hostname - backend hostname parsed from the legacy project
347
+ * @returns matching BAS destination, if available
348
+ */
349
+ async function findBASDestination(destinationName, hostname) {
350
+ const destinations = await listDestinations();
351
+ const systems = Object.values(destinations).map((destination) => toMigrationSystem(destination));
352
+ const destinationMatch = destinationName ? systems.find((system) => system.name === destinationName) : undefined;
353
+ if (destinationMatch) {
354
+ return destinationMatch;
355
+ }
356
+ if (!hostname) {
357
+ return undefined;
358
+ }
359
+ try {
360
+ const normalizedHostname = /^https?:\/\//i.test(hostname) ? hostname : `https://${hostname}`;
361
+ const sourceOrigin = new URL(normalizedHostname).origin;
362
+ const matches = systems.filter((system) => new URL(system.url).origin === sourceOrigin);
363
+ if (matches.length === 1) {
364
+ return matches[0];
365
+ }
366
+ if (matches.length > 1) {
367
+ const answer = await prompts({
368
+ type: 'select',
369
+ name: 'system',
370
+ message: t('migrate.prompts.selectBASDestination'),
371
+ choices: matches.map((system) => ({
372
+ title: t('migrate.prompts.destinationChoice', {
373
+ name: system.name,
374
+ client: system.client || t('migrate.prompts.defaultClient')
375
+ }),
376
+ value: system
377
+ }))
378
+ });
379
+ if (answer.system === undefined) {
380
+ throw new Error(t('migrate.validation.operationCancelled'));
381
+ }
382
+ return answer.system;
383
+ }
384
+ }
385
+ catch (error) {
386
+ // Re-throw cancellation errors so the user can abort
387
+ if (error.message === t('migrate.validation.operationCancelled')) {
388
+ throw error;
389
+ }
390
+ // Invalid source or destination URLs cannot be matched safely.
391
+ }
392
+ return undefined;
393
+ }
394
+ function toMigrationSystem(destination) {
395
+ return {
396
+ name: destination.Name,
397
+ url: destination.Host,
398
+ client: destination['sap-client'] || undefined,
399
+ scp: destination.ProxyType !== DestinationProxyType.ON_PREMISE
400
+ };
401
+ }
402
+ /**
403
+ * Execute npm install and capture errors, warnings, and security issues.
404
+ *
405
+ * @param resolvedPath - project path
406
+ * @param logger - logger instance
407
+ * @returns npm install result with captured messages
408
+ */
409
+ async function executeNpmInstall(resolvedPath, logger) {
410
+ const npmErrors = [];
411
+ const npmSecurityWarnings = [];
412
+ try {
413
+ await execNpmCommand(['install'], {
414
+ cwd: resolvedPath,
415
+ throwOnError: true,
416
+ logger: {
417
+ info: (msg) => {
418
+ const message = typeof msg === 'string' ? msg : JSON.stringify(msg);
419
+ // Capture security vulnerabilities
420
+ if (message.toLowerCase().includes('vulnerabilities') ||
421
+ message.toLowerCase().includes('security')) {
422
+ npmSecurityWarnings.push(message);
423
+ }
424
+ logger.info(msg);
425
+ },
426
+ error: (msg) => {
427
+ const message = typeof msg === 'string' ? msg : JSON.stringify(msg);
428
+ npmErrors.push(message);
429
+ logger.error(msg);
430
+ },
431
+ debug: (msg) => {
432
+ logger.debug(msg);
433
+ }
434
+ }
435
+ });
436
+ logger.info(t('npm.installSuccess'));
437
+ return {
438
+ success: true,
439
+ ...(npmSecurityWarnings.length > 0 && { securityWarnings: npmSecurityWarnings })
440
+ };
441
+ }
442
+ catch (error) {
443
+ const installError = error;
444
+ npmErrors.push(installError.message);
445
+ logger.error(t('migrate.status.installFailed'));
446
+ return {
447
+ success: false,
448
+ errors: npmErrors,
449
+ ...(npmSecurityWarnings.length > 0 && { securityWarnings: npmSecurityWarnings })
450
+ };
451
+ }
452
+ }
453
+ /**
454
+ * Resolve migration options with fallbacks from matched system.
455
+ *
456
+ * @param options
457
+ * @param matchedSystem
458
+ */
459
+ function resolveMigrationOptions(options, matchedSystem) {
460
+ return {
461
+ ...options,
462
+ destination: options.destination ?? options.sapSystemName ?? matchedSystem?.name,
463
+ hostname: options.hostname ?? (matchedSystem?.url ? getHostnameFromUrl(matchedSystem.url) : undefined),
464
+ client: options.client ??
465
+ matchedSystem?.client ??
466
+ (ProjectAccess.getClientFromDestinationName(options.destination ?? options.sapSystemName ?? matchedSystem?.name ?? '') ||
467
+ undefined)
468
+ };
469
+ }
470
+ /**
471
+ * Calculate base URI for migration.
472
+ * Priority: hostname > matchedSystem.url (if no hostname override) > destination (BAS only).
473
+ *
474
+ * @param destination
475
+ * @param hostname
476
+ * @param matchedSystem
477
+ * @param hostnameOverride
478
+ */
479
+ function calculateBaseUri(destination, hostname, matchedSystem, hostnameOverride) {
480
+ // Start with destination-based URI: BAS routes by destination name via the proxy;
481
+ // outside BAS only a full hostname URL is valid.
482
+ let baseUri = isAppStudio() && destination ? `/${destination}` : '';
483
+ // Override with matched system URL if present and no hostname override
484
+ if (matchedSystem?.url && !hostnameOverride) {
485
+ baseUri = matchedSystem.url;
486
+ }
487
+ // Override with hostname if provided (takes precedence)
488
+ else if (hostname) {
489
+ baseUri = `https://${hostname}`;
490
+ }
491
+ return baseUri;
492
+ }
493
+ /**
494
+ * Log migration messages by type.
495
+ *
496
+ * @param messages
497
+ * @param logger
498
+ */
499
+ function logMigrationMessages(messages, logger) {
500
+ logger.info(t('migrate.status.messages'));
501
+ messages.forEach((msg) => {
502
+ const logMessage = ` ${msg.type}: ${msg.description}`;
503
+ if (msg.type === 'ERROR') {
504
+ logger.error(logMessage);
505
+ }
506
+ else if (msg.type === 'WARNING') {
507
+ logger.warn(logMessage);
508
+ }
509
+ else {
510
+ logger.info(logMessage);
511
+ }
512
+ });
513
+ }
514
+ /**
515
+ * Handle successful migration - install dependencies and write log.
516
+ *
517
+ * @param result
518
+ * @param options
519
+ * @param resolvedPath
520
+ * @param migrationLogEntry
521
+ * @param logger
522
+ */
523
+ async function handleMigrationSuccess(result, options, resolvedPath, migrationLogEntry, logger) {
524
+ logger.info(t('migrate.status.success'));
525
+ if (result.messages?.length) {
526
+ logMigrationMessages(result.messages, logger);
527
+ }
528
+ if (options.skipInstall) {
529
+ logger.warn(t('migrate.status.skipInstallWarning'));
530
+ }
531
+ else {
532
+ logger.info(t('migrate.status.installing'));
533
+ migrationLogEntry.npmInstall = await executeNpmInstall(resolvedPath, logger);
534
+ }
535
+ writeMigrationLog(migrationLogEntry, logger);
536
+ logger.info(`Migration log location: ${getMigrationLogsDirectory()}`);
537
+ }
538
+ /**
539
+ * Handle failed migration - write log and throw error.
540
+ *
541
+ * @param result
542
+ * @param migrationLogEntry
543
+ * @param logger
544
+ */
545
+ function handleMigrationFailure(result, migrationLogEntry, logger) {
546
+ logger.error(t('migrate.status.failed'));
547
+ if (result.messages?.length) {
548
+ result.messages.forEach((msg) => logger.error(` ${msg.description}`));
549
+ }
550
+ writeMigrationLog(migrationLogEntry, logger);
551
+ logger.info(`Migration log location: ${getMigrationLogsDirectory()}`);
552
+ throw new Error(t('migrate.status.failedError'));
553
+ }
554
+ /**
555
+ * Execute the migration command.
556
+ *
557
+ * @param projectPath - path to the project to migrate
558
+ * @param options - command options
559
+ */
560
+ async function migrate(projectPath, options) {
561
+ const logger = getLogger();
562
+ // 1. Get or prompt for project path
563
+ const resolvedPath = await getProjectPath(projectPath);
564
+ logger.info(t('migrate.status.migratingAt', { path: resolvedPath }));
565
+ // 2. Check if force flag is required
566
+ const shouldProceed = await checkForceRequired(resolvedPath, options.force ?? false);
567
+ if (!shouldProceed) {
568
+ return;
569
+ }
570
+ const matchedSystem = await findMigrationSystem(resolvedPath);
571
+ // 3. Resolve options with fallbacks
572
+ const resolvedOptions = resolveMigrationOptions(options, matchedSystem);
573
+ const { destination, hostname } = await getDestinationOrHostname(resolvedOptions);
574
+ // 4. Get optional client
575
+ const client = await getClient(resolvedOptions.client);
576
+ // 5. Get UI5 version
577
+ const ui5Version = await getUI5Version(options.ui5Version);
578
+ const sapClient = client ?? (destination ? ProjectAccess.getClientFromDestinationName(destination) || undefined : undefined);
579
+ // 6. Execute migration
580
+ logger.info(t('migrate.status.starting'));
581
+ const baseUri = calculateBaseUri(destination, hostname, matchedSystem, !!options.hostname);
582
+ const ui5SnapshotUrl = ui5Version ? `https://ui5.sap.com/${ui5Version}` : '';
583
+ const migrationHostname = hostname ?? (matchedSystem?.url ? getHostnameFromUrl(matchedSystem.url) : undefined);
584
+ // Initialize i18n for proper error messages
585
+ await initMigrationI18n();
586
+ const internalToggle = isInternalFeaturesSettingEnabled();
587
+ // Load project info first, then merge CLI overrides
588
+ const migrationProjectInfo = sapClient || destination || hostname
589
+ ? {
590
+ ...(sapClient && { sapClient }),
591
+ ...(destination && { destination }),
592
+ ...(migrationHostname && { hostname: migrationHostname }),
593
+ ...(!options.destination && !options.sapSystemName && matchedSystem && { scp: matchedSystem.scp })
594
+ }
595
+ : undefined;
596
+ const result = await ProjectMigrator.migrate({
597
+ projectRoot: resolvedPath,
598
+ baseUri,
599
+ ui5SnapshotUrl,
600
+ importProjectInfo: migrationProjectInfo,
601
+ internalToggle: internalToggle || undefined
602
+ });
603
+ // Prepare migration log entry
604
+ const migrationLogEntry = {
605
+ timestamp: new Date().toISOString(),
606
+ projectPath: resolvedPath,
607
+ projectName: basename(resolvedPath),
608
+ migrationMessages: result.messages || [],
609
+ migrationSuccess: result.result
610
+ };
611
+ if (result.result) {
612
+ await handleMigrationSuccess(result, options, resolvedPath, migrationLogEntry, logger);
613
+ }
614
+ else {
615
+ handleMigrationFailure(result, migrationLogEntry, logger);
616
+ }
617
+ }
618
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,43 @@
1
+ import type { Logger } from '@sap-ux/logger';
2
+ export interface MigrationLogEntry {
3
+ timestamp: string;
4
+ projectPath: string;
5
+ projectName: string;
6
+ migrationMessages: Array<{
7
+ type: string;
8
+ description: string;
9
+ }>;
10
+ npmInstall?: {
11
+ success: boolean;
12
+ errors?: string[];
13
+ warnings?: string[];
14
+ securityWarnings?: string[];
15
+ };
16
+ migrationSuccess: boolean;
17
+ }
18
+ /**
19
+ * Get the migration logs directory.
20
+ *
21
+ * @returns path to migration logs directory
22
+ */
23
+ export declare function getMigrationLogsDirectory(): string;
24
+ /**
25
+ * Ensure the migration logs directory exists.
26
+ */
27
+ export declare function ensureMigrationLogsDirectory(): void;
28
+ /**
29
+ * Generate a unique log file name for a migration.
30
+ *
31
+ * @param projectName - name of the migrated project
32
+ * @param timestamp - ISO timestamp string
33
+ * @returns log file name
34
+ */
35
+ export declare function generateLogFileName(projectName: string, timestamp: string): string;
36
+ /**
37
+ * Write migration log to file.
38
+ *
39
+ * @param logEntry - migration log entry
40
+ * @param logger - optional logger for feedback
41
+ */
42
+ export declare function writeMigrationLog(logEntry: MigrationLogEntry, logger?: Logger): void;
43
+ //# sourceMappingURL=traceability.d.ts.map
@@ -0,0 +1,52 @@
1
+ import { join } from 'node:path';
2
+ import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
3
+ import { getFioriToolsDirectory } from '@sap-ux/store';
4
+ /**
5
+ * Get the migration logs directory.
6
+ *
7
+ * @returns path to migration logs directory
8
+ */
9
+ export function getMigrationLogsDirectory() {
10
+ const fioriToolsDir = getFioriToolsDirectory();
11
+ return join(fioriToolsDir, 'migration-logs');
12
+ }
13
+ /**
14
+ * Ensure the migration logs directory exists.
15
+ */
16
+ export function ensureMigrationLogsDirectory() {
17
+ const logsDir = getMigrationLogsDirectory();
18
+ if (!existsSync(logsDir)) {
19
+ mkdirSync(logsDir, { recursive: true });
20
+ }
21
+ }
22
+ /**
23
+ * Generate a unique log file name for a migration.
24
+ *
25
+ * @param projectName - name of the migrated project
26
+ * @param timestamp - ISO timestamp string
27
+ * @returns log file name
28
+ */
29
+ export function generateLogFileName(projectName, timestamp) {
30
+ const cleanTimestamp = timestamp.replace(/[:.]/g, '-').replace(/Z$/, '');
31
+ return `${projectName}_${cleanTimestamp}.json`;
32
+ }
33
+ /**
34
+ * Write migration log to file.
35
+ *
36
+ * @param logEntry - migration log entry
37
+ * @param logger - optional logger for feedback
38
+ */
39
+ export function writeMigrationLog(logEntry, logger) {
40
+ try {
41
+ ensureMigrationLogsDirectory();
42
+ const logsDir = getMigrationLogsDirectory();
43
+ const logFileName = generateLogFileName(logEntry.projectName, logEntry.timestamp);
44
+ const logFilePath = join(logsDir, logFileName);
45
+ writeFileSync(logFilePath, JSON.stringify(logEntry, null, 2), 'utf-8');
46
+ logger?.info(`Migration log written to: ${logFilePath}`);
47
+ }
48
+ catch (error) {
49
+ logger?.warn(`Failed to write migration log: ${error.message}`);
50
+ }
51
+ }
52
+ //# sourceMappingURL=traceability.js.map
@@ -0,0 +1,18 @@
1
+ import type { BackendSystem, BackendSystemKey } from '@sap-ux/store';
2
+ /**
3
+ * Create a store service for BackendSystem entities.
4
+ * When `silentForJson` is true, uses a NullTransport logger so that store
5
+ * diagnostic messages do not appear on stdout before a JSON payload.
6
+ *
7
+ * @param silentForJson - suppress store log output when true
8
+ * @returns configured store service
9
+ */
10
+ export declare function getSystemService(silentForJson: boolean): Promise<import("@sap-ux/store").Service<BackendSystem, BackendSystemKey>>;
11
+ /**
12
+ * Returns a view of a BackendSystem with only non-sensitive fields.
13
+ *
14
+ * @param system - the backend system
15
+ * @returns non-sensitive system data
16
+ */
17
+ export declare function toPublicView(system: BackendSystem): Record<string, unknown>;
18
+ //# sourceMappingURL=system-store.d.ts.map
@@ -0,0 +1,38 @@
1
+ import { getService } from '@sap-ux/store';
2
+ import { NullTransport, ToolsLogger } from '@sap-ux/logger';
3
+ import { getLogger } from '../../tracing/index.js';
4
+ /**
5
+ * Create a store service for BackendSystem entities.
6
+ * When `silentForJson` is true, uses a NullTransport logger so that store
7
+ * diagnostic messages do not appear on stdout before a JSON payload.
8
+ *
9
+ * @param silentForJson - suppress store log output when true
10
+ * @returns configured store service
11
+ */
12
+ export function getSystemService(silentForJson) {
13
+ const logger = getLogger();
14
+ const storeLogger = silentForJson ? new ToolsLogger({ transports: [new NullTransport()] }) : logger;
15
+ return getService({
16
+ entityName: 'system',
17
+ logger: storeLogger
18
+ });
19
+ }
20
+ /**
21
+ * Returns a view of a BackendSystem with only non-sensitive fields.
22
+ *
23
+ * @param system - the backend system
24
+ * @returns non-sensitive system data
25
+ */
26
+ export function toPublicView(system) {
27
+ return {
28
+ name: system.name,
29
+ url: system.url,
30
+ client: system.client,
31
+ systemType: system.systemType,
32
+ authenticationType: system.authenticationType,
33
+ connectionType: system.connectionType,
34
+ userDisplayName: system.userDisplayName,
35
+ hasSensitiveData: system.hasSensitiveData
36
+ };
37
+ }
38
+ //# sourceMappingURL=system-store.js.map
@@ -10,5 +10,5 @@ export { promptYUIQuestions } from './prompts.js';
10
10
  */
11
11
  export declare function runNpmInstallCommand(basePath: string, installArgs?: string[], options?: {
12
12
  logger?: Logger;
13
- }): void;
13
+ }): Promise<void>;
14
14
  //# sourceMappingURL=index.d.ts.map
@@ -1,4 +1,5 @@
1
1
  import { execNpmCommand } from '@sap-ux/project-access';
2
+ import { t } from '../i18n.js';
2
3
  export { promptYUIQuestions } from './prompts.js';
3
4
  /**
4
5
  * Run npm install command.
@@ -10,12 +11,13 @@ export { promptYUIQuestions } from './prompts.js';
10
11
  */
11
12
  export function runNpmInstallCommand(basePath, installArgs = [], options) {
12
13
  const logger = options?.logger;
13
- execNpmCommand(['install', ...installArgs], { cwd: basePath, logger: logger })
14
+ return execNpmCommand(['install', ...installArgs], { cwd: basePath, logger: logger })
14
15
  .then(() => {
15
- logger?.info('npm install completed successfully.');
16
+ logger?.info(t('npm.installSuccess'));
16
17
  })
17
18
  .catch((error) => {
18
- logger?.error(`npm install failed. '${error.message}'`);
19
+ logger?.error(t('npm.installFailed', { error: error.message }));
20
+ throw error;
19
21
  });
20
22
  }
21
23
  //# sourceMappingURL=index.js.map
@@ -64,5 +64,65 @@
64
64
  "systemAdded": "System '{{name}}' added",
65
65
  "systemUpdated": "System '{{name}}' updated",
66
66
  "systemRemoved": "System '{{name}}' removed"
67
+ },
68
+ "migrate": {
69
+ "command": {
70
+ "description": "Migrate legacy WebIDE Fiori project to modern Fiori tools format. In BAS, a destination is required. Outside BAS, hostname and client are required; destination is optional.",
71
+ "options": {
72
+ "destination": "SAP System destination name (required in BAS)",
73
+ "sapSystemName": "SAP System name (alias for destination)",
74
+ "hostname": "Backend hostname (required outside BAS unless resolved from a saved system)",
75
+ "client": "SAP Client (required outside BAS unless resolved from destination or saved system)",
76
+ "ui5Version": "UI5 version (defaults to source project version)",
77
+ "force": "Force migration even if project is already a Fiori tools project",
78
+ "skipInstall": "Skip the `npm install` step after migration"
79
+ }
80
+ },
81
+ "validation": {
82
+ "pathUnsafe": "Path contains unsafe characters",
83
+ "pathNotExists": "Path does not exist: {{path}}",
84
+ "destinationInvalid": "Destination name must contain only letters, numbers, hyphens, and underscores",
85
+ "hostnameInvalid": "Invalid hostname format",
86
+ "clientInvalid": "SAP client must be a 3-digit number (e.g., 100)",
87
+ "ui5VersionInvalid": "UI5 version must follow semantic versioning format (e.g., 1.120.0)",
88
+ "fieldRequired": "{{fieldName}} is required",
89
+ "operationCancelled": "Operation cancelled by user"
90
+ },
91
+ "prompts": {
92
+ "confirmCurrentDir": "Migrate project at current directory: {{path}}?",
93
+ "enterPath": "Enter project path:",
94
+ "enterPathField": "Project path",
95
+ "alreadyMigrated": "Project appears to be already migrated to Fiori tools.",
96
+ "forceConfirm": "Force migration anyway?",
97
+ "cancelled": "Migration cancelled.",
98
+ "enterBASDestination": "Enter SAP Business Application Studio destination:",
99
+ "destinationField": "Destination",
100
+ "useDestination": "Use SAP System destination?",
101
+ "enterBTPDestination": "Enter BTP destination or SAP system name (letters, numbers, hyphens, underscores only):",
102
+ "enterHostname": "Enter hostname:",
103
+ "hostnameField": "Hostname",
104
+ "enterClient": "SAP Client:",
105
+ "clientField": "SAP Client",
106
+ "enterClientOptional": "SAP Client (optional, press Enter to skip):",
107
+ "enterUI5Version": "UI5 Version (optional, press Enter to use project default):",
108
+ "selectBASDestination": "Select the SAP Business Application Studio destination:",
109
+ "destinationChoice": "{{name}} ({{client}})",
110
+ "defaultClient": "default client"
111
+ },
112
+ "status": {
113
+ "migratingAt": "Migrating project at: {{path}}",
114
+ "starting": "Starting migration...",
115
+ "success": "✓ Migration completed successfully!",
116
+ "messages": "\nMessages:",
117
+ "skipInstallWarning": "`npm install` was skipped. Install project dependencies before running the application.",
118
+ "installing": "Installing project dependencies...",
119
+ "installFailed": "Migration completed, but dependency installation failed. Resolve the npm error above before running the project.",
120
+ "failed": "✗ Migration failed",
121
+ "failedError": "Migration failed"
122
+ }
123
+ },
124
+ "npm": {
125
+ "installSuccess": "npm install completed successfully.",
126
+ "installFailed": "npm install failed. '{{error}}'"
67
127
  }
68
128
  }
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.4.18",
4
+ "version": "1.5.1",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://github.com/SAP/open-ux-tools.git",
@@ -35,26 +35,28 @@
35
35
  "mem-fs": "2.1.0",
36
36
  "mem-fs-editor": "9.4.0",
37
37
  "prompts": "2.4.2",
38
- "@sap-ux/abap-deploy-config-inquirer": "2.0.52",
39
- "@sap-ux/abap-deploy-config-writer": "1.0.31",
40
- "@sap-ux/adp-tooling": "1.0.64",
41
- "@sap-ux/btp-utils": "2.0.6",
42
- "@sap-ux/app-config-writer": "1.1.27",
43
- "@sap-ux/cap-config-writer": "1.0.43",
44
- "@sap-ux/inquirer-common": "1.0.47",
38
+ "@sap-ux/abap-deploy-config-inquirer": "2.0.53",
39
+ "@sap-ux/abap-deploy-config-writer": "1.0.32",
40
+ "@sap-ux/adp-tooling": "1.0.65",
41
+ "@sap-ux/btp-utils": "2.0.7",
42
+ "@sap-ux/app-config-writer": "1.1.28",
43
+ "@sap-ux/cap-config-writer": "1.0.44",
44
+ "@sap-ux/inquirer-common": "1.0.48",
45
45
  "@sap-ux/logger": "1.0.3",
46
- "@sap-ux/mockserver-config-writer": "1.0.22",
47
- "@sap-ux/odata-service-writer": "1.0.25",
48
- "@sap-ux/preview-middleware": "1.2.26",
49
- "@sap-ux/project-access": "2.3.0",
50
- "@sap-ux/project-input-validator": "1.0.23",
46
+ "@sap-ux/mockserver-config-writer": "1.0.23",
47
+ "@sap-ux/odata-service-writer": "1.0.26",
48
+ "@sap-ux/preview-middleware": "1.2.27",
49
+ "@sap-ux/project-access": "2.3.1",
50
+ "@sap-ux/project-input-validator": "1.0.24",
51
+ "@sap-ux/system-access": "1.0.16",
51
52
  "@sap-ux/store": "2.0.7",
52
- "@sap-ux/ui5-config": "1.0.9",
53
- "@sap-ux/ui5-test-writer": "1.18.1",
54
- "@sap-ux/flp-config-inquirer": "1.0.64",
55
- "@sap-ux/nodejs-utils": "1.0.10",
56
- "@sap-ux/axios-extension": "2.1.1",
57
- "@sap-ux/system-access": "1.0.15"
53
+ "@sap-ux/ui5-config": "1.0.10",
54
+ "@sap-ux/ui5-test-writer": "1.18.2",
55
+ "@sap-ux/fiori-migration-writer": "0.0.4",
56
+ "@sap-ux/feature-toggle": "1.0.5",
57
+ "@sap-ux/flp-config-inquirer": "1.0.65",
58
+ "@sap-ux/nodejs-utils": "1.0.11",
59
+ "@sap-ux/axios-extension": "2.1.2"
58
60
  },
59
61
  "devDependencies": {
60
62
  "@jest/globals": "30.4.1",
@@ -63,7 +65,7 @@
63
65
  "@types/mem-fs": "1.1.2",
64
66
  "@types/mem-fs-editor": "7.0.1",
65
67
  "@types/prompts": "2.4.9",
66
- "@sap-ux/inquirer-common": "1.0.47",
68
+ "@sap-ux/inquirer-common": "1.0.48",
67
69
  "@sap-ux/store": "2.0.7"
68
70
  },
69
71
  "scripts": {