@sap-ux/repo-app-import-sub-generator 1.2.60 → 1.2.62

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.
@@ -1,4 +1,4 @@
1
- import { appListResultFields, downloadTypeConfig, generatorTitleConfig, adtSourceTemplateId } from '../utils/constants.js';
1
+ import { appListResultFields, downloadTypeConfig, generatorTitleConfig, adtSourceTemplateId, appListFieldsWithoutSourceTemplate, sourceTemplateIdField } from '../utils/constants.js';
2
2
  import { AppDownloadType } from '../app/types.js';
3
3
  import { PromptState } from './prompt-state.js';
4
4
  import { t } from '../utils/i18n.js';
@@ -64,27 +64,44 @@ export const formatAppChoices = (appList) => {
64
64
  *
65
65
  * @param {AbapServiceProvider} provider - The ABAP service provider.
66
66
  * @param {string} appId - Application ID to filter the list.
67
- * @param {AppDownloadType} downloadType - The download type determining which search params to use.
68
- * @returns {Promise<AppIndex>} A list of applications filtered by source template.
67
+ * @param {AppDownloadType} downloadType - The download type determining which search parameters to use.
68
+ * @returns {Promise<AppIndex>} A list of deployed applications. For the ADTQuickDeploy flow, only ADT-deployed apps are returned. For the AbapRepository flow on systems that support it, ADT-deployed apps are excluded.
69
69
  */
70
70
  async function getAppList(provider, appId, downloadType = AppDownloadType.ADTQuickDeploy) {
71
+ const baseSearchParams = downloadTypeConfig[downloadType].searchParams;
72
+ const searchParams = appId ? { ...baseSearchParams, 'sap.app/id': appId } : baseSearchParams;
71
73
  try {
72
- const baseSearchParams = downloadTypeConfig[downloadType].searchParams;
73
- const searchParams = appId
74
- ? {
75
- ...baseSearchParams,
76
- 'sap.app/id': appId
77
- }
78
- : baseSearchParams;
79
74
  const results = await provider.getAppIndex().search(searchParams, appListResultFields);
80
75
  if (downloadType === AppDownloadType.AbapRepository) {
81
76
  // For ABAP Repository downloads, filter out apps with the ADT source template as they follow the quick deploy app download flow.
82
- return results.filter((app) => app['sap.app/sourceTemplate/id'] !== adtSourceTemplateId);
77
+ const filtered = results.filter((app) => app[sourceTemplateIdField] !== adtSourceTemplateId);
78
+ RepoAppDownloadLogger.logger?.debug(`App list fetched: ${results.length} total, ${filtered.length} after filtering out ADT-deployed apps`);
79
+ return filtered;
83
80
  }
84
81
  return results;
85
82
  }
86
83
  catch (error) {
87
- RepoAppDownloadLogger.logger?.error(t('error.applicationListFetchError', { error: error.message }));
84
+ if (downloadType === AppDownloadType.AbapRepository &&
85
+ error?.response?.status === 400) {
86
+ // Older systems may not support sourceTemplateIdField or the sap.app/type search param.
87
+ // Retry with no search params and without sourceTemplateIdField — return all results as-is
88
+ // since old systems won't have ADT-deployed apps to filter out anyway.
89
+ RepoAppDownloadLogger.logger?.debug(`Retrying without ${sourceTemplateIdField} and search params`);
90
+ try {
91
+ // sap.app/type=application is also dropped — older systems may reject it too.
92
+ // Non-application entries in the list are acceptable; they will fail at the download step.
93
+ const retryResults = await provider.getAppIndex().search({}, appListFieldsWithoutSourceTemplate);
94
+ RepoAppDownloadLogger.logger?.debug(`Retry succeeded: ${retryResults.length} results`);
95
+ return retryResults;
96
+ }
97
+ catch (retryError) {
98
+ const retryMessage = retryError instanceof Error ? retryError.message : String(retryError);
99
+ RepoAppDownloadLogger.logger?.error(t('error.applicationListFetchError', { error: retryMessage }));
100
+ return [];
101
+ }
102
+ }
103
+ const message = error instanceof Error ? error.message : String(error);
104
+ RepoAppDownloadLogger.logger?.error(t('error.applicationListFetchError', { error: message }));
88
105
  return [];
89
106
  }
90
107
  }
@@ -25,9 +25,8 @@ const getTargetFolderPrompt = (appRootPath, appId) => {
25
25
  if (appId) {
26
26
  return true;
27
27
  }
28
- // If appId is not provided, check if the user has selected an app.
29
- // If an app is selected, display the prompt accordingly.
30
- return Boolean(answers?.selectedApp?.appId);
28
+ // Only show after a successful download — admZip is set by downloadApp on success.
29
+ return Boolean(answers?.selectedApp?.appId) && !!PromptState.admZip;
31
30
  },
32
31
  guiOptions: {
33
32
  applyDefaultWhenDirty: true,
@@ -4,6 +4,7 @@
4
4
  export declare const EventName: {
5
5
  readonly ABAP_REPO_DOWNLOAD_SUCCESS: "ABAP_REPO_DOWNLOAD_SUCCESS";
6
6
  readonly ABAP_REPO_DOWNLOAD_FAIL: "ABAP_REPO_DOWNLOAD_FAIL";
7
+ readonly ABAP_REPO_DOWNLOAD_NO_FILES_RETURNED: "ABAP_REPO_DOWNLOAD_NO_FILES_RETURNED";
7
8
  readonly ADT_QUICK_DEPLOY_DOWNLOAD_SUCCESS: "ADT_QUICK_DEPLOY_DOWNLOAD_SUCCESS";
8
9
  readonly ADT_QUICK_DEPLOY_DOWNLOAD_FAIL: "ADT_QUICK_DEPLOY_DOWNLOAD_FAIL";
9
10
  readonly ABAP_REPO_DOWNLOAD_MIGRATION_COMPLETED: "ABAP_REPO_DOWNLOAD_MIGRATION_COMPLETED";
@@ -4,6 +4,7 @@
4
4
  export const EventName = {
5
5
  ABAP_REPO_DOWNLOAD_SUCCESS: 'ABAP_REPO_DOWNLOAD_SUCCESS',
6
6
  ABAP_REPO_DOWNLOAD_FAIL: 'ABAP_REPO_DOWNLOAD_FAIL',
7
+ ABAP_REPO_DOWNLOAD_NO_FILES_RETURNED: 'ABAP_REPO_DOWNLOAD_NO_FILES_RETURNED',
7
8
  ADT_QUICK_DEPLOY_DOWNLOAD_SUCCESS: 'ADT_QUICK_DEPLOY_DOWNLOAD_SUCCESS',
8
9
  ADT_QUICK_DEPLOY_DOWNLOAD_FAIL: 'ADT_QUICK_DEPLOY_DOWNLOAD_FAIL',
9
10
  ABAP_REPO_DOWNLOAD_MIGRATION_COMPLETED: 'ABAP_REPO_DOWNLOAD_MIGRATION_COMPLETED'
@@ -8,8 +8,9 @@
8
8
  "appConfigGenError": "An error occurred when generating the application configuration: {{- error}}. For more information, view the logs.",
9
9
  "endPhase": "An error occurred in the end phase: {{- error}}. For more information, view the logs.",
10
10
  "writingPhase": "An error occurred in the writing phase: {{- error}}. For more information, view the logs.",
11
- "errorProcessingJsonFile": "An error occurred when processing the JSON file: {{- error}}. Check the file is valid.",
11
+ "errorProcessingJsonFile": "An error occurred when processing the JSON file: {{- error}}. Ensure that the file is valid.",
12
12
  "noAppsDeployed": "No applications deployed to this system can be downloaded.",
13
+ "appDownloadFailed": "The application was found but no ZIP archive was returned by the system. The ABAP system may not support this download method.",
13
14
  "validationErrors": {
14
15
  "invalidMetadataPackage": "Invalid or missing package in the metadata. Check the metadata.",
15
16
  "invalidServiceName": "Invalid or missing `serviceName` in `serviceBindingDetails`. Check the `serviceName`.",
@@ -7,8 +7,9 @@ export interface DownloadTypeConfig {
7
7
  searchParams: Record<string, string>;
8
8
  }
9
9
  export declare const adtSourceTemplateId = "@sap.adt.sevicebinding.deploy:lrop";
10
+ export declare const sourceTemplateIdField = "sap.app/sourceTemplate/id";
10
11
  /**
11
- * Title and description config per download flow, used in the constructor
12
+ * Title and configuration of the description per download flow, used in the constructor
12
13
  */
13
14
  export declare const generatorTitleConfig: Record<AppDownloadType, {
14
15
  title: string;
@@ -25,4 +26,5 @@ export declare const qfaJsonFileName = "qfa.json";
25
26
  export declare const defaultAnswers: RepoAppDownloadAnswers;
26
27
  export declare const extractedFilePath = "extractedFiles";
27
28
  export declare const appListResultFields: string[];
29
+ export declare const appListFieldsWithoutSourceTemplate: string[];
28
30
  //# sourceMappingURL=constants.d.ts.map
@@ -4,8 +4,10 @@ import { AppDownloadType } from '../app/types.js';
4
4
  import { t } from './i18n.js';
5
5
  // The source template ID used for filtering the apps in the repository
6
6
  export const adtSourceTemplateId = '@sap.adt.sevicebinding.deploy:lrop';
7
+ // The app index field name for the source template ID
8
+ export const sourceTemplateIdField = 'sap.app/sourceTemplate/id';
7
9
  /**
8
- * Title and description config per download flow, used in the constructor
10
+ * Title and configuration of the description per download flow, used in the constructor
9
11
  */
10
12
  export const generatorTitleConfig = {
11
13
  [AppDownloadType.ADTQuickDeploy]: {
@@ -61,14 +63,17 @@ export const defaultAnswers = {
61
63
  };
62
64
  // Path for storing the extracted files from repository
63
65
  export const extractedFilePath = 'extractedFiles';
64
- // Fields to retrieve from the app list, useful for displaying app metadata
66
+ // Fields to retrieve from the app list, useful for displaying app metadata.
67
+ // Includes sap.app/sourceTemplate/id so ADT-deployed apps can be filtered out on modern systems.
65
68
  export const appListResultFields = [
66
- 'sap.app/id', // ID of the application
67
- 'sap.app/title', // Title of the application
68
- 'sap.app/description', // Description of the application
69
- 'sap.app/sourceTemplate/id', // ID of the source template
70
- 'repoName', // Repository name where the app is located
71
- 'fileType', // Type of file (.zip etc)
72
- 'url' // URL for accessing the app
69
+ 'sap.app/id',
70
+ 'sap.app/title',
71
+ 'sap.app/description',
72
+ sourceTemplateIdField,
73
+ 'repoName',
74
+ 'fileType',
75
+ 'url'
73
76
  ];
77
+ // Fallback for older systems that reject sap.app/sourceTemplate/id with HTTP 400.
78
+ export const appListFieldsWithoutSourceTemplate = appListResultFields.filter((field) => field !== sourceTemplateIdField);
74
79
  //# sourceMappingURL=constants.js.map
@@ -26,8 +26,9 @@ export declare function extractZip(extractedProjectPath: string, fs: Editor): Pr
26
26
  * Downloads application files from the ABAP repository.
27
27
  *
28
28
  * @param {string} repoName - The repository name of the application.
29
+ * @returns {Promise<boolean>} - Resolves to false if no data was returned (such as on a legacy ABAP system), true otherwise.
29
30
  */
30
- export declare function downloadApp(repoName: string): Promise<void>;
31
+ export declare function downloadApp(repoName: string): Promise<boolean>;
31
32
  /**
32
33
  * Resolve a transport request for the given app/package.
33
34
  *
@@ -59,6 +59,7 @@ export async function extractZip(extractedProjectPath, fs) {
59
59
  * Downloads application files from the ABAP repository.
60
60
  *
61
61
  * @param {string} repoName - The repository name of the application.
62
+ * @returns {Promise<boolean>} - Resolves to false if no data was returned (such as on a legacy ABAP system), true otherwise.
62
63
  */
63
64
  export async function downloadApp(repoName) {
64
65
  const serviceProvider = PromptState.systemSelection?.connectedSystem?.serviceProvider;
@@ -66,9 +67,14 @@ export async function downloadApp(repoName) {
66
67
  ui5AbapRepository.log = RepoAppDownloadLogger.logger;
67
68
  RepoAppDownloadLogger.logger?.debug(`App download started: ${repoName}`);
68
69
  const downloadedAppPackage = await ui5AbapRepository.downloadFiles(repoName);
70
+ if (!downloadedAppPackage || downloadedAppPackage.length === 0) {
71
+ RepoAppDownloadLogger.logger?.error(t('error.appDownloadFailed'));
72
+ return false;
73
+ }
69
74
  RepoAppDownloadLogger.logger?.debug(`App download completed: ${repoName}`);
70
75
  // store downloaded package in prompt state
71
76
  PromptState.admZip = downloadedAppPackage;
77
+ return true;
72
78
  }
73
79
  /**
74
80
  * Resolve a transport request for the given app/package.
@@ -19,7 +19,7 @@ export declare const validateQfaJsonFile: (config: QfaJsonConfig) => boolean;
19
19
  */
20
20
  export declare const isValidPromptState: (targetFolder: string, appId?: string) => boolean;
21
21
  /**
22
- * Validates the app selection and handles app download if applicable.
22
+ * Validates the app selection and handles the app download, if applicable.
23
23
  *
24
24
  * @param answers - The selected app information.
25
25
  * @param appList - The list of available apps.
@@ -7,6 +7,8 @@ import { AppDownloadType } from '../app/types.js';
7
7
  import { downloadApp, hasQfaJson } from '../utils/download-utils.js';
8
8
  import { MessageType } from '@sap-devx/yeoman-ui-types';
9
9
  import { qfaJsonFileName } from '../utils/constants.js';
10
+ import { sendTelemetry, TelemetryHelper } from '@sap-ux/fiori-generator-shared';
11
+ import { EventName } from '../telemetryEvents/index.js';
10
12
  /**
11
13
  * Validates the metadata section of the app configuration.
12
14
  *
@@ -98,7 +100,15 @@ async function generateAppNotFoundHelpLink() {
98
100
  return ErrorHandler.getHelpLink(HELP_NODES.ADT_APP_NOT_FOUND_ERROR, ERROR_TYPE.INTERNAL_SERVER_ERROR, t('error.noAppsDeployed'));
99
101
  }
100
102
  /**
101
- * Validates the app selection and handles app download if applicable.
103
+ * Generates a help link for the "App Download Failed" error.
104
+ *
105
+ * @returns {Promise<ValidationLink>} - A promise resolving to a validation link for the error.
106
+ */
107
+ async function generateAppDownloadFailedHelpLink() {
108
+ return ErrorHandler.getHelpLink(HELP_NODES.ABAP_REPO_APP_DOWNLOAD_FAILED, ERROR_TYPE.INTERNAL_SERVER_ERROR, t('error.appDownloadFailed'));
109
+ }
110
+ /**
111
+ * Validates the app selection and handles the app download, if applicable.
102
112
  *
103
113
  * @param answers - The selected app information.
104
114
  * @param appList - The list of available apps.
@@ -119,7 +129,15 @@ export async function validateAppSelection(answers, appList, quickDeployedAppCon
119
129
  // Valid app selected, try to download
120
130
  if (answers?.appId) {
121
131
  try {
122
- await downloadApp(answers.repoName);
132
+ const downloadSucceeded = await downloadApp(answers.repoName);
133
+ if (!downloadSucceeded) {
134
+ if (downloadType === AppDownloadType.AbapRepository) {
135
+ await sendTelemetry(EventName.ABAP_REPO_DOWNLOAD_NO_FILES_RETURNED, TelemetryHelper.createTelemetryData({}) ?? {}).catch(() => {
136
+ // telemetry errors are non-fatal
137
+ });
138
+ }
139
+ return await generateAppDownloadFailedHelpLink();
140
+ }
123
141
  const isQfaJsonPresent = hasQfaJson();
124
142
  if (!isQfaJsonPresent && downloadType === AppDownloadType.ADTQuickDeploy) {
125
143
  appWizard?.showError(t('error.qfaJsonNotFound', { jsonFileName: qfaJsonFileName }), MessageType.notification);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sap-ux/repo-app-import-sub-generator",
3
3
  "description": "Generator to download LROP Fiori applications deployed from an ABAP repository.",
4
- "version": "1.2.60",
4
+ "version": "1.2.62",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://github.com/SAP/open-ux-tools.git",
@@ -27,11 +27,11 @@
27
27
  "yeoman-generator": "5.10.0",
28
28
  "@sap-ux/feature-toggle": "1.0.5",
29
29
  "@sap-ux/fiori-generator-shared": "1.2.16",
30
- "@sap-ux/inquirer-common": "1.0.41",
30
+ "@sap-ux/inquirer-common": "1.0.42",
31
31
  "@sap-ux/project-access": "2.2.0",
32
- "@sap-ux/odata-service-inquirer": "3.2.18",
33
- "@sap-ux/fiori-elements-writer": "3.1.57",
34
- "@sap-ux/fiori-freestyle-writer": "3.0.102",
32
+ "@sap-ux/odata-service-inquirer": "3.2.19",
33
+ "@sap-ux/fiori-elements-writer": "3.1.59",
34
+ "@sap-ux/fiori-freestyle-writer": "3.0.104",
35
35
  "@sap-ux/logger": "1.0.3",
36
36
  "@sap-ux/project-input-validator": "1.0.19",
37
37
  "@sap-ux/launch-config": "1.0.21",
@@ -42,7 +42,7 @@
42
42
  "@sap-ux/axios-extension": "2.1.0",
43
43
  "@sap-ux/store": "2.0.7",
44
44
  "@sap-ux/system-access": "1.0.14",
45
- "@sap-ux/guided-answers-helper": "1.0.1"
45
+ "@sap-ux/guided-answers-helper": "1.0.2"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@jest/globals": "30.4.1",