@sap-ux/create 1.1.4 → 1.2.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 +23 -1
- package/dist/cli/update/index.js +2 -0
- package/dist/cli/update/service.d.ts +9 -0
- package/dist/cli/update/service.js +244 -0
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -416,7 +416,7 @@ Command group for updating saved resources. A subcommand is required.
|
|
|
416
416
|
|
|
417
417
|
Usage: `npx --yes @sap-ux/create@latest update [subcommand] [options]`
|
|
418
418
|
|
|
419
|
-
The available subcommands are: `system`
|
|
419
|
+
The available subcommands are: `system` and `service-metadata`
|
|
420
420
|
|
|
421
421
|
|
|
422
422
|
--------------------------------
|
|
@@ -445,6 +445,28 @@ Options:
|
|
|
445
445
|
|
|
446
446
|
--------------------------------
|
|
447
447
|
|
|
448
|
+
## [`update service-metadata`](#update-service-metadata)
|
|
449
|
+
|
|
450
|
+
Refresh the local OData service metadata.xml from the live backend for a Fiori application.
|
|
451
|
+
Also fetches value-help (external) service metadata when available.
|
|
452
|
+
|
|
453
|
+
Only supported for Fiori applications connected to an EDMX backend. CAP applications are not supported,
|
|
454
|
+
as their service metadata is generated locally from the CDS model rather than fetched from a backend.
|
|
455
|
+
|
|
456
|
+
Example:
|
|
457
|
+
|
|
458
|
+
`npx --yes @sap-ux/create@latest update service-metadata /path/to/my-fiori-app`
|
|
459
|
+
|
|
460
|
+
`npx --yes @sap-ux/create@latest update service-metadata /path/to/my-fiori-app --simulate`
|
|
461
|
+
|
|
462
|
+
Options:
|
|
463
|
+
- `--service <name>` - Name of the data source in manifest.json (defaults to mainService or first service)
|
|
464
|
+
- `--no-value-help` - Skip fetching value-help (external) service metadata
|
|
465
|
+
- `-s, --simulate` - Simulate only. Do not write. Also sets `--verbose`.
|
|
466
|
+
- `-v, --verbose` - Show verbose information.
|
|
467
|
+
|
|
468
|
+
--------------------------------
|
|
469
|
+
|
|
448
470
|
## [`change`](#change)
|
|
449
471
|
|
|
450
472
|
Command group for changing existing SAP Fiori applications. A subcommand is required.
|
package/dist/cli/update/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { addSystemUpdateCommand } from './system.js';
|
|
3
|
+
import { addServiceUpdateCommand } from './service.js';
|
|
3
4
|
/**
|
|
4
5
|
* Return 'create-fiori update *' commands. Commands include also the handler action.
|
|
5
6
|
*
|
|
@@ -8,6 +9,7 @@ import { addSystemUpdateCommand } from './system.js';
|
|
|
8
9
|
export function getUpdateCommands() {
|
|
9
10
|
const updateCommands = new Command('update');
|
|
10
11
|
addSystemUpdateCommand(updateCommands);
|
|
12
|
+
addServiceUpdateCommand(updateCommands);
|
|
11
13
|
return updateCommands;
|
|
12
14
|
}
|
|
13
15
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
/**
|
|
3
|
+
* Add the "update service-metadata" subcommand to a passed command.
|
|
4
|
+
* Refreshes the local OData service metadata and value-help service metadata from the live backend.
|
|
5
|
+
*
|
|
6
|
+
* @param cmd - commander command to attach the service-metadata subcommand to
|
|
7
|
+
*/
|
|
8
|
+
export declare function addServiceUpdateCommand(cmd: Command): void;
|
|
9
|
+
//# sourceMappingURL=service.d.ts.map
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { AbapCloudEnvironment, AbapServiceProvider, createForAbapOnCloud, createForDestination, ODataVersion as CatalogODataVersion, TlsPatch } from '@sap-ux/axios-extension';
|
|
2
|
+
import { isAppStudio, WebIDEUsage } from '@sap-ux/btp-utils';
|
|
3
|
+
import { getExternalServiceReferences, OdataVersion, ServiceType, update as updateOdataService } from '@sap-ux/odata-service-writer';
|
|
4
|
+
import { createApplicationAccess, FileName } from '@sap-ux/project-access';
|
|
5
|
+
import { BackendSystemKey, getService } from '@sap-ux/store';
|
|
6
|
+
import { UI5Config } from '@sap-ux/ui5-config';
|
|
7
|
+
import { create as createStore } from 'mem-fs';
|
|
8
|
+
import { create as createEditor } from 'mem-fs-editor';
|
|
9
|
+
import { readFile } from 'node:fs/promises';
|
|
10
|
+
import { join, resolve } from 'node:path';
|
|
11
|
+
import { getLogger, setLogLevelVerbose, traceChanges } from '../../tracing/index.js';
|
|
12
|
+
import { validateBasePath } from '../../validation/index.js';
|
|
13
|
+
/**
|
|
14
|
+
* Creates an AbapServiceProvider for the given backend config.
|
|
15
|
+
* Returns null and logs an error if the required config is missing.
|
|
16
|
+
*
|
|
17
|
+
* @param backendConfig
|
|
18
|
+
* @param logger
|
|
19
|
+
*/
|
|
20
|
+
async function buildProvider(backendConfig, logger) {
|
|
21
|
+
if (isAppStudio()) {
|
|
22
|
+
if (!backendConfig.destination) {
|
|
23
|
+
logger.error(`No destination found in '${FileName.Ui5Yaml}'. Add a 'destination' entry to the backend configuration to connect in SAP Business Application Studio.`);
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
const destination = { Name: backendConfig.destination, WebIDEUsage: WebIDEUsage.ODATA_ABAP };
|
|
27
|
+
return createForDestination({}, destination);
|
|
28
|
+
}
|
|
29
|
+
const backendUrl = backendConfig.connectPath
|
|
30
|
+
? new URL(backendConfig.connectPath, backendConfig.url).href
|
|
31
|
+
: backendConfig.url;
|
|
32
|
+
const clientSuffix = backendConfig.client ? ` (client ${backendConfig.client})` : '';
|
|
33
|
+
const systemService = await getService({ entityName: 'system' });
|
|
34
|
+
if (!systemService) {
|
|
35
|
+
logger.error(`A stored connection configuration for backend system '${backendUrl}'${clientSuffix} cannot be found. Use 'npx @sap-ux/create@latest add system' to create a matching entry.`);
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const system = await systemService.read(new BackendSystemKey({ url: backendUrl, client: backendConfig.client }));
|
|
39
|
+
if (!system) {
|
|
40
|
+
logger.error(`No stored system found for URL '${backendUrl}'${clientSuffix}. Run 'sap-ux add system' first.`);
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const providerConfig = {
|
|
44
|
+
baseURL: backendConfig.url,
|
|
45
|
+
params: { 'sap-client': backendConfig.client },
|
|
46
|
+
...(system.username ? { auth: { username: system.username, password: system.password ?? '' } } : {})
|
|
47
|
+
};
|
|
48
|
+
if (TlsPatch.isPatchRequired(providerConfig.baseURL ?? '')) {
|
|
49
|
+
TlsPatch.apply();
|
|
50
|
+
}
|
|
51
|
+
if (system.authenticationType === 'reentranceTicket' || system.serviceKeys) {
|
|
52
|
+
return createForAbapOnCloud({
|
|
53
|
+
environment: AbapCloudEnvironment.EmbeddedSteampunk,
|
|
54
|
+
url: backendConfig.url
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
return new AbapServiceProvider(providerConfig);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Fetches external (value-help) services when requested.
|
|
61
|
+
* Returns undefined if not requested, none found, or the fetch fails.
|
|
62
|
+
*
|
|
63
|
+
* @param provider
|
|
64
|
+
* @param servicePath
|
|
65
|
+
* @param metadataXml
|
|
66
|
+
* @param fetchExternal
|
|
67
|
+
* @param logger
|
|
68
|
+
*/
|
|
69
|
+
async function fetchExternalServicesIfRequested(provider, servicePath, metadataXml, fetchExternal, logger) {
|
|
70
|
+
if (!fetchExternal) {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
const references = getExternalServiceReferences(servicePath, metadataXml);
|
|
74
|
+
if (references.length === 0) {
|
|
75
|
+
logger.debug('No external service references found in metadata');
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
logger.info(`Fetching ${references.length} external service(s)...`);
|
|
79
|
+
try {
|
|
80
|
+
const externalServices = await provider.fetchExternalServices(references);
|
|
81
|
+
logger.debug(`Fetched ${externalServices.length} external service(s)`);
|
|
82
|
+
return externalServices;
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
logger.warn(`Could not fetch external service metadata: ${error.message}. Continuing without external services.`);
|
|
86
|
+
logger.debug(error);
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Fetches the service's remote annotations from the backend catalog.
|
|
92
|
+
* Returns undefined when none are available or the fetch fails, so that a metadata refresh
|
|
93
|
+
* never deletes the existing local annotation files (see odata-service-writer guard).
|
|
94
|
+
*
|
|
95
|
+
* @param provider
|
|
96
|
+
* @param servicePath
|
|
97
|
+
* @param odataVersion
|
|
98
|
+
* @param logger
|
|
99
|
+
*/
|
|
100
|
+
async function fetchAnnotations(provider, servicePath, odataVersion, logger) {
|
|
101
|
+
// OData V4 embeds annotations in the metadata; the catalog returns none.
|
|
102
|
+
if (odataVersion === OdataVersion.v4) {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
const annotations = await provider.catalog(CatalogODataVersion.v2).getAnnotations({ path: servicePath });
|
|
107
|
+
if (annotations.length === 0) {
|
|
108
|
+
logger.debug('No remote annotations found for service');
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
logger.debug(`Fetched ${annotations.length} annotation source(s)`);
|
|
112
|
+
return annotations.map((annotation) => ({
|
|
113
|
+
technicalName: annotation.TechnicalName,
|
|
114
|
+
xml: annotation.Definitions
|
|
115
|
+
}));
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
logger.warn(`Could not fetch annotations: ${error.message}. Existing local annotation files will be left untouched.`);
|
|
119
|
+
logger.debug(error);
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Add the "update service-metadata" subcommand to a passed command.
|
|
125
|
+
* Refreshes the local OData service metadata and value-help service metadata from the live backend.
|
|
126
|
+
*
|
|
127
|
+
* @param cmd - commander command to attach the service-metadata subcommand to
|
|
128
|
+
*/
|
|
129
|
+
export function addServiceUpdateCommand(cmd) {
|
|
130
|
+
cmd.command('service-metadata <appPath>')
|
|
131
|
+
.description(`Refresh the local OData service metadata.xml from the live backend for a Fiori application.
|
|
132
|
+
Also fetches value-help (external) service metadata when available.
|
|
133
|
+
|
|
134
|
+
Only supported for Fiori applications connected to an EDMX backend. CAP applications are not supported,
|
|
135
|
+
as their service metadata is generated locally from the CDS model rather than fetched from a backend.
|
|
136
|
+
|
|
137
|
+
Example:
|
|
138
|
+
\`npx --yes @sap-ux/create@latest update service-metadata /path/to/my-fiori-app\`
|
|
139
|
+
\`npx --yes @sap-ux/create@latest update service-metadata /path/to/my-fiori-app --simulate\``)
|
|
140
|
+
.option('--service <name>', 'Name of the data source in manifest.json (defaults to mainService or first service)')
|
|
141
|
+
.option('--no-value-help', 'Skip fetching value-help (external) service metadata')
|
|
142
|
+
.option('-s, --simulate', 'Simulate only. Do not write. Also sets `--verbose`.')
|
|
143
|
+
.option('-v, --verbose', 'Show verbose information.')
|
|
144
|
+
.action(async (appPath, options) => {
|
|
145
|
+
if (options.verbose || options.simulate) {
|
|
146
|
+
setLogLevelVerbose();
|
|
147
|
+
}
|
|
148
|
+
await updateService(resolve(appPath), options.valueHelp !== false, !!options.simulate, options.service);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Refreshes the local OData service metadata for a Fiori application.
|
|
153
|
+
*
|
|
154
|
+
* @param appPath - absolute path to the Fiori application root
|
|
155
|
+
* @param fetchExternalServiceMetadata - whether to also fetch value-help service metadata
|
|
156
|
+
* @param simulate - dry run; trace changes but do not write to disk
|
|
157
|
+
* @param serviceNameOpt - name of the data source in manifest (defaults to mainService or first service)
|
|
158
|
+
*/
|
|
159
|
+
async function updateService(appPath, fetchExternalServiceMetadata, simulate, serviceNameOpt) {
|
|
160
|
+
const logger = getLogger();
|
|
161
|
+
try {
|
|
162
|
+
logger.debug(`Called update service-metadata for path '${appPath}'`);
|
|
163
|
+
await validateBasePath(appPath);
|
|
164
|
+
const appAccess = await createApplicationAccess(appPath);
|
|
165
|
+
if (appAccess.projectType !== 'EDMXBackend') {
|
|
166
|
+
logger.error('Update service-metadata is only supported for Fiori applications connected to an EDMX backend');
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const serviceName = serviceNameOpt ?? appAccess.app.mainService ?? Object.keys(appAccess.app.services)[0];
|
|
170
|
+
if (!serviceName) {
|
|
171
|
+
logger.error(`No OData service found in manifest for app at '${appPath}'`);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (serviceNameOpt && !appAccess.app.services[serviceNameOpt]) {
|
|
175
|
+
logger.error(`Service '${serviceNameOpt}' not found in manifest. Available: ${Object.keys(appAccess.app.services).join(', ')}`);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const servicePath = appAccess.app.services[serviceName]?.uri;
|
|
179
|
+
if (!servicePath) {
|
|
180
|
+
logger.error(`Service '${serviceName}' has no URI defined in manifest`);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const manifest = await appAccess.readManifest();
|
|
184
|
+
const odataVersionRaw = manifest['sap.app']?.dataSources?.[serviceName]?.settings?.odataVersion;
|
|
185
|
+
const odataVersion = odataVersionRaw?.startsWith('4') ? OdataVersion.v4 : OdataVersion.v2;
|
|
186
|
+
logger.debug(`Service '${serviceName}' at path '${servicePath}' (OData ${odataVersion})`);
|
|
187
|
+
const ui5YamlContent = await readFile(join(appPath, FileName.Ui5Yaml), 'utf-8');
|
|
188
|
+
const ui5Config = await UI5Config.newInstance(ui5YamlContent);
|
|
189
|
+
const backendConfig = ui5Config.getBackendConfigsFromFioriToolsProxyMiddleware()[0];
|
|
190
|
+
if (!backendConfig) {
|
|
191
|
+
logger.error(`No backend configuration found in '${FileName.Ui5Yaml}' for app at '${appPath}'`);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
const provider = await buildProvider(backendConfig, logger);
|
|
195
|
+
if (!provider) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
logger.info(`Fetching metadata for service '${serviceName}'...`);
|
|
199
|
+
let metadataXml;
|
|
200
|
+
try {
|
|
201
|
+
metadataXml = await provider.service(servicePath).metadata();
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
if (isAppStudio() && backendConfig.destination) {
|
|
205
|
+
throw new Error(`The service metadata is returning an error. Please check that the destination '${backendConfig.destination}' exists and the service is accessible.`);
|
|
206
|
+
}
|
|
207
|
+
throw error;
|
|
208
|
+
}
|
|
209
|
+
logger.debug(`Received metadata for service '${serviceName}'`);
|
|
210
|
+
const externalServices = await fetchExternalServicesIfRequested(provider, servicePath, metadataXml, fetchExternalServiceMetadata, logger);
|
|
211
|
+
const annotations = await fetchAnnotations(provider, servicePath, odataVersion, logger);
|
|
212
|
+
const serviceData = {
|
|
213
|
+
name: serviceName,
|
|
214
|
+
path: servicePath,
|
|
215
|
+
version: odataVersion,
|
|
216
|
+
type: ServiceType.EDMX,
|
|
217
|
+
metadata: metadataXml,
|
|
218
|
+
annotations,
|
|
219
|
+
externalServices,
|
|
220
|
+
previewSettings: {
|
|
221
|
+
path: backendConfig.path,
|
|
222
|
+
url: backendConfig.url,
|
|
223
|
+
client: backendConfig.client,
|
|
224
|
+
destination: backendConfig.destination,
|
|
225
|
+
connectPath: backendConfig.connectPath
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
const memStore = createStore();
|
|
229
|
+
const fs = createEditor(memStore);
|
|
230
|
+
// updateMiddlewares = false: refresh metadata.xml, annotation files and manifest dataSources only.
|
|
231
|
+
// The ui5*.yaml middlewares are intentionally left untouched by a metadata re-sync.
|
|
232
|
+
await updateOdataService(appPath, serviceData, fs, false);
|
|
233
|
+
await traceChanges(fs);
|
|
234
|
+
if (!simulate) {
|
|
235
|
+
await new Promise((resolve, reject) => fs.commit((err) => (err ? reject(err) : resolve())));
|
|
236
|
+
logger.info('Metadata updated.');
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
logger.error(error.message);
|
|
241
|
+
logger.debug(error);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
//# sourceMappingURL=service.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.
|
|
4
|
+
"version": "1.2.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "https://github.com/SAP/open-ux-tools.git",
|
|
@@ -33,23 +33,23 @@
|
|
|
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.
|
|
36
|
+
"@sap-ux/abap-deploy-config-inquirer": "2.0.36",
|
|
37
37
|
"@sap-ux/abap-deploy-config-writer": "1.0.20",
|
|
38
|
-
"@sap-ux/adp-tooling": "1.0.
|
|
38
|
+
"@sap-ux/adp-tooling": "1.0.42",
|
|
39
39
|
"@sap-ux/btp-utils": "2.0.6",
|
|
40
40
|
"@sap-ux/app-config-writer": "1.1.15",
|
|
41
41
|
"@sap-ux/cap-config-writer": "1.0.28",
|
|
42
|
-
"@sap-ux/inquirer-common": "1.0.
|
|
42
|
+
"@sap-ux/inquirer-common": "1.0.31",
|
|
43
43
|
"@sap-ux/logger": "1.0.3",
|
|
44
44
|
"@sap-ux/mockserver-config-writer": "1.0.15",
|
|
45
|
-
"@sap-ux/odata-service-writer": "1.0.
|
|
46
|
-
"@sap-ux/preview-middleware": "1.1.
|
|
45
|
+
"@sap-ux/odata-service-writer": "1.0.18",
|
|
46
|
+
"@sap-ux/preview-middleware": "1.1.3",
|
|
47
47
|
"@sap-ux/project-access": "2.1.10",
|
|
48
48
|
"@sap-ux/project-input-validator": "1.0.15",
|
|
49
49
|
"@sap-ux/system-access": "1.0.10",
|
|
50
50
|
"@sap-ux/store": "2.0.6",
|
|
51
51
|
"@sap-ux/ui5-config": "1.0.7",
|
|
52
|
-
"@sap-ux/flp-config-inquirer": "1.0.
|
|
52
|
+
"@sap-ux/flp-config-inquirer": "1.0.42",
|
|
53
53
|
"@sap-ux/nodejs-utils": "1.0.9",
|
|
54
54
|
"@sap-ux/axios-extension": "2.0.8"
|
|
55
55
|
},
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"@types/mem-fs": "1.1.2",
|
|
61
61
|
"@types/mem-fs-editor": "7.0.1",
|
|
62
62
|
"@types/prompts": "2.4.9",
|
|
63
|
-
"@sap-ux/inquirer-common": "1.0.
|
|
63
|
+
"@sap-ux/inquirer-common": "1.0.31",
|
|
64
64
|
"@sap-ux/store": "2.0.6"
|
|
65
65
|
},
|
|
66
66
|
"scripts": {
|