@sap-ux/fiori-migration-writer 0.0.5 → 0.0.6

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.
Files changed (108) hide show
  1. package/dist/ProjectMigrator.d.ts +20 -8
  2. package/dist/ProjectMigrator.js +121 -123
  3. package/dist/adapters/ui5-config-adapter.js +64 -18
  4. package/dist/adapters/ui5-config-helpers.js +3 -10
  5. package/dist/components/extension.d.ts +3 -1
  6. package/dist/components/extension.js +5 -4
  7. package/dist/config/backend-i18n.d.ts +27 -6
  8. package/dist/config/backend-i18n.js +5 -11
  9. package/dist/config/flp/intent.d.ts +6 -3
  10. package/dist/config/flp/intent.js +6 -4
  11. package/dist/config/flp/sandbox.d.ts +3 -1
  12. package/dist/config/flp/sandbox.js +17 -12
  13. package/dist/config/launch-config.d.ts +4 -3
  14. package/dist/config/launch-config.js +5 -16
  15. package/dist/config/manifest-update.d.ts +21 -1
  16. package/dist/config/manifest-update.js +7 -12
  17. package/dist/config/package-json.d.ts +3 -1
  18. package/dist/config/package-json.js +15 -9
  19. package/dist/config/ui5-yaml.d.ts +9 -4
  20. package/dist/config/ui5-yaml.js +14 -10
  21. package/dist/data/sap-libraries.d.ts +3 -1
  22. package/dist/data/sap-libraries.js +4 -3
  23. package/dist/data/ui5-version.js +1 -1
  24. package/dist/files/project-files.d.ts +4 -3
  25. package/dist/files/project-files.js +10 -16
  26. package/dist/files/webapp.d.ts +5 -2
  27. package/dist/files/webapp.js +30 -35
  28. package/dist/index.d.ts +2 -2
  29. package/dist/index.js +2 -2
  30. package/dist/migration-process/configuration.d.ts +3 -1
  31. package/dist/migration-process/configuration.js +3 -2
  32. package/dist/migration-process/legacy-helpers.d.ts +3 -1
  33. package/dist/migration-process/legacy-helpers.js +19 -32
  34. package/dist/migration-process/legacy.d.ts +3 -1
  35. package/dist/migration-process/legacy.js +66 -79
  36. package/dist/migration-process/migration-phases.d.ts +6 -1
  37. package/dist/migration-process/migration-phases.js +26 -14
  38. package/dist/migration-process/setup.d.ts +9 -6
  39. package/dist/migration-process/setup.js +19 -10
  40. package/dist/migration-process/validation.d.ts +5 -2
  41. package/dist/migration-process/validation.js +6 -4
  42. package/dist/migration-types.d.ts +5 -2
  43. package/dist/project/adaptation-project.js +1 -0
  44. package/dist/project/project-data.js +1 -1
  45. package/dist/project/project-detection.d.ts +3 -1
  46. package/dist/project/project-detection.js +10 -4
  47. package/dist/project/project-extension.d.ts +1 -1
  48. package/dist/project/project-extension.js +3 -1
  49. package/dist/project/project-info.d.ts +3 -1
  50. package/dist/project/project-info.js +6 -3
  51. package/dist/project/regular-project.js +17 -9
  52. package/dist/template/application.d.ts +2 -0
  53. package/dist/template/application.js +11 -9
  54. package/dist/template/assembly.js +4 -4
  55. package/dist/template/base.d.ts +2 -0
  56. package/dist/template/base.js +2 -30
  57. package/dist/template/details.d.ts +3 -1
  58. package/dist/template/details.js +7 -5
  59. package/dist/template/template-helpers.d.ts +3 -1
  60. package/dist/template/template-helpers.js +6 -5
  61. package/dist/template/theme.d.ts +3 -1
  62. package/dist/template/theme.js +3 -2
  63. package/dist/types/mem-fs-types.d.ts +1 -1
  64. package/dist/utils/Project.d.ts +17 -14
  65. package/dist/utils/Project.js +94 -72
  66. package/dist/utils/checkForMigration.d.ts +4 -3
  67. package/dist/utils/checkForMigration.js +11 -8
  68. package/dist/utils/file-access.d.ts +36 -34
  69. package/dist/utils/file-access.js +61 -93
  70. package/dist/utils/file-discovery.d.ts +5 -2
  71. package/dist/utils/file-discovery.js +6 -4
  72. package/dist/utils/file-system-utils.d.ts +15 -5
  73. package/dist/utils/file-system-utils.js +34 -11
  74. package/dist/utils/fs-adapter.d.ts +5 -63
  75. package/dist/utils/fs-adapter.js +6 -111
  76. package/dist/utils/manifest-and-version-utils.d.ts +3 -1
  77. package/dist/utils/manifest-and-version-utils.js +6 -3
  78. package/dist/utils/migration-utils.d.ts +3 -1
  79. package/dist/utils/migration-utils.js +5 -4
  80. package/dist/utils/project-access-adapters.d.ts +3 -1
  81. package/dist/utils/project-access-adapters.js +3 -2
  82. package/dist/utils/project-discovery.d.ts +6 -2
  83. package/dist/utils/project-discovery.js +44 -18
  84. package/dist/utils/project-readers/adaptation-project-utils.d.ts +5 -2
  85. package/dist/utils/project-readers/adaptation-project-utils.js +21 -16
  86. package/dist/utils/project-readers/backend-utils.d.ts +3 -1
  87. package/dist/utils/project-readers/backend-utils.js +4 -3
  88. package/dist/utils/project-readers/extension-project-utils.d.ts +5 -2
  89. package/dist/utils/project-readers/extension-project-utils.js +15 -11
  90. package/dist/utils/project-readers/manifest-reader.d.ts +3 -1
  91. package/dist/utils/project-readers/manifest-reader.js +3 -2
  92. package/dist/utils/project-readers/manifest-utils.d.ts +5 -2
  93. package/dist/utils/project-readers/manifest-utils.js +9 -6
  94. package/dist/utils/project-readers/package-reader.d.ts +3 -1
  95. package/dist/utils/project-readers/package-reader.js +3 -2
  96. package/dist/utils/project-readers/reuse-lib-utils.d.ts +7 -3
  97. package/dist/utils/project-readers/reuse-lib-utils.js +13 -7
  98. package/dist/utils/project-readers/webapp-path-resolver.d.ts +3 -1
  99. package/dist/utils/project-readers/webapp-path-resolver.js +24 -19
  100. package/dist/utils/service.d.ts +3 -2
  101. package/dist/utils/service.js +10 -6
  102. package/dist/utils/template/template-post-processors.d.ts +3 -1
  103. package/dist/utils/template/template-post-processors.js +4 -3
  104. package/dist/utils/template/template-renderer.d.ts +3 -1
  105. package/dist/utils/template/template-renderer.js +3 -2
  106. package/dist/utils/template-generators/file-handlers.d.ts +5 -2
  107. package/dist/utils/template-generators/file-handlers.js +59 -32
  108. package/package.json +4 -4
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Utilities for detecting and processing reuse library projects
3
3
  */
4
+ import type { Editor } from 'mem-fs-editor';
4
5
  import type { Manifest } from '../../project-spec-types.js';
5
6
  import { MigrationTypes } from '../constants.js';
6
7
  import type { ProjectFolder } from '../../types.js';
@@ -27,10 +28,11 @@ export declare function isSamePath(pathA: string, pathB: string): boolean;
27
28
  * same way the workspace discovery path does and returns the library whose root
28
29
  * resolves back to projectRoot.
29
30
  *
31
+ * @param fs - mem-fs editor
30
32
  * @param projectRoot - Root path of the project
31
33
  * @returns The nested manifest and its path, or undefined if none is found
32
34
  */
33
- export declare function findNestedReuseLibManifest(projectRoot: string): Promise<{
35
+ export declare function findNestedReuseLibManifest(fs: Editor, projectRoot: string): Promise<{
34
36
  manifest: Manifest;
35
37
  manifestPath: string;
36
38
  } | undefined>;
@@ -40,16 +42,18 @@ export declare function findNestedReuseLibManifest(projectRoot: string): Promise
40
42
  * @param projectRoot - Root path of the project
41
43
  * @param type - Optional migration type
42
44
  * @param manifest - Optional manifest object
45
+ * @param fsEditor - optional mem-fs editor; a fresh one is created when omitted
43
46
  * @returns True if project is a reuse library
44
47
  */
45
- export declare function checkIfReuseLib(projectRoot: string, type?: MigrationTypes, manifest?: Manifest): Promise<boolean>;
48
+ export declare function checkIfReuseLib(projectRoot: string, type?: MigrationTypes, manifest?: Manifest, fsEditor?: Editor): Promise<boolean>;
46
49
  /**
47
50
  * Get module name for reuse library
48
51
  *
49
52
  * @param projectRoot - Root path of the project
50
53
  * @param workspaceFolders - Optional workspace folders
51
54
  * @param manifest - Optional manifest object
55
+ * @param fsEditor - optional mem-fs editor; a fresh one is created when omitted
52
56
  * @returns Module name
53
57
  */
54
- export declare function getReuseLibModuleName(projectRoot: string, workspaceFolders?: readonly ProjectFolder[], manifest?: Manifest): Promise<string>;
58
+ export declare function getReuseLibModuleName(projectRoot: string, workspaceFolders?: readonly ProjectFolder[], manifest?: Manifest, fsEditor?: Editor): Promise<string>;
55
59
  //# sourceMappingURL=reuse-lib-utils.d.ts.map
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import { basename, join, relative } from 'node:path';
5
5
  import { getReuseLibs, ReuseLibType } from '../file-discovery.js';
6
+ import { createMemFsEditor } from '../fs-adapter.js';
6
7
  import { readJSON } from '../../index.js';
7
8
  import { FileName } from '../../project-spec-types.js';
8
9
  import { MigrationTypes } from '../constants.js';
@@ -32,17 +33,18 @@ export function isSamePath(pathA, pathB) {
32
33
  * same way the workspace discovery path does and returns the library whose root
33
34
  * resolves back to projectRoot.
34
35
  *
36
+ * @param fs - mem-fs editor
35
37
  * @param projectRoot - Root path of the project
36
38
  * @returns The nested manifest and its path, or undefined if none is found
37
39
  */
38
- export async function findNestedReuseLibManifest(projectRoot) {
39
- const libs = await getReuseLibs([{ uri: URI.file(projectRoot), name: projectRoot, index: 0 }]);
40
+ export async function findNestedReuseLibManifest(fs, projectRoot) {
41
+ const libs = await getReuseLibs(fs, [{ uri: URI.file(projectRoot), name: projectRoot, index: 0 }]);
40
42
  const matchedLib = libs.find((lib) => lib.value.type === ReuseLibType.LIBRARY && isSamePath(lib.value.libRoot, projectRoot));
41
43
  if (!matchedLib) {
42
44
  return undefined;
43
45
  }
44
46
  try {
45
- const manifest = await readJSON(matchedLib.value.path);
47
+ const manifest = await readJSON(fs, matchedLib.value.path);
46
48
  return { manifest, manifestPath: matchedLib.value.path };
47
49
  }
48
50
  catch {
@@ -57,17 +59,19 @@ export async function findNestedReuseLibManifest(projectRoot) {
57
59
  * @param projectRoot - Root path of the project
58
60
  * @param type - Optional migration type
59
61
  * @param manifest - Optional manifest object
62
+ * @param fsEditor - optional mem-fs editor; a fresh one is created when omitted
60
63
  * @returns True if project is a reuse library
61
64
  */
62
- export async function checkIfReuseLib(projectRoot, type, manifest) {
65
+ export async function checkIfReuseLib(projectRoot, type, manifest, fsEditor) {
63
66
  if (type) {
64
67
  return type === MigrationTypes.library;
65
68
  }
66
69
  if (manifest) {
67
70
  return manifest['sap.app']?.type === 'library';
68
71
  }
72
+ const fs = fsEditor ?? createMemFsEditor();
69
73
  try {
70
- const reuseManifest = await readJSON(join(projectRoot, FileName.Manifest));
74
+ const reuseManifest = await readJSON(fs, join(projectRoot, FileName.Manifest));
71
75
  return reuseManifest['sap.app']?.type === 'library';
72
76
  }
73
77
  catch {
@@ -82,9 +86,11 @@ export async function checkIfReuseLib(projectRoot, type, manifest) {
82
86
  * @param projectRoot - Root path of the project
83
87
  * @param workspaceFolders - Optional workspace folders
84
88
  * @param manifest - Optional manifest object
89
+ * @param fsEditor - optional mem-fs editor; a fresh one is created when omitted
85
90
  * @returns Module name
86
91
  */
87
- export async function getReuseLibModuleName(projectRoot, workspaceFolders, manifest) {
92
+ export async function getReuseLibModuleName(projectRoot, workspaceFolders, manifest, fsEditor) {
93
+ const fs = fsEditor ?? createMemFsEditor();
88
94
  let moduleName;
89
95
  if (manifest) {
90
96
  moduleName = manifest['sap.app']?.id;
@@ -96,7 +102,7 @@ export async function getReuseLibModuleName(projectRoot, workspaceFolders, manif
96
102
  name: folder.name,
97
103
  index: folder.index
98
104
  }));
99
- const libs = await getReuseLibs(workspaceFoldersWithUri);
105
+ const libs = await getReuseLibs(fs, workspaceFoldersWithUri);
100
106
  const matchedLib = libs.find((lib) => {
101
107
  return isSamePath(lib.value.libRoot, projectRoot);
102
108
  });
@@ -1,11 +1,13 @@
1
+ import type { Editor } from 'mem-fs-editor';
1
2
  import { MigrationTypes } from '../constants.js';
2
3
  /**
3
4
  * Get webapp path for project
4
5
  * Tries multiple sources in order of priority
5
6
  *
7
+ * @param fs - mem-fs editor
6
8
  * @param projectRoot
7
9
  * @param type
8
10
  * @param libPath
9
11
  */
10
- export declare function getWebappPath(projectRoot: string, type?: MigrationTypes, libPath?: string): Promise<string>;
12
+ export declare function getWebappPath(fs: Editor, projectRoot: string, type?: MigrationTypes, libPath?: string): Promise<string>;
11
13
  //# sourceMappingURL=webapp-path-resolver.d.ts.map
@@ -4,24 +4,26 @@
4
4
  import { join } from 'node:path';
5
5
  import { parse } from 'yaml';
6
6
  import { DirName, FileName } from '../../project-spec-types.js';
7
- import { fileExists, readJSON, readFile } from '../../index.js';
7
+ import { fileExists, readJSON } from '../../index.js';
8
+ import { readFile } from '../../utils/file-access.js';
8
9
  import { sapWattCommonSetting } from '../../types.js';
9
10
  import { MigrationTypes } from '../constants.js';
10
11
  /**
11
12
  * Resolve webapp path from .project.json file
12
13
  *
14
+ * @param fs - mem-fs editor
13
15
  * @param projectRoot
14
16
  */
15
- async function resolveFromProjectJson(projectRoot) {
17
+ async function resolveFromProjectJson(fs, projectRoot) {
16
18
  const projectJsonPath = join(projectRoot, '.project.json');
17
- if (!(await fileExists(projectJsonPath))) {
19
+ if (!fileExists(fs, projectJsonPath)) {
18
20
  return undefined;
19
21
  }
20
22
  try {
21
- const projectJson = await readJSON(projectJsonPath);
23
+ const projectJson = await readJSON(fs, projectJsonPath);
22
24
  if (projectJson?.hcpdeploy?.entryPath) {
23
25
  const projectWebappPath = join(projectRoot, projectJson.hcpdeploy.entryPath);
24
- return (await fileExists(projectWebappPath)) ? projectJson.hcpdeploy.entryPath : undefined;
26
+ return fileExists(fs, projectWebappPath) ? projectJson.hcpdeploy.entryPath : undefined;
25
27
  }
26
28
  }
27
29
  catch (error) {
@@ -32,20 +34,21 @@ async function resolveFromProjectJson(projectRoot) {
32
34
  /**
33
35
  * Resolve webapp path from .che/project.json file
34
36
  *
37
+ * @param fs - mem-fs editor
35
38
  * @param projectRoot
36
39
  */
37
- async function resolveFromCheProjectJson(projectRoot) {
40
+ async function resolveFromCheProjectJson(fs, projectRoot) {
38
41
  const cheProjectJsonPath = join(projectRoot, '.che', 'project.json');
39
- if (!(await fileExists(cheProjectJsonPath))) {
42
+ if (!fileExists(fs, cheProjectJsonPath)) {
40
43
  return undefined;
41
44
  }
42
45
  try {
43
- const projectJson = await readJSON(cheProjectJsonPath);
46
+ const projectJson = await readJSON(fs, cheProjectJsonPath);
44
47
  if (projectJson?.attributes?.[sapWattCommonSetting]) {
45
48
  const settings = JSON.parse(projectJson.attributes[sapWattCommonSetting]?.[0]);
46
49
  if (settings?.hcpdeploy?.entryPath) {
47
50
  const projectWebappPath = join(projectRoot, settings.hcpdeploy.entryPath);
48
- return (await fileExists(projectWebappPath)) ? settings.hcpdeploy.entryPath : undefined;
51
+ return fileExists(fs, projectWebappPath) ? settings.hcpdeploy.entryPath : undefined;
49
52
  }
50
53
  }
51
54
  }
@@ -57,11 +60,12 @@ async function resolveFromCheProjectJson(projectRoot) {
57
60
  /**
58
61
  * Resolve webapp path from ui5.yaml file
59
62
  *
63
+ * @param fs - mem-fs editor
60
64
  * @param projectRoot
61
65
  */
62
- function resolveFromUi5Yaml(projectRoot) {
66
+ function resolveFromUi5Yaml(fs, projectRoot) {
63
67
  try {
64
- const yamlContent = readFile(join(projectRoot, FileName.UI5Yaml));
68
+ const yamlContent = readFile(fs, join(projectRoot, FileName.UI5Yaml));
65
69
  return parse(yamlContent)?.resources?.configuration?.paths?.webapp;
66
70
  }
67
71
  catch (error) {
@@ -86,27 +90,28 @@ function resolveForLibrary(projectRoot, libPath) {
86
90
  * Get webapp path for project
87
91
  * Tries multiple sources in order of priority
88
92
  *
93
+ * @param fs - mem-fs editor
89
94
  * @param projectRoot
90
95
  * @param type
91
96
  * @param libPath
92
97
  */
93
- export async function getWebappPath(projectRoot, type, libPath) {
98
+ export async function getWebappPath(fs, projectRoot, type, libPath) {
94
99
  const defaultPath = join(projectRoot, DirName.Webapp);
95
100
  // Check if default webapp folder exists
96
- if (await fileExists(defaultPath)) {
101
+ if (fileExists(fs, defaultPath)) {
97
102
  return DirName.Webapp;
98
103
  }
99
104
  // Check if manifest.json is in root (no webapp folder)
100
- if (await fileExists(join(projectRoot, FileName.Manifest))) {
105
+ if (fileExists(fs, join(projectRoot, FileName.Manifest))) {
101
106
  return '';
102
107
  }
103
108
  // Check for legacy Maven/Java structure: src/main/webapp
104
109
  const legacyMavenPath = join('src', 'main', DirName.Webapp);
105
110
  const legacyMavenFullPath = join(projectRoot, legacyMavenPath);
106
- if (await fileExists(legacyMavenFullPath)) {
111
+ if (fileExists(fs, legacyMavenFullPath)) {
107
112
  // Verify it actually has webapp content (manifest.json)
108
113
  const legacyManifestPath = join(legacyMavenFullPath, FileName.Manifest);
109
- if (await fileExists(legacyManifestPath)) {
114
+ if (fileExists(fs, legacyManifestPath)) {
110
115
  return legacyMavenPath;
111
116
  }
112
117
  }
@@ -118,17 +123,17 @@ export async function getWebappPath(projectRoot, type, libPath) {
118
123
  }
119
124
  }
120
125
  // Try .project.json
121
- const projectJsonPath = await resolveFromProjectJson(projectRoot);
126
+ const projectJsonPath = await resolveFromProjectJson(fs, projectRoot);
122
127
  if (projectJsonPath) {
123
128
  return projectJsonPath;
124
129
  }
125
130
  // Try .che/project.json
126
- const cheProjectJsonPath = await resolveFromCheProjectJson(projectRoot);
131
+ const cheProjectJsonPath = await resolveFromCheProjectJson(fs, projectRoot);
127
132
  if (cheProjectJsonPath) {
128
133
  return cheProjectJsonPath;
129
134
  }
130
135
  // Try ui5.yaml
131
- const ui5YamlPath = resolveFromUi5Yaml(projectRoot);
136
+ const ui5YamlPath = resolveFromUi5Yaml(fs, projectRoot);
132
137
  if (ui5YamlPath) {
133
138
  return ui5YamlPath;
134
139
  }
@@ -1,7 +1,8 @@
1
1
  import type { Manifest } from '../project-spec-types.js';
2
+ import type { Editor } from 'mem-fs-editor';
2
3
  export declare function xmlToJson(metadata: any): JSON | undefined;
3
- export declare function validateMetadata(rootPath: string, webappPath: string, isSAPApp?: boolean, manifestJSON?: Manifest | undefined): Promise<{
4
+ export declare function validateMetadata(rootPath: string, webappPath: string, isSAPApp?: boolean, manifestJSON?: Manifest | undefined, fsEditor?: Editor): {
4
5
  config: boolean;
5
6
  fileExists: boolean;
6
- }>;
7
+ };
7
8
  //# sourceMappingURL=service.d.ts.map
@@ -1,4 +1,5 @@
1
1
  import { fileExists } from './file-access.js';
2
+ import { createMemFsEditor } from './fs-adapter.js';
2
3
  import { XMLValidator, XMLParser } from 'fast-xml-parser';
3
4
  import { join } from 'node:path';
4
5
  const options = {
@@ -11,7 +12,8 @@ const options = {
11
12
  export function xmlToJson(metadata) {
12
13
  return XMLValidator.validate(metadata) ? new XMLParser(options).parse(metadata, true) : undefined;
13
14
  }
14
- export async function validateMetadata(rootPath, webappPath, isSAPApp, manifestJSON) {
15
+ export function validateMetadata(rootPath, webappPath, isSAPApp, manifestJSON, fsEditor) {
16
+ const fs = fsEditor ?? createMemFsEditor();
15
17
  if (!manifestJSON) {
16
18
  return { config: false, fileExists: false };
17
19
  }
@@ -21,7 +23,7 @@ export async function validateMetadata(rootPath, webappPath, isSAPApp, manifestJ
21
23
  }
22
24
  // Validate datasources if they exist
23
25
  if (manifestJSON['sap.app']?.dataSources) {
24
- return validateDataSources(manifestJSON['sap.app'].dataSources, rootPath, webappPath);
26
+ return validateDataSources(fs, manifestJSON['sap.app'].dataSources, rootPath, webappPath);
25
27
  }
26
28
  return { config: false, fileExists: false };
27
29
  }
@@ -30,11 +32,12 @@ export async function validateMetadata(rootPath, webappPath, isSAPApp, manifestJ
30
32
  * Preserves last-wins behavior: if multiple datasources have localUri,
31
33
  * the result reflects the last one processed
32
34
  *
35
+ * @param fs
33
36
  * @param dataSources
34
37
  * @param rootPath
35
38
  * @param webappPath
36
39
  */
37
- async function validateDataSources(dataSources, rootPath, webappPath) {
40
+ function validateDataSources(fs, dataSources, rootPath, webappPath) {
38
41
  const datasourceKeys = Object.keys(dataSources);
39
42
  let metadataValidation = { config: false, fileExists: false };
40
43
  // Check datasources sequentially - stops at first valid metadata file found
@@ -42,7 +45,7 @@ async function validateDataSources(dataSources, rootPath, webappPath) {
42
45
  const datasource = dataSources[datasourceKey];
43
46
  // Check OData datasources or datasources without explicit type
44
47
  if (datasource.type === 'OData' || datasource.type === undefined) {
45
- const result = await checkLocalMetadata(datasource, rootPath, webappPath); // NOSONAR: Last-wins pattern for datasource validation
48
+ const result = checkLocalMetadata(fs, datasource, rootPath, webappPath);
46
49
  if (result.config) {
47
50
  metadataValidation = result;
48
51
  }
@@ -53,16 +56,17 @@ async function validateDataSources(dataSources, rootPath, webappPath) {
53
56
  /**
54
57
  * Check if local metadata file exists for a datasource
55
58
  *
59
+ * @param fs
56
60
  * @param datasource
57
61
  * @param rootPath
58
62
  * @param webappPath
59
63
  */
60
- async function checkLocalMetadata(datasource, rootPath, webappPath) {
64
+ function checkLocalMetadata(fs, datasource, rootPath, webappPath) {
61
65
  if (!datasource.settings?.localUri) {
62
66
  return { config: false, fileExists: false };
63
67
  }
64
68
  const metadataPath = join(rootPath, webappPath, datasource.settings.localUri);
65
- const exists = await fileExists(metadataPath);
69
+ const exists = fileExists(fs, metadataPath);
66
70
  return {
67
71
  config: true,
68
72
  fileExists: exists
@@ -1,4 +1,5 @@
1
1
  import type { Data } from 'ejs';
2
+ import type { Editor } from 'mem-fs-editor';
2
3
  /**
3
4
  * Apply file-specific post-processing handlers
4
5
  *
@@ -10,6 +11,7 @@ import type { Data } from 'ejs';
10
11
  * - GitIgnore: Merges with existing .gitignore if present
11
12
  * - package.json: Merges with existing package.json if present
12
13
  *
14
+ * @param fs - mem-fs editor
13
15
  * @param templateName - Name of the template being processed
14
16
  * @param templateContent - Rendered template content
15
17
  * @param projectRoot - Root directory of the project
@@ -17,5 +19,5 @@ import type { Data } from 'ejs';
17
19
  * @param templateData - Original template data (for package.json handler)
18
20
  * @returns Post-processed template content
19
21
  */
20
- export declare function applyFileSpecificHandlers(templateName: string, templateContent: string, projectRoot: string, targetFile: string, templateData: Data): Promise<string>;
22
+ export declare function applyFileSpecificHandlers(fs: Editor, templateName: string, templateContent: string, projectRoot: string, targetFile: string, templateData: Data): Promise<string>;
21
23
  //# sourceMappingURL=template-post-processors.d.ts.map
@@ -14,6 +14,7 @@ const packageJsonTmplName = `${FileName.Package}.tmpl`;
14
14
  * - GitIgnore: Merges with existing .gitignore if present
15
15
  * - package.json: Merges with existing package.json if present
16
16
  *
17
+ * @param fs - mem-fs editor
17
18
  * @param templateName - Name of the template being processed
18
19
  * @param templateContent - Rendered template content
19
20
  * @param projectRoot - Root directory of the project
@@ -21,18 +22,18 @@ const packageJsonTmplName = `${FileName.Package}.tmpl`;
21
22
  * @param templateData - Original template data (for package.json handler)
22
23
  * @returns Post-processed template content
23
24
  */
24
- export async function applyFileSpecificHandlers(templateName, templateContent, projectRoot, targetFile, templateData) {
25
+ export async function applyFileSpecificHandlers(fs, templateName, templateContent, projectRoot, targetFile, templateData) {
25
26
  // Handle LocateReuseLibs template
26
27
  if (templateName === TemplateFileName.LocateReuseLibs) {
27
28
  return handleLocateReuseLibsFile(templateContent, templateData);
28
29
  }
29
30
  // Handle .gitignore template
30
31
  if (templateName === TemplateFileName.GitIgnoreTmpl) {
31
- return handleGitIgnoreFile(targetFile, templateContent);
32
+ return handleGitIgnoreFile(targetFile, templateContent, fs);
32
33
  }
33
34
  // Handle package.json template
34
35
  if (templateName === packageJsonTmplName) {
35
- return handlePackageJsonFile(projectRoot, targetFile, templateContent, templateData);
36
+ return handlePackageJsonFile(projectRoot, targetFile, templateContent, templateData, fs);
36
37
  }
37
38
  // No special handling needed
38
39
  return templateContent;
@@ -1,10 +1,12 @@
1
1
  import type { Data } from 'ejs';
2
+ import type { Editor } from 'mem-fs-editor';
2
3
  /**
3
4
  * Render a template file
4
5
  *
5
6
  * Reads the template file and optionally renders it using EJS.
6
7
  * If rendering is disabled, returns the raw file content.
7
8
  *
9
+ * @param fs - mem-fs editor
8
10
  * @param templateRoot - Root directory containing templates
9
11
  * @param fullTemplateName - Full path to template file relative to templateRoot
10
12
  * @param templateData - Data to use for template rendering
@@ -13,5 +15,5 @@ import type { Data } from 'ejs';
13
15
  * @returns Rendered template content or raw file content
14
16
  * @throws Error if template path contains path traversal
15
17
  */
16
- export declare function renderTemplate(templateRoot: string, fullTemplateName: string, templateData: Data, opts: object, isRendered: boolean): string;
18
+ export declare function renderTemplate(fs: Editor, templateRoot: string, fullTemplateName: string, templateData: Data, opts: object, isRendered: boolean): string;
17
19
  //# sourceMappingURL=template-renderer.d.ts.map
@@ -30,6 +30,7 @@ function validateTemplatePath(templateRoot, templateName) {
30
30
  * Reads the template file and optionally renders it using EJS.
31
31
  * If rendering is disabled, returns the raw file content.
32
32
  *
33
+ * @param fs - mem-fs editor
33
34
  * @param templateRoot - Root directory containing templates
34
35
  * @param fullTemplateName - Full path to template file relative to templateRoot
35
36
  * @param templateData - Data to use for template rendering
@@ -38,10 +39,10 @@ function validateTemplatePath(templateRoot, templateName) {
38
39
  * @returns Rendered template content or raw file content
39
40
  * @throws Error if template path contains path traversal
40
41
  */
41
- export function renderTemplate(templateRoot, fullTemplateName, templateData, opts, isRendered) {
42
+ export function renderTemplate(fs, templateRoot, fullTemplateName, templateData, opts, isRendered) {
42
43
  // Validate path to prevent directory traversal attacks
43
44
  const safePath = validateTemplatePath(templateRoot, fullTemplateName);
44
- const templateFile = readFile(safePath);
45
+ const templateFile = readFile(fs, safePath);
45
46
  return isRendered ? render(templateFile, templateData, { ...DEFAULT_EJS_OPTS, ...opts }) : templateFile;
46
47
  }
47
48
  //# sourceMappingURL=template-renderer.js.map
@@ -1,10 +1,12 @@
1
+ import type { Editor } from 'mem-fs-editor';
1
2
  /**
2
3
  * Handle .gitignore file generation - append to existing file
3
4
  *
4
5
  * @param targetFile
5
6
  * @param templateContent
7
+ * @param fsEditor - optional mem-fs editor; a fresh one is created when omitted
6
8
  */
7
- export declare function handleGitIgnoreFile(targetFile: string, templateContent: string): Promise<string>;
9
+ export declare function handleGitIgnoreFile(targetFile: string, templateContent: string, fsEditor?: Editor): string;
8
10
  /**
9
11
  * Handle package.json file generation - merge with existing file
10
12
  *
@@ -12,8 +14,9 @@ export declare function handleGitIgnoreFile(targetFile: string, templateContent:
12
14
  * @param targetFile
13
15
  * @param templateContent
14
16
  * @param templateData
17
+ * @param fsEditor - optional mem-fs editor; a fresh one is created when omitted
15
18
  */
16
- export declare function handlePackageJsonFile(projectRoot: string, targetFile: string, templateContent: string, templateData: any): Promise<string>;
19
+ export declare function handlePackageJsonFile(projectRoot: string, targetFile: string, templateContent: string, templateData: any, fsEditor?: Editor): Promise<string>;
17
20
  /**
18
21
  * Handle locateReuseLibs file - replace mockserver path
19
22
  *
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { join } from 'node:path';
6
6
  import { readFile, fileExists, readJSON, deleteFile } from '../file-access.js';
7
+ import { createMemFsEditor } from '../fs-adapter.js';
7
8
  import mergeWith from 'lodash/mergeWith.js';
8
9
  import { MigrationTypes } from '../constants.js';
9
10
  /**
@@ -11,17 +12,54 @@ import { MigrationTypes } from '../constants.js';
11
12
  *
12
13
  * @param targetFile
13
14
  * @param templateContent
15
+ * @param fsEditor - optional mem-fs editor; a fresh one is created when omitted
14
16
  */
15
- export async function handleGitIgnoreFile(targetFile, templateContent) {
16
- if (!(await fileExists(targetFile))) {
17
+ export function handleGitIgnoreFile(targetFile, templateContent, fsEditor) {
18
+ const fs = fsEditor ?? createMemFsEditor();
19
+ if (!fileExists(fs, targetFile)) {
17
20
  return templateContent;
18
21
  }
19
- const currentFileContent = readFile(targetFile);
22
+ const currentFileContent = readFile(fs, targetFile);
20
23
  let mergedContent = `${currentFileContent}\n${templateContent}`;
21
24
  // Replace `changes_preview.js` so it can be checked in after migration
22
25
  mergedContent = mergedContent.replaceAll(`/changes_preview.js`, '').replaceAll(`changes_preview.js`, '');
23
26
  return mergedContent;
24
27
  }
28
+ /**
29
+ * Rename scripts in the existing package.json that collide with template script names,
30
+ * preserving the originals under a `<name>_old` key.
31
+ *
32
+ * @param currentFileContent - Parsed existing package.json (mutated in place)
33
+ * @param templateContentJSON - Parsed template package.json
34
+ */
35
+ function renameConflictingScripts(currentFileContent, templateContentJSON) {
36
+ const oldScripts = {};
37
+ for (const script in templateContentJSON?.scripts) {
38
+ if (currentFileContent?.scripts?.[script] && !currentFileContent.scripts[`${script}_old`]) {
39
+ oldScripts[`${script}_old`] = currentFileContent.scripts[script];
40
+ delete currentFileContent.scripts[script];
41
+ }
42
+ else if (currentFileContent?.scripts?.[script]) {
43
+ delete currentFileContent.scripts[script];
44
+ }
45
+ }
46
+ for (const oldScript in oldScripts) {
47
+ currentFileContent.scripts[oldScript] = oldScripts[oldScript];
48
+ }
49
+ }
50
+ /**
51
+ * Add the extension-project `start-ui5-flp` script to the template package.json.
52
+ *
53
+ * @param templateContentJSON - Parsed template package.json (mutated in place)
54
+ * @param templateData - Template data with project/service configuration
55
+ */
56
+ function addExtensionStartScript(templateContentJSON, templateData) {
57
+ let queryStr = templateData.project.ui5Theme ? `sap-theme=${templateData.project.ui5Theme}&` : '';
58
+ queryStr = templateData.service.client ? `${queryStr}sap-client=${templateData.service.client}&` : queryStr;
59
+ templateContentJSON.scripts ??= {};
60
+ templateContentJSON.scripts['start-ui5-flp'] =
61
+ `fiori run --open "/test-resources/sap/ushell/shells/sandbox/fioriSandbox.html?${queryStr}sap-ushell-test-url-url=..%2F..%2F..%2F..%2F..%2F..&sap-ushell-test-url-additionalInformation=SAPUI5.Component%3D${templateData.fullyQualifiedProjectName}#Test-url"`;
62
+ }
25
63
  /**
26
64
  * Handle package.json file generation - merge with existing file
27
65
  *
@@ -29,14 +67,17 @@ export async function handleGitIgnoreFile(targetFile, templateContent) {
29
67
  * @param targetFile
30
68
  * @param templateContent
31
69
  * @param templateData
70
+ * @param fsEditor - optional mem-fs editor; a fresh one is created when omitted
32
71
  */
33
- export async function handlePackageJsonFile(projectRoot, targetFile, templateContent, templateData) {
34
- if (!(await fileExists(targetFile))) {
72
+ export async function handlePackageJsonFile(projectRoot, targetFile, templateContent, templateData, fsEditor) {
73
+ const fs = fsEditor ?? createMemFsEditor();
74
+ const ownsEditor = !fsEditor;
75
+ if (!fileExists(fs, targetFile)) {
35
76
  return templateContent;
36
77
  }
37
78
  let currentFileContent;
38
79
  try {
39
- currentFileContent = await readJSON(targetFile);
80
+ currentFileContent = await readJSON(fs, targetFile);
40
81
  }
41
82
  catch (error) {
42
83
  // If file can't be read (shouldn't happen after fileExists check, but handle it anyway)
@@ -44,35 +85,15 @@ export async function handlePackageJsonFile(projectRoot, targetFile, templateCon
44
85
  return templateContent;
45
86
  }
46
87
  const templateContentJSON = JSON.parse(templateContent);
47
- // Rename old scripts that have same name as new ones
48
- const oldScripts = {};
49
- for (const script in templateContentJSON?.scripts) {
50
- if (currentFileContent?.scripts?.[script] && !currentFileContent.scripts[`${script}_old`]) {
51
- oldScripts[`${script}_old`] = currentFileContent.scripts[script];
52
- delete currentFileContent.scripts[script];
53
- }
54
- else if (currentFileContent?.scripts?.[script]) {
55
- delete currentFileContent.scripts[script];
56
- }
57
- }
58
- // Add renamed old scripts back
59
- for (const oldScript in oldScripts) {
60
- currentFileContent.scripts[oldScript] = oldScripts[oldScript];
61
- }
88
+ renameConflictingScripts(currentFileContent, templateContentJSON);
62
89
  // Handle extension project special script
63
90
  if (templateData.project.type === MigrationTypes.projectExtension) {
64
- let queryStr = templateData.project.ui5Theme ? `sap-theme=${templateData.project.ui5Theme}&` : '';
65
- queryStr = templateData.service.client ? `${queryStr}sap-client=${templateData.service.client}&` : queryStr;
66
- if (!templateContentJSON.scripts) {
67
- templateContentJSON.scripts = {};
68
- }
69
- templateContentJSON.scripts['start-ui5-flp'] =
70
- `fiori run --open "/test-resources/sap/ushell/shells/sandbox/fioriSandbox.html?${queryStr}sap-ushell-test-url-url=..%2F..%2F..%2F..%2F..%2F..&sap-ushell-test-url-additionalInformation=SAPUI5.Component%3D${templateData.fullyQualifiedProjectName}#Test-url"`;
91
+ addExtensionStartScript(templateContentJSON, templateData);
71
92
  }
72
93
  // Remove conflicting dependencies
73
94
  removeLegacyDependencies(currentFileContent, templateContentJSON);
74
95
  // Remove legacy Grunt module and file
75
- await removeLegacyGruntModule(projectRoot, currentFileContent);
96
+ removeLegacyGruntModule(fs, projectRoot, currentFileContent);
76
97
  // Remove old beta ui5-tooling module
77
98
  removeBetaUi5ToolingModule(currentFileContent);
78
99
  // Filter duplicate keywords
@@ -83,6 +104,11 @@ export async function handlePackageJsonFile(projectRoot, targetFile, templateCon
83
104
  return objValue.concat(srcValue);
84
105
  }
85
106
  });
107
+ // When we created the editor ourselves, persist staged side effects (e.g. Gruntfile.js deletion).
108
+ // When the caller passed an editor, they own the commit (migration flow commits once at the end).
109
+ if (ownsEditor) {
110
+ await new Promise((resolve, reject) => fs.commit((err) => (err ? reject(err) : resolve())));
111
+ }
86
112
  return JSON.stringify(mergedContent, null, 2);
87
113
  }
88
114
  /**
@@ -117,10 +143,11 @@ function removeLegacyDependencies(currentFileContent, templateContentJSON) {
117
143
  /**
118
144
  * Remove legacy Grunt module and associated file
119
145
  *
146
+ * @param fs - mem-fs editor
120
147
  * @param projectRoot
121
148
  * @param currentFileContent
122
149
  */
123
- async function removeLegacyGruntModule(projectRoot, currentFileContent) {
150
+ function removeLegacyGruntModule(fs, projectRoot, currentFileContent) {
124
151
  const gruntModule = '@sap/grunt-sapui5-bestpractice-build';
125
152
  let deleteGruntFile = false;
126
153
  if (currentFileContent?.devDependencies?.[gruntModule]) {
@@ -134,8 +161,8 @@ async function removeLegacyGruntModule(projectRoot, currentFileContent) {
134
161
  // If dependency exists, also remove the Gruntfile.js
135
162
  if (deleteGruntFile) {
136
163
  const gruntFilePath = join(projectRoot, 'Gruntfile.js');
137
- if (await fileExists(gruntFilePath)) {
138
- deleteFile(gruntFilePath);
164
+ if (fileExists(fs, gruntFilePath)) {
165
+ deleteFile(fs, gruntFilePath);
139
166
  }
140
167
  }
141
168
  }
package/package.json CHANGED
@@ -10,7 +10,7 @@
10
10
  "bugs": {
11
11
  "url": "https://github.com/SAP/open-ux-tools/issues?q=is%3Aopen+is%3Aissue+label%3Abug+label%3Afiori-migration-writer"
12
12
  },
13
- "version": "0.0.5",
13
+ "version": "0.0.6",
14
14
  "license": "Apache-2.0",
15
15
  "main": "dist/index.js",
16
16
  "files": [
@@ -37,17 +37,17 @@
37
37
  "uuid": "11.1.1",
38
38
  "vscode-uri": "3.1.0",
39
39
  "yaml": "2.9.0",
40
- "@sap-ux/adp-tooling": "1.0.66",
41
40
  "@sap-ux/axios-extension": "2.1.3",
42
41
  "@sap-ux/btp-utils": "2.0.8",
43
42
  "@sap-ux/i18n": "1.0.6",
44
43
  "@sap-ux/launch-config": "1.0.25",
44
+ "@sap-ux/adp-tooling": "1.0.66",
45
45
  "@sap-ux/nodejs-utils": "1.0.12",
46
46
  "@sap-ux/project-access": "2.3.1",
47
- "@sap-ux/store": "2.0.7",
48
47
  "@sap-ux/ui5-application-writer": "2.1.6",
49
48
  "@sap-ux/ui5-config": "1.0.10",
50
- "@sap-ux/ui5-info": "1.0.7"
49
+ "@sap-ux/ui5-info": "1.0.7",
50
+ "@sap-ux/store": "2.0.7"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@jest/globals": "30.4.1",