@sap-ux/project-access 1.23.0 → 1.24.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.
@@ -0,0 +1,2 @@
1
+ export { execNpmCommand } from './npm-command';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.execNpmCommand = void 0;
4
+ var npm_command_1 = require("./npm-command");
5
+ Object.defineProperty(exports, "execNpmCommand", { enumerable: true, get: function () { return npm_command_1.execNpmCommand; } });
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,15 @@
1
+ import type { Logger } from '@sap-ux/logger';
2
+ /**
3
+ * Execute an npm command.
4
+ *
5
+ * @param commandArguments - command arguments as array, e.g. ['install', '@sap/ux-specification@1.2.3']
6
+ * @param [options] - optional options
7
+ * @param [options.cwd] - optional current working directory
8
+ * @param [options.logger] - optional logger instance
9
+ * @returns - stdout of the command
10
+ */
11
+ export declare function execNpmCommand(commandArguments: string[], options?: {
12
+ cwd?: string;
13
+ logger?: Logger;
14
+ }): Promise<string>;
15
+ //# sourceMappingURL=npm-command.d.ts.map
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.execNpmCommand = void 0;
4
+ const child_process_1 = require("child_process");
5
+ /**
6
+ * Execute an npm command.
7
+ *
8
+ * @param commandArguments - command arguments as array, e.g. ['install', '@sap/ux-specification@1.2.3']
9
+ * @param [options] - optional options
10
+ * @param [options.cwd] - optional current working directory
11
+ * @param [options.logger] - optional logger instance
12
+ * @returns - stdout of the command
13
+ */
14
+ async function execNpmCommand(commandArguments, options) {
15
+ return new Promise((resolve, reject) => {
16
+ const isWin = process.platform.startsWith('win');
17
+ // Command to execute npm is platform specific, 'npm.cmd' on windows, 'npm' otherwise
18
+ const npmCommand = isWin ? 'npm.cmd' : 'npm';
19
+ // Platform specific spawn options, 'windowsVerbatimArguments' and 'shell' true on windows
20
+ const defaultSpawnOptions = isWin ? { windowsVerbatimArguments: true, shell: true } : {};
21
+ const logger = options?.logger;
22
+ const cwd = options?.cwd;
23
+ const spawnOptions = typeof cwd === 'string' ? { ...defaultSpawnOptions, cwd } : defaultSpawnOptions;
24
+ const spawnProcess = (0, child_process_1.spawn)(npmCommand, commandArguments, spawnOptions);
25
+ let stdOut = '';
26
+ let stdErr = '';
27
+ spawnProcess.stdout.on('data', (data) => {
28
+ stdOut += data.toString();
29
+ });
30
+ spawnProcess.stderr.on('data', (data) => {
31
+ stdErr += data.toString();
32
+ });
33
+ spawnProcess.on('exit', () => {
34
+ if (logger) {
35
+ const commandString = `${npmCommand} ${commandArguments.join(' ')}`;
36
+ if (stdErr) {
37
+ logger.error(`Command '${commandString}' not successful, stderr: ${stdErr}`);
38
+ }
39
+ if (stdOut) {
40
+ logger.info(`Command '${commandString}' successful, stdout: ${stdOut}`);
41
+ }
42
+ }
43
+ resolve(stdOut);
44
+ });
45
+ spawnProcess.on('error', (error) => {
46
+ logger?.error(`Error executing npm command '${npmCommand} ${commandArguments.join(' ')}': ${error}`);
47
+ reject(error);
48
+ });
49
+ });
50
+ }
51
+ exports.execNpmCommand = execNpmCommand;
52
+ //# sourceMappingURL=npm-command.js.map
@@ -8,6 +8,7 @@ export declare const FileName: {
8
8
  readonly MtaYaml: "mta.yaml";
9
9
  readonly Package: "package.json";
10
10
  readonly Pom: "pom.xml";
11
+ readonly SpecificationDistTags: "specification-dist-tags.json";
11
12
  readonly Tsconfig: "tsconfig.json";
12
13
  readonly Ui5Yaml: "ui5.yaml";
13
14
  readonly Ui5LocalYaml: "ui5-local.yaml";
@@ -16,6 +17,7 @@ export declare const FileName: {
16
17
  };
17
18
  export declare const DirName: {
18
19
  readonly Changes: "changes";
20
+ readonly ModuleCache: "module-cache";
19
21
  readonly Schemas: ".schemas";
20
22
  readonly Pages: "pages";
21
23
  readonly Webapp: "webapp";
@@ -44,4 +46,12 @@ export declare const FioriToolsSettings: {
44
46
  export declare const SchemaName: {
45
47
  readonly Ftfs: "ftfs";
46
48
  };
49
+ /**
50
+ * Directory where fiori tools settings are stored
51
+ */
52
+ export declare const fioriToolsDirectory: string;
53
+ /**
54
+ * Directory where modules are cached
55
+ */
56
+ export declare const moduleCacheRoot: string;
47
57
  //# sourceMappingURL=constants.d.ts.map
package/dist/constants.js CHANGED
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SchemaName = exports.FioriToolsSettings = exports.DirName = exports.FileName = void 0;
3
+ exports.moduleCacheRoot = exports.fioriToolsDirectory = exports.SchemaName = exports.FioriToolsSettings = exports.DirName = exports.FileName = void 0;
4
+ const os_1 = require("os");
5
+ const path_1 = require("path");
4
6
  exports.FileName = {
5
7
  AdaptationConfig: 'config.json',
6
8
  CapJavaApplicationYaml: 'application.yaml',
@@ -11,6 +13,7 @@ exports.FileName = {
11
13
  MtaYaml: 'mta.yaml',
12
14
  Package: 'package.json',
13
15
  Pom: 'pom.xml',
16
+ SpecificationDistTags: 'specification-dist-tags.json',
14
17
  Tsconfig: 'tsconfig.json',
15
18
  Ui5Yaml: 'ui5.yaml',
16
19
  Ui5LocalYaml: 'ui5-local.yaml',
@@ -19,6 +22,7 @@ exports.FileName = {
19
22
  };
20
23
  exports.DirName = {
21
24
  Changes: 'changes',
25
+ ModuleCache: 'module-cache',
22
26
  Schemas: '.schemas',
23
27
  Pages: 'pages',
24
28
  Webapp: 'webapp',
@@ -47,4 +51,12 @@ exports.FioriToolsSettings = {
47
51
  exports.SchemaName = {
48
52
  Ftfs: 'ftfs'
49
53
  };
54
+ /**
55
+ * Directory where fiori tools settings are stored
56
+ */
57
+ exports.fioriToolsDirectory = (0, path_1.join)((0, os_1.homedir)(), exports.FioriToolsSettings.dir);
58
+ /**
59
+ * Directory where modules are cached
60
+ */
61
+ exports.moduleCacheRoot = (0, path_1.join)(exports.fioriToolsDirectory, exports.DirName.ModuleCache);
50
62
  //# sourceMappingURL=constants.js.map
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { FileName, DirName, FioriToolsSettings } from './constants';
2
2
  export { getFilePaths } from './file';
3
- export { addPackageDevDependency, createApplicationAccess, createProjectAccess, findAllApps, findCapProjects, findFioriArtifacts, findProjectRoot, getAppRootFromWebappPath, getAppProgrammingLanguage, getAppType, getCapCustomPaths, getCapEnvironment, getCapModelAndServices, getCapProjectType, getCdsFiles, getCdsRoots, getCdsServices, getCapI18nFolderNames, getI18nPropertiesPaths, getMinUI5VersionFromManifest, getMinUI5VersionAsArray, getMinimumUI5Version, getMtaPath, getNodeModulesPath, getProject, getProjectType, getWebappPath, isCapProject, isCapJavaProject, isCapNodeJsProject, loadModuleFromProject, readCapServiceMetadataEdmx, readUi5Yaml, toReferenceUri, filterDataSourcesByType, clearCdsModuleCache, updatePackageScript } from './project';
3
+ export { addPackageDevDependency, clearCdsModuleCache, createApplicationAccess, createProjectAccess, findAllApps, findCapProjects, findFioriArtifacts, findProjectRoot, getAppRootFromWebappPath, getAppProgrammingLanguage, getAppType, getCapCustomPaths, getCapEnvironment, getCapModelAndServices, getCapProjectType, getCdsFiles, getCdsRoots, getCdsServices, getCapI18nFolderNames, getSpecification, getI18nPropertiesPaths, getMinUI5VersionFromManifest, getMinUI5VersionAsArray, getMinimumUI5Version, getMtaPath, getNodeModulesPath, getProject, getProjectType, getWebappPath, isCapProject, isCapJavaProject, isCapNodeJsProject, loadModuleFromProject, readCapServiceMetadataEdmx, readUi5Yaml, refreshSpecificationDistTags, toReferenceUri, filterDataSourcesByType, updatePackageScript } from './project';
4
4
  export * from './types';
5
5
  export * from './library';
6
6
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.updatePackageScript = exports.clearCdsModuleCache = exports.filterDataSourcesByType = exports.toReferenceUri = exports.readUi5Yaml = exports.readCapServiceMetadataEdmx = exports.loadModuleFromProject = exports.isCapNodeJsProject = exports.isCapJavaProject = exports.isCapProject = exports.getWebappPath = exports.getProjectType = exports.getProject = exports.getNodeModulesPath = exports.getMtaPath = exports.getMinimumUI5Version = exports.getMinUI5VersionAsArray = exports.getMinUI5VersionFromManifest = exports.getI18nPropertiesPaths = exports.getCapI18nFolderNames = exports.getCdsServices = exports.getCdsRoots = exports.getCdsFiles = exports.getCapProjectType = exports.getCapModelAndServices = exports.getCapEnvironment = exports.getCapCustomPaths = exports.getAppType = exports.getAppProgrammingLanguage = exports.getAppRootFromWebappPath = exports.findProjectRoot = exports.findFioriArtifacts = exports.findCapProjects = exports.findAllApps = exports.createProjectAccess = exports.createApplicationAccess = exports.addPackageDevDependency = exports.getFilePaths = exports.FioriToolsSettings = exports.DirName = exports.FileName = void 0;
17
+ exports.updatePackageScript = exports.filterDataSourcesByType = exports.toReferenceUri = exports.refreshSpecificationDistTags = exports.readUi5Yaml = exports.readCapServiceMetadataEdmx = exports.loadModuleFromProject = exports.isCapNodeJsProject = exports.isCapJavaProject = exports.isCapProject = exports.getWebappPath = exports.getProjectType = exports.getProject = exports.getNodeModulesPath = exports.getMtaPath = exports.getMinimumUI5Version = exports.getMinUI5VersionAsArray = exports.getMinUI5VersionFromManifest = exports.getI18nPropertiesPaths = exports.getSpecification = exports.getCapI18nFolderNames = exports.getCdsServices = exports.getCdsRoots = exports.getCdsFiles = exports.getCapProjectType = exports.getCapModelAndServices = exports.getCapEnvironment = exports.getCapCustomPaths = exports.getAppType = exports.getAppProgrammingLanguage = exports.getAppRootFromWebappPath = exports.findProjectRoot = exports.findFioriArtifacts = exports.findCapProjects = exports.findAllApps = exports.createProjectAccess = exports.createApplicationAccess = exports.clearCdsModuleCache = exports.addPackageDevDependency = exports.getFilePaths = exports.FioriToolsSettings = exports.DirName = exports.FileName = void 0;
18
18
  var constants_1 = require("./constants");
19
19
  Object.defineProperty(exports, "FileName", { enumerable: true, get: function () { return constants_1.FileName; } });
20
20
  Object.defineProperty(exports, "DirName", { enumerable: true, get: function () { return constants_1.DirName; } });
@@ -23,6 +23,7 @@ var file_1 = require("./file");
23
23
  Object.defineProperty(exports, "getFilePaths", { enumerable: true, get: function () { return file_1.getFilePaths; } });
24
24
  var project_1 = require("./project");
25
25
  Object.defineProperty(exports, "addPackageDevDependency", { enumerable: true, get: function () { return project_1.addPackageDevDependency; } });
26
+ Object.defineProperty(exports, "clearCdsModuleCache", { enumerable: true, get: function () { return project_1.clearCdsModuleCache; } });
26
27
  Object.defineProperty(exports, "createApplicationAccess", { enumerable: true, get: function () { return project_1.createApplicationAccess; } });
27
28
  Object.defineProperty(exports, "createProjectAccess", { enumerable: true, get: function () { return project_1.createProjectAccess; } });
28
29
  Object.defineProperty(exports, "findAllApps", { enumerable: true, get: function () { return project_1.findAllApps; } });
@@ -40,6 +41,7 @@ Object.defineProperty(exports, "getCdsFiles", { enumerable: true, get: function
40
41
  Object.defineProperty(exports, "getCdsRoots", { enumerable: true, get: function () { return project_1.getCdsRoots; } });
41
42
  Object.defineProperty(exports, "getCdsServices", { enumerable: true, get: function () { return project_1.getCdsServices; } });
42
43
  Object.defineProperty(exports, "getCapI18nFolderNames", { enumerable: true, get: function () { return project_1.getCapI18nFolderNames; } });
44
+ Object.defineProperty(exports, "getSpecification", { enumerable: true, get: function () { return project_1.getSpecification; } });
43
45
  Object.defineProperty(exports, "getI18nPropertiesPaths", { enumerable: true, get: function () { return project_1.getI18nPropertiesPaths; } });
44
46
  Object.defineProperty(exports, "getMinUI5VersionFromManifest", { enumerable: true, get: function () { return project_1.getMinUI5VersionFromManifest; } });
45
47
  Object.defineProperty(exports, "getMinUI5VersionAsArray", { enumerable: true, get: function () { return project_1.getMinUI5VersionAsArray; } });
@@ -55,9 +57,9 @@ Object.defineProperty(exports, "isCapNodeJsProject", { enumerable: true, get: fu
55
57
  Object.defineProperty(exports, "loadModuleFromProject", { enumerable: true, get: function () { return project_1.loadModuleFromProject; } });
56
58
  Object.defineProperty(exports, "readCapServiceMetadataEdmx", { enumerable: true, get: function () { return project_1.readCapServiceMetadataEdmx; } });
57
59
  Object.defineProperty(exports, "readUi5Yaml", { enumerable: true, get: function () { return project_1.readUi5Yaml; } });
60
+ Object.defineProperty(exports, "refreshSpecificationDistTags", { enumerable: true, get: function () { return project_1.refreshSpecificationDistTags; } });
58
61
  Object.defineProperty(exports, "toReferenceUri", { enumerable: true, get: function () { return project_1.toReferenceUri; } });
59
62
  Object.defineProperty(exports, "filterDataSourcesByType", { enumerable: true, get: function () { return project_1.filterDataSourcesByType; } });
60
- Object.defineProperty(exports, "clearCdsModuleCache", { enumerable: true, get: function () { return project_1.clearCdsModuleCache; } });
61
63
  Object.defineProperty(exports, "updatePackageScript", { enumerable: true, get: function () { return project_1.updatePackageScript; } });
62
64
  __exportStar(require("./types"), exports);
63
65
  __exportStar(require("./library"), exports);
@@ -1,4 +1,4 @@
1
- import type { ApplicationAccess, ProjectAccess } from '../types';
1
+ import type { ApplicationAccess, ApplicationAccessOptions, ProjectAccess, ProjectAccessOptions } from '../types';
2
2
  import type { Editor } from 'mem-fs-editor';
3
3
  /**
4
4
  * Create an instance of ApplicationAccess that contains information about the application, like paths and services.
@@ -9,12 +9,13 @@ import type { Editor } from 'mem-fs-editor';
9
9
  * When calling this function, adding or removing a CDS file in memory or changing CDS configuration will not be considered until present on real file system.
10
10
  * @returns - Instance of ApplicationAccess that contains information about the application, like paths and services
11
11
  */
12
- export declare function createApplicationAccess(appRoot: string, fs?: Editor): Promise<ApplicationAccess>;
12
+ export declare function createApplicationAccess(appRoot: string, fs?: Editor | ApplicationAccessOptions): Promise<ApplicationAccess>;
13
13
  /**
14
14
  * Create an instance of ProjectAccess that contains information about the project, like applications, paths, services.
15
15
  *
16
16
  * @param root - Project root path
17
+ * @param options - optional options, e.g. logger instance.
17
18
  * @returns - Instance of ProjectAccess that contains information about the project
18
19
  */
19
- export declare function createProjectAccess(root: string): Promise<ProjectAccess>;
20
+ export declare function createProjectAccess(root: string, options?: ProjectAccessOptions): Promise<ProjectAccess>;
20
21
  //# sourceMappingURL=access.d.ts.map
@@ -7,24 +7,26 @@ const info_1 = require("./info");
7
7
  const search_1 = require("./search");
8
8
  const file_1 = require("../file");
9
9
  const constants_1 = require("../constants");
10
+ const specification_1 = require("./specification");
10
11
  /**
11
12
  *
12
13
  */
13
14
  class ApplicationAccessImp {
14
15
  _project;
15
16
  appId;
16
- fs;
17
+ options;
17
18
  /**
18
19
  * Constructor for ApplicationAccess.
19
20
  *
20
21
  * @param _project - Project structure
21
22
  * @param appId - Application ID
22
- * @param fs optional `mem-fs-editor` instance.
23
+ * @param options - optional options, see below
24
+ * @param options.fs - optional `mem-fs-editor` instance
23
25
  */
24
- constructor(_project, appId, fs) {
26
+ constructor(_project, appId, options) {
25
27
  this._project = _project;
26
28
  this.appId = appId;
27
- this.fs = fs;
29
+ this.options = options;
28
30
  }
29
31
  /**
30
32
  * Returns the application structure.
@@ -55,7 +57,7 @@ class ApplicationAccessImp {
55
57
  * ```
56
58
  */
57
59
  createAnnotationI18nEntries(newEntries) {
58
- return (0, i18n_1.createAnnotationI18nEntries)(this.project.root, this.app.manifest, this.app.i18n, newEntries, this.fs);
60
+ return (0, i18n_1.createAnnotationI18nEntries)(this.project.root, this.app.manifest, this.app.i18n, newEntries, this.options?.fs);
59
61
  }
60
62
  /**
61
63
  * Maintains new translation entries in an existing i18n file or in a new i18n properties file if it does not exist.
@@ -79,7 +81,7 @@ class ApplicationAccessImp {
79
81
  * ```
80
82
  */
81
83
  createUI5I18nEntries(newEntries, modelKey = 'i18n') {
82
- return (0, i18n_1.createUI5I18nEntries)(this.project.root, this.app.manifest, this.app.i18n, newEntries, modelKey, this.fs);
84
+ return (0, i18n_1.createUI5I18nEntries)(this.project.root, this.app.manifest, this.app.i18n, newEntries, modelKey, this.options?.fs);
83
85
  }
84
86
  /**
85
87
  * Maintains new translation entries in an existing i18n file or in a new i18n properties file if it does not exist.
@@ -89,7 +91,7 @@ class ApplicationAccessImp {
89
91
  * @description If `i18n` entry is missing from `"sap.app":{}`, default `i18n/i18n.properties` is used. Update of `manifest.json` file is not needed.
90
92
  */
91
93
  createManifestI18nEntries(newEntries) {
92
- return (0, i18n_1.createManifestI18nEntries)(this.project.root, this.app.i18n, newEntries, this.fs);
94
+ return (0, i18n_1.createManifestI18nEntries)(this.project.root, this.app.i18n, newEntries, this.options?.fs);
93
95
  }
94
96
  /**
95
97
  * Maintains new translation entries in CAP i18n files.
@@ -99,7 +101,7 @@ class ApplicationAccessImp {
99
101
  * @returns boolean or exception
100
102
  */
101
103
  createCapI18nEntries(filePath, newI18nEntries) {
102
- return (0, i18n_1.createCapI18nEntries)(this.project.root, filePath, newI18nEntries, this.fs);
104
+ return (0, i18n_1.createCapI18nEntries)(this.project.root, filePath, newI18nEntries, this.options?.fs);
103
105
  }
104
106
  /**
105
107
  * Return the application id of this app, which is the relative path from the project root
@@ -124,7 +126,7 @@ class ApplicationAccessImp {
124
126
  * @returns i18n bundles or exception captured in optional errors object
125
127
  */
126
128
  getI18nBundles() {
127
- return (0, i18n_1.getI18nBundles)(this.project.root, this.app.i18n, this.project.projectType, this.fs);
129
+ return (0, i18n_1.getI18nBundles)(this.project.root, this.app.i18n, this.project.projectType, this.options?.fs);
128
130
  }
129
131
  /**
130
132
  * Return absolute paths to i18n.properties files from manifest.
@@ -134,6 +136,14 @@ class ApplicationAccessImp {
134
136
  getI18nPropertiesPaths() {
135
137
  return (0, i18n_1.getI18nPropertiesPaths)(this.app.manifest);
136
138
  }
139
+ /**
140
+ * Return an instance of @sap/ux-specification specific to the application version.
141
+ *
142
+ * @returns - instance of @sap/ux-specification
143
+ */
144
+ async getSpecification() {
145
+ return (0, specification_1.getSpecification)(this.app.appRoot);
146
+ }
137
147
  /**
138
148
  * Updates package.json file asynchronously by keeping the previous indentation.
139
149
  *
@@ -183,13 +193,16 @@ class ApplicationAccessImp {
183
193
  */
184
194
  class ProjectAccessImp {
185
195
  _project;
196
+ options;
186
197
  /**
187
198
  * Constructor for ProjectAccess.
188
199
  *
189
200
  * @param _project - Project structure
201
+ * @param options - optional options, like logger
190
202
  */
191
- constructor(_project) {
203
+ constructor(_project, options) {
192
204
  this._project = _project;
205
+ this.options = options;
193
206
  }
194
207
  /**
195
208
  * Returns list of application IDs.
@@ -209,7 +222,7 @@ class ProjectAccessImp {
209
222
  if (!this.project.apps[appId]) {
210
223
  throw new Error(`Could not find app with id ${appId}`);
211
224
  }
212
- return new ApplicationAccessImp(this.project, appId);
225
+ return new ApplicationAccessImp(this.project, appId, this.options);
213
226
  }
214
227
  /**
215
228
  * Project structure.
@@ -236,6 +249,15 @@ class ProjectAccessImp {
236
249
  return this.project.root;
237
250
  }
238
251
  }
252
+ /**
253
+ * Type guard for Editor or ApplicationAccessOptions.
254
+ *
255
+ * @param argument - argument to check
256
+ * @returns true if argument is Editor, false if it is ApplicationAccessOptions
257
+ */
258
+ function isEditor(argument) {
259
+ return argument.commit !== undefined;
260
+ }
239
261
  /**
240
262
  * Create an instance of ApplicationAccess that contains information about the application, like paths and services.
241
263
  *
@@ -254,7 +276,11 @@ async function createApplicationAccess(appRoot, fs) {
254
276
  }
255
277
  const project = await (0, info_1.getProject)(app.projectRoot);
256
278
  const appId = (0, path_1.relative)(project.root, appRoot);
257
- return new ApplicationAccessImp(project, appId, fs);
279
+ let options;
280
+ if (fs) {
281
+ options = isEditor(fs) ? { fs } : fs;
282
+ }
283
+ return new ApplicationAccessImp(project, appId, options);
258
284
  }
259
285
  catch (error) {
260
286
  throw Error(`Error when creating application access for ${appRoot}: ${error}`);
@@ -265,12 +291,13 @@ exports.createApplicationAccess = createApplicationAccess;
265
291
  * Create an instance of ProjectAccess that contains information about the project, like applications, paths, services.
266
292
  *
267
293
  * @param root - Project root path
294
+ * @param options - optional options, e.g. logger instance.
268
295
  * @returns - Instance of ProjectAccess that contains information about the project
269
296
  */
270
- async function createProjectAccess(root) {
297
+ async function createProjectAccess(root, options) {
271
298
  try {
272
299
  const project = await (0, info_1.getProject)(root);
273
- const projectAccess = new ProjectAccessImp(project);
300
+ const projectAccess = new ProjectAccessImp(project, options);
274
301
  return projectAccess;
275
302
  }
276
303
  catch (error) {
@@ -9,4 +9,5 @@ export { getWebappPath, readUi5Yaml } from './ui5-config';
9
9
  export { getMtaPath } from './mta';
10
10
  export { createApplicationAccess, createProjectAccess } from './access';
11
11
  export { updatePackageScript } from './script';
12
+ export { getSpecification, refreshSpecificationDistTags } from './specification';
12
13
  //# sourceMappingURL=index.d.ts.map
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.updatePackageScript = exports.createProjectAccess = exports.createApplicationAccess = exports.getMtaPath = exports.readUi5Yaml = exports.getWebappPath = exports.getAppRootFromWebappPath = exports.findProjectRoot = exports.findFioriArtifacts = exports.findCapProjects = exports.findAllApps = exports.loadModuleFromProject = exports.getProjectType = exports.getProject = exports.getMinimumUI5Version = exports.getMinUI5VersionAsArray = exports.getMinUI5VersionFromManifest = exports.getAppType = exports.getAppProgrammingLanguage = exports.getI18nPropertiesPaths = exports.getCapI18nFolderNames = exports.getNodeModulesPath = exports.addPackageDevDependency = exports.filterDataSourcesByType = exports.clearCdsModuleCache = exports.toReferenceUri = exports.readCapServiceMetadataEdmx = exports.getCapEnvironment = exports.isCapNodeJsProject = exports.isCapJavaProject = exports.isCapProject = exports.getCdsServices = exports.getCdsRoots = exports.getCdsFiles = exports.getCapProjectType = exports.getCapModelAndServices = exports.getCapCustomPaths = void 0;
3
+ exports.refreshSpecificationDistTags = exports.getSpecification = exports.updatePackageScript = exports.createProjectAccess = exports.createApplicationAccess = exports.getMtaPath = exports.readUi5Yaml = exports.getWebappPath = exports.getAppRootFromWebappPath = exports.findProjectRoot = exports.findFioriArtifacts = exports.findCapProjects = exports.findAllApps = exports.loadModuleFromProject = exports.getProjectType = exports.getProject = exports.getMinimumUI5Version = exports.getMinUI5VersionAsArray = exports.getMinUI5VersionFromManifest = exports.getAppType = exports.getAppProgrammingLanguage = exports.getI18nPropertiesPaths = exports.getCapI18nFolderNames = exports.getNodeModulesPath = exports.addPackageDevDependency = exports.filterDataSourcesByType = exports.clearCdsModuleCache = exports.toReferenceUri = exports.readCapServiceMetadataEdmx = exports.getCapEnvironment = exports.isCapNodeJsProject = exports.isCapJavaProject = exports.isCapProject = exports.getCdsServices = exports.getCdsRoots = exports.getCdsFiles = exports.getCapProjectType = exports.getCapModelAndServices = exports.getCapCustomPaths = void 0;
4
4
  var cap_1 = require("./cap");
5
5
  Object.defineProperty(exports, "getCapCustomPaths", { enumerable: true, get: function () { return cap_1.getCapCustomPaths; } });
6
6
  Object.defineProperty(exports, "getCapModelAndServices", { enumerable: true, get: function () { return cap_1.getCapModelAndServices; } });
@@ -49,4 +49,7 @@ Object.defineProperty(exports, "createApplicationAccess", { enumerable: true, ge
49
49
  Object.defineProperty(exports, "createProjectAccess", { enumerable: true, get: function () { return access_1.createProjectAccess; } });
50
50
  var script_1 = require("./script");
51
51
  Object.defineProperty(exports, "updatePackageScript", { enumerable: true, get: function () { return script_1.updatePackageScript; } });
52
+ var specification_1 = require("./specification");
53
+ Object.defineProperty(exports, "getSpecification", { enumerable: true, get: function () { return specification_1.getSpecification; } });
54
+ Object.defineProperty(exports, "refreshSpecificationDistTags", { enumerable: true, get: function () { return specification_1.refreshSpecificationDistTags; } });
52
55
  //# sourceMappingURL=index.js.map
@@ -1,3 +1,4 @@
1
+ import type { Logger } from '@sap-ux/logger';
1
2
  /**
2
3
  * Load module from project or app. Throws error if module is not installed.
3
4
  *
@@ -12,4 +13,23 @@
12
13
  * @returns - loaded module.
13
14
  */
14
15
  export declare function loadModuleFromProject<T>(projectRoot: string, moduleName: string): Promise<T>;
16
+ /**
17
+ * Get a module, if it is not cached it will be installed and returned.
18
+ *
19
+ * @param module - name of the module
20
+ * @param version - version of the module
21
+ * @param options - optional options
22
+ * @param options.logger - optional logger instance
23
+ * @returns - module
24
+ */
25
+ export declare function getModule<T>(module: string, version: string, options?: {
26
+ logger?: Logger;
27
+ }): Promise<T>;
28
+ /**
29
+ * Delete a module from cache.
30
+ *
31
+ * @param module - name of the module
32
+ * @param version - version of the module
33
+ */
34
+ export declare function deleteModule(module: string, version: string): Promise<void>;
15
35
  //# sourceMappingURL=module-loader.d.ts.map
@@ -23,8 +23,13 @@ var __importStar = (this && this.__importStar) || function (mod) {
23
23
  return result;
24
24
  };
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.loadModuleFromProject = void 0;
26
+ exports.deleteModule = exports.getModule = exports.loadModuleFromProject = void 0;
27
+ const fs_1 = require("fs");
28
+ const promises_1 = require("fs/promises");
29
+ const path_1 = require("path");
27
30
  const dependencies_1 = require("./dependencies");
31
+ const constants_1 = require("../constants");
32
+ const command_1 = require("../command");
28
33
  /**
29
34
  * Load module from project or app. Throws error if module is not installed.
30
35
  *
@@ -53,4 +58,39 @@ async function loadModuleFromProject(projectRoot, moduleName) {
53
58
  return module;
54
59
  }
55
60
  exports.loadModuleFromProject = loadModuleFromProject;
61
+ /**
62
+ * Get a module, if it is not cached it will be installed and returned.
63
+ *
64
+ * @param module - name of the module
65
+ * @param version - version of the module
66
+ * @param options - optional options
67
+ * @param options.logger - optional logger instance
68
+ * @returns - module
69
+ */
70
+ async function getModule(module, version, options) {
71
+ const logger = options?.logger;
72
+ const moduleDirectory = (0, path_1.join)(constants_1.moduleCacheRoot, module, version);
73
+ if (!(0, fs_1.existsSync)((0, path_1.join)(moduleDirectory, constants_1.FileName.Package))) {
74
+ if ((0, fs_1.existsSync)(moduleDirectory)) {
75
+ await (0, promises_1.rm)(moduleDirectory, { recursive: true });
76
+ }
77
+ await (0, promises_1.mkdir)(moduleDirectory, { recursive: true });
78
+ await (0, command_1.execNpmCommand)(['install', `${module}@${version}`], { cwd: moduleDirectory, logger });
79
+ }
80
+ return loadModuleFromProject(moduleDirectory, module);
81
+ }
82
+ exports.getModule = getModule;
83
+ /**
84
+ * Delete a module from cache.
85
+ *
86
+ * @param module - name of the module
87
+ * @param version - version of the module
88
+ */
89
+ async function deleteModule(module, version) {
90
+ const moduleDirectory = (0, path_1.join)(constants_1.moduleCacheRoot, module, version);
91
+ if ((0, fs_1.existsSync)(moduleDirectory)) {
92
+ await (0, promises_1.rm)(moduleDirectory, { recursive: true });
93
+ }
94
+ }
95
+ exports.deleteModule = deleteModule;
56
96
  //# sourceMappingURL=module-loader.js.map
@@ -0,0 +1,24 @@
1
+ import type { Logger } from '@sap-ux/logger';
2
+ /**
3
+ * Loads and return specification from project or cache.
4
+ * 1. if package.json contains devDependency to specification, attempts to load from project.
5
+ * 2. if not in package.json of project, attempts to load from cache.
6
+ *
7
+ * @param root - root path of the project/app
8
+ * @param [options] - optional options
9
+ * @param [options.logger] - logger instance
10
+ * @returns - specification instance
11
+ */
12
+ export declare function getSpecification<T>(root: string, options?: {
13
+ logger?: Logger;
14
+ }): Promise<T>;
15
+ /**
16
+ * Refreshes the specification dist-tags cache. Also cleans specification modules in cache that are not required anymore.
17
+ *
18
+ * @param [options] - optional options, like logger
19
+ * @param [options.logger] - logger instance
20
+ */
21
+ export declare function refreshSpecificationDistTags(options?: {
22
+ logger?: Logger;
23
+ }): Promise<void>;
24
+ //# sourceMappingURL=specification.d.ts.map
@@ -0,0 +1,129 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.refreshSpecificationDistTags = exports.getSpecification = void 0;
4
+ const fs_1 = require("fs");
5
+ const promises_1 = require("fs/promises");
6
+ const path_1 = require("path");
7
+ const semver_1 = require("semver");
8
+ const module_loader_1 = require("./module-loader");
9
+ const ui5_config_1 = require("./ui5-config");
10
+ const info_1 = require("./info");
11
+ const constants_1 = require("../constants");
12
+ const file_1 = require("../file");
13
+ const command_1 = require("../command");
14
+ const specificationDistTagPath = (0, path_1.join)(constants_1.fioriToolsDirectory, constants_1.FileName.SpecificationDistTags);
15
+ /**
16
+ * Loads and return specification from project or cache.
17
+ * 1. if package.json contains devDependency to specification, attempts to load from project.
18
+ * 2. if not in package.json of project, attempts to load from cache.
19
+ *
20
+ * @param root - root path of the project/app
21
+ * @param [options] - optional options
22
+ * @param [options.logger] - logger instance
23
+ * @returns - specification instance
24
+ */
25
+ async function getSpecification(root, options) {
26
+ let specification;
27
+ const logger = options?.logger;
28
+ try {
29
+ const packageJson = await (0, file_1.readJSON)((0, path_1.join)(root, constants_1.FileName.Package));
30
+ if (packageJson.devDependencies?.['@sap/ux-specification']) {
31
+ logger?.debug(`Specification found in devDependencies of project '${root}', trying to load`);
32
+ // Early return with load module from project. If it throws an error it is not handled here.
33
+ return (0, module_loader_1.loadModuleFromProject)(root, '@sap/ux-specification');
34
+ }
35
+ }
36
+ catch {
37
+ logger?.debug(`Specification not found in project '${root}', trying to load from cache`);
38
+ }
39
+ let distTag = 'latest';
40
+ try {
41
+ const webappPath = await (0, ui5_config_1.getWebappPath)(root);
42
+ const manifest = await (0, file_1.readJSON)((0, path_1.join)(webappPath, constants_1.FileName.Manifest));
43
+ const minUI5Version = (0, info_1.getMinimumUI5Version)(manifest);
44
+ if (minUI5Version && (0, semver_1.valid)(minUI5Version)) {
45
+ const [mayor, minor] = minUI5Version.split('.');
46
+ distTag = `UI5-${mayor}.${minor}`;
47
+ }
48
+ }
49
+ catch (error) {
50
+ logger?.error(`Failed to get minimum UI5 version from manifest: ${error} using 'latest'`);
51
+ }
52
+ try {
53
+ specification = await getSpecificationByDistTag(distTag, { logger });
54
+ logger?.debug(`Specification loaded from cache using dist-tag '${distTag}'`);
55
+ }
56
+ catch (error) {
57
+ logger?.error(`Failed to load specification: ${error}`);
58
+ throw new Error(`Failed to load specification: ${error}`);
59
+ }
60
+ return specification;
61
+ }
62
+ exports.getSpecification = getSpecification;
63
+ /**
64
+ * Refreshes the specification dist-tags cache. Also cleans specification modules in cache that are not required anymore.
65
+ *
66
+ * @param [options] - optional options, like logger
67
+ * @param [options.logger] - logger instance
68
+ */
69
+ async function refreshSpecificationDistTags(options) {
70
+ const logger = options?.logger;
71
+ try {
72
+ const distTagsString = await (0, command_1.execNpmCommand)(['view', '@sap/ux-specification', 'dist-tags', '--json'], {
73
+ logger
74
+ });
75
+ const distTags = JSON.parse(distTagsString);
76
+ await (0, file_1.writeFile)(specificationDistTagPath, JSON.stringify(distTags, null, 4));
77
+ const uniqueVersions = new Set(Object.values(distTags));
78
+ // Check if we have cached versions that are not required anymore
79
+ const specificationCachePath = (0, path_1.join)(constants_1.moduleCacheRoot, '@sap/ux-specification');
80
+ const removeExistingVersions = (0, fs_1.existsSync)(specificationCachePath)
81
+ ? (await (0, promises_1.readdir)(specificationCachePath, { withFileTypes: true }))
82
+ .filter((d) => d.isDirectory())
83
+ .filter((d) => !uniqueVersions.has(d.name))
84
+ .map((d) => d.name)
85
+ : [];
86
+ // Delete cached versions that are not required anymore
87
+ for (const version of removeExistingVersions) {
88
+ await (0, module_loader_1.deleteModule)('@sap/ux-specification', version);
89
+ logger?.debug(`Deleted unused specification module '@sap/ux-specification@${version}' from cache`);
90
+ }
91
+ }
92
+ catch (error) {
93
+ logger?.error(`Error refreshing specification dist-tags: ${error}`);
94
+ }
95
+ }
96
+ exports.refreshSpecificationDistTags = refreshSpecificationDistTags;
97
+ /**
98
+ * Loads and return specification from cache by dist-tag.
99
+ *
100
+ * @param distTag - dist-tag of the specification, like 'latest' or 'UI5-1.71'
101
+ * @param [options] - optional options
102
+ * @param [options.logger] - optional logger instance
103
+ * @returns - specification instance
104
+ */
105
+ async function getSpecificationByDistTag(distTag, options) {
106
+ const logger = options?.logger;
107
+ const version = await convertDistTagToVersion(distTag, { logger });
108
+ const specification = await (0, module_loader_1.getModule)('@sap/ux-specification', version, { logger });
109
+ return specification;
110
+ }
111
+ /**
112
+ * Converts dist-tag to version.
113
+ *
114
+ * @param distTag - dist-tag of the specification, like 'latest' or 'UI5-1.71'
115
+ * @param [options] - optional options
116
+ * @param [options.logger] - optional logger instance
117
+ * @returns - version for given dist-tag
118
+ */
119
+ async function convertDistTagToVersion(distTag, options) {
120
+ const logger = options?.logger;
121
+ if (!(0, fs_1.existsSync)(specificationDistTagPath)) {
122
+ logger?.debug(`Specification dist-tags not found at '${specificationDistTagPath}'. Trying to refresh.`);
123
+ await refreshSpecificationDistTags({ logger });
124
+ }
125
+ const specificationDistTags = await (0, file_1.readJSON)(specificationDistTagPath);
126
+ const version = specificationDistTags[distTag] ?? specificationDistTags.latest;
127
+ return version;
128
+ }
129
+ //# sourceMappingURL=specification.js.map
@@ -1,5 +1,6 @@
1
- import type { I18nBundles } from '../i18n';
1
+ import type { Logger } from '@sap-ux/logger';
2
2
  import type { NewI18nEntry } from '@sap-ux/i18n';
3
+ import type { I18nBundles } from '../i18n';
3
4
  import type { ApplicationStructure, I18nPropertiesPaths, Project, ProjectType } from '../info';
4
5
  import type { Editor } from 'mem-fs-editor';
5
6
  import type { Package } from '../package';
@@ -9,6 +10,9 @@ interface BaseAccess {
9
10
  readonly root: string;
10
11
  readonly projectType: ProjectType;
11
12
  }
13
+ export interface ApplicationAccessOptions extends ProjectAccessOptions {
14
+ fs?: Editor;
15
+ }
12
16
  export interface ApplicationAccess extends BaseAccess {
13
17
  readonly app: ApplicationStructure;
14
18
  /**
@@ -95,6 +99,10 @@ export interface ApplicationAccess extends BaseAccess {
95
99
  * @returns absolute paths to i18n.properties
96
100
  */
97
101
  getI18nPropertiesPaths(): Promise<I18nPropertiesPaths>;
102
+ /**
103
+ * Returns an instance of @sap/ux-specification for the application.
104
+ */
105
+ getSpecification<T>(): Promise<T>;
98
106
  /**
99
107
  * Updates package.json file asynchronously by keeping the previous indentation.
100
108
  *
@@ -110,6 +118,9 @@ export interface ApplicationAccess extends BaseAccess {
110
118
  */
111
119
  updateManifestJSON(manifest: Manifest, memFs?: Editor): Promise<void>;
112
120
  }
121
+ export interface ProjectAccessOptions {
122
+ logger?: Logger;
123
+ }
113
124
  export interface ProjectAccess extends BaseAccess {
114
125
  getApplicationIds: () => string[];
115
126
  getApplication: (appId: string) => ApplicationAccess;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap-ux/project-access",
3
- "version": "1.23.0",
3
+ "version": "1.24.0",
4
4
  "description": "Library to access SAP Fiori tools projects",
5
5
  "repository": {
6
6
  "type": "git",