@sap-ux/fiori-migration-writer 0.0.4 → 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 +9 -9
@@ -1,4 +1,5 @@
1
1
  import { dirname, join, relative, sep } from 'node:path';
2
+ import { createMemFsEditor } from './fs-adapter.js';
2
3
  import { getReuseLibs, findAllProjectRoots, ReuseLibType } from './file-discovery.js';
3
4
  import { DirName } from '../project-spec-types.js';
4
5
  import { legacyPath, findAllWebIDEProjectFolders, findProjectsByManifest } from './common.js';
@@ -11,18 +12,19 @@ import { processRegularProject } from '../project/regular-project.js';
11
12
  import { processReuseLibrary } from '../project/reuse-library.js';
12
13
  import { i18nText } from '../i18n.js';
13
14
  // Import extracted readers and utilities
14
- import { getWebappPath, getManifestJson, getManifestPath, getPackageJson, getPackageJsonPath, getVersionFromManifest, getSemanticObjectAction, getFlpIntentFromHtml, getFloorPlan, getMainEntity, getFirstBackend, checkMigratableAdaptationProject, isFioriToolsAdaptationProject, readProjectExtensionSettings, checkIfProjectExtension, getExtensionProjectModuleName, checkIfReuseLib, findNestedReuseLibManifest, getReuseLibModuleName, isSamePath, getClientFromDestinationName, getNeoAppData, getDestinationFromNeoApp } from './project-readers/index.js';
15
+ import { getWebappPath, getManifestJson, getManifestPath, getPackageJson, getPackageJsonPath, getVersionFromManifest, getSemanticObjectAction, getFlpIntentFromHtml, getFloorPlan, getMainEntity, getFirstBackend, checkMigratableAdaptationProject, isFioriToolsAdaptationProject, readProjectExtensionSettings, checkIfProjectExtension, getExtensionProjectModuleName, checkIfReuseLib, findNestedReuseLibManifest, getReuseLibModuleName, getClientFromDestinationName, getNeoAppData, getDestinationFromNeoApp, isSamePath } from './project-readers/index.js';
15
16
  /**
16
17
  * Try to load manifest from standard or legacy paths
17
18
  *
19
+ * @param fs - mem-fs editor
18
20
  * @param projectRoot - Project root path
19
21
  * @param webappPath - Webapp path
20
22
  * @returns Manifest and updated webapp path, or undefined if not found
21
23
  */
22
- async function tryLoadManifest(projectRoot, webappPath) {
24
+ async function tryLoadManifest(fs, projectRoot, webappPath) {
23
25
  // Try standard location first
24
26
  try {
25
- const manifest = await getManifestJson(projectRoot, webappPath);
27
+ const manifest = await getManifestJson(fs, projectRoot, webappPath);
26
28
  return { manifest, updatedWebappPath: webappPath };
27
29
  }
28
30
  catch {
@@ -30,7 +32,7 @@ async function tryLoadManifest(projectRoot, webappPath) {
30
32
  }
31
33
  // Try legacy path
32
34
  try {
33
- const manifest = await getManifestJson(join(projectRoot, legacyPath), webappPath);
35
+ const manifest = await getManifestJson(fs, join(projectRoot, legacyPath), webappPath);
34
36
  return { manifest, updatedWebappPath: join(legacyPath, webappPath) };
35
37
  }
36
38
  catch {
@@ -41,11 +43,12 @@ async function tryLoadManifest(projectRoot, webappPath) {
41
43
  /**
42
44
  * Try to find manifest in nested reuse lib structure
43
45
  *
46
+ * @param fs - mem-fs editor
44
47
  * @param projectRoot - Project root path
45
48
  * @returns Manifest and webapp path if found
46
49
  */
47
- async function tryFindNestedLibManifest(projectRoot) {
48
- const nestedLib = await findNestedReuseLibManifest(projectRoot);
50
+ async function tryFindNestedLibManifest(fs, projectRoot) {
51
+ const nestedLib = await findNestedReuseLibManifest(fs, projectRoot);
49
52
  if (nestedLib) {
50
53
  return {
51
54
  manifest: nestedLib.manifest,
@@ -57,6 +60,7 @@ async function tryFindNestedLibManifest(projectRoot) {
57
60
  /**
58
61
  * Handle manifest resolution when no manifest was found in standard locations
59
62
  *
63
+ * @param fs - mem-fs editor
60
64
  * @param projectRoot - Project root path
61
65
  * @param migrationType - Migration type
62
66
  * @param isProjectExtension - Whether project is an extension
@@ -64,13 +68,13 @@ async function tryFindNestedLibManifest(projectRoot) {
64
68
  * @param checkIfReuseLib - Function to check if reuse library
65
69
  * @returns Resolution result with manifest, webappPath, isReuseLib flag, and messages
66
70
  */
67
- async function resolveManifestForUnknownProject(projectRoot, migrationType, isProjectExtension, checkMigratableAdaptationProject, checkIfReuseLib) {
71
+ async function resolveManifestForUnknownProject(fs, projectRoot, migrationType, isProjectExtension, checkMigratableAdaptationProject, checkIfReuseLib) {
68
72
  const messages = [];
69
73
  let isReuseLib = await checkIfReuseLib(projectRoot, migrationType);
70
74
  let manifest;
71
75
  let webappPath;
72
76
  if (!isReuseLib && !isProjectExtension) {
73
- const nestedLib = await tryFindNestedLibManifest(projectRoot);
77
+ const nestedLib = await tryFindNestedLibManifest(fs, projectRoot);
74
78
  if (nestedLib) {
75
79
  manifest = nestedLib.manifest;
76
80
  webappPath = nestedLib.webappPath;
@@ -135,8 +139,10 @@ export class ProjectAccess {
135
139
  * @param migrationType
136
140
  * @param workspaceFolders
137
141
  * @param libPath
142
+ * @param fsEditor - optional mem-fs editor; a fresh one is created when omitted (standalone discovery)
138
143
  */
139
- static async getProjectInfo(projectRoot, migrationType, workspaceFolders, libPath) {
144
+ static async getProjectInfo(projectRoot, migrationType, workspaceFolders, libPath, fsEditor) {
145
+ const fs = fsEditor ?? createMemFsEditor();
140
146
  let manifest;
141
147
  let messages = [];
142
148
  let isReuseLib;
@@ -146,18 +152,18 @@ export class ProjectAccess {
146
152
  hasRootIntent: { ...defaultProjectInfo.hasRootIntent }
147
153
  };
148
154
  // Get initial webapp path
149
- projectInfo.webappPath = await this.getWebappPath(projectRoot, migrationType, libPath);
155
+ projectInfo.webappPath = await this.getWebappPath(projectRoot, migrationType, libPath, fs);
150
156
  // Try to load manifest from standard or legacy paths
151
- const manifestResult = await tryLoadManifest(projectRoot, projectInfo.webappPath);
157
+ const manifestResult = await tryLoadManifest(fs, projectRoot, projectInfo.webappPath);
152
158
  if (manifestResult) {
153
159
  manifest = manifestResult.manifest;
154
160
  projectInfo.webappPath = manifestResult.updatedWebappPath;
155
161
  }
156
162
  // Check project type flags
157
- const isProjectExtension = await this.checkIfProjectExtension(projectRoot);
163
+ const isProjectExtension = await this.checkIfProjectExtension(projectRoot, fs);
158
164
  // If no manifest found, resolve from unknown project structure
159
165
  if (!manifest) {
160
- const resolution = await resolveManifestForUnknownProject(projectRoot, migrationType, isProjectExtension, this.checkMigratableAdaptationProject.bind(this), this.checkIfReuseLib.bind(this));
166
+ const resolution = await resolveManifestForUnknownProject(fs, projectRoot, migrationType, isProjectExtension, (root) => this.checkMigratableAdaptationProject(root, fs), (root, type) => this.checkIfReuseLib(root, type, undefined, fs));
161
167
  manifest = resolution.manifest;
162
168
  if (resolution.webappPath) {
163
169
  projectInfo.webappPath = resolution.webappPath;
@@ -166,13 +172,13 @@ export class ProjectAccess {
166
172
  messages = messages.concat(resolution.messages);
167
173
  }
168
174
  // Determine reuse lib status if not yet known
169
- isReuseLib ??= await this.checkIfReuseLib(projectRoot, migrationType, manifest);
175
+ isReuseLib ??= await this.checkIfReuseLib(projectRoot, migrationType, manifest, fs);
170
176
  // Check adaptation project status
171
- const adaptationCheck = await this.checkMigratableAdaptationProject(projectRoot);
177
+ const adaptationCheck = await this.checkMigratableAdaptationProject(projectRoot, fs);
172
178
  const isAdaptationProject = adaptationCheck?.reference !== undefined;
173
179
  // Process based on project type
174
180
  const projectType = determineProjectType(isReuseLib, isProjectExtension, isAdaptationProject, manifest);
175
- const result = await this.processProjectByType(projectType, projectRoot, manifest, projectInfo, workspaceFolders);
181
+ const result = await this.processProjectByType(fs, projectType, projectRoot, manifest, projectInfo, workspaceFolders);
176
182
  projectInfo = result.projectInfo;
177
183
  messages = messages.concat(result.messages);
178
184
  return { projectInfo, messages };
@@ -180,6 +186,7 @@ export class ProjectAccess {
180
186
  /**
181
187
  * Process project based on its type
182
188
  *
189
+ * @param fs - mem-fs editor
183
190
  * @param projectType - Type of project to process
184
191
  * @param projectRoot - Project root path
185
192
  * @param manifest - Project manifest
@@ -187,17 +194,18 @@ export class ProjectAccess {
187
194
  * @param workspaceFolders - Workspace folders
188
195
  * @returns Processed project info and messages
189
196
  */
190
- static async processProjectByType(projectType, projectRoot, manifest, projectInfo, workspaceFolders) {
197
+ static async processProjectByType(fs, projectType, projectRoot, manifest, projectInfo, workspaceFolders) {
191
198
  switch (projectType) {
192
199
  case 'regular':
193
200
  return processRegularProject({
201
+ fs,
194
202
  projectRoot,
195
203
  manifest: manifest,
196
204
  defaultProjectInfo,
197
205
  projectInfo,
198
206
  services: {
199
207
  projectData: {
200
- getPackageJson: this.getPackageJson.bind(this),
208
+ getPackageJson: (filePath) => this.getPackageJson(filePath, fs),
201
209
  getMainEntity: this.getMainEntity.bind(this),
202
210
  hasUI5Tooling: this.hasUI5Tooling.bind(this)
203
211
  },
@@ -207,18 +215,28 @@ export class ProjectAccess {
207
215
  getFloorPlan: this.getFloorPlan.bind(this)
208
216
  },
209
217
  backendConfig: {
210
- getFirstBackend: this.getFirstBackend.bind(this),
211
- getFlpIntentFromHtml: this.getFlpIntentFromHtml.bind(this),
212
- getDestinationFromNeoApp: this.getDestinationFromNeoApp.bind(this),
218
+ getFirstBackend: (root) => this.getFirstBackend(root, fs),
219
+ getFlpIntentFromHtml: (htmlFilePath) => this.getFlpIntentFromHtml(htmlFilePath, fs),
220
+ getDestinationFromNeoApp: (root, destinationIn) => this.getDestinationFromNeoApp(root, destinationIn, fs),
213
221
  getClientFromDestinationName: this.getClientFromDestinationName.bind(this)
214
222
  }
215
223
  }
216
224
  });
217
- case 'reuseLib':
225
+ case 'reuseLib': {
226
+ const reuseLibInfo = await processReuseLibrary(projectRoot, defaultProjectInfo, workspaceFolders, manifest, (filePath) => this.getPackageJson(filePath, fs), this.hasUI5Tooling.bind(this), (root, folders, mf) => this.getReuseLibModuleName(root, folders, mf, fs));
218
227
  return {
219
- projectInfo: await processReuseLibrary(projectRoot, defaultProjectInfo, workspaceFolders, manifest, this.getPackageJson.bind(this), this.hasUI5Tooling.bind(this), this.getReuseLibModuleName.bind(this)),
228
+ // Preserve the webappPath resolved before processReuseLibrary (e.g. from .project.json)
229
+ // and set the default targetMockHtmlFile for libraries.
230
+ projectInfo: {
231
+ ...reuseLibInfo,
232
+ webappPath: projectInfo.webappPath !== DirName.Webapp
233
+ ? projectInfo.webappPath
234
+ : reuseLibInfo.webappPath,
235
+ targetMockHtmlFile: 'test/flpSandboxMockServer.html'
236
+ },
220
237
  messages: []
221
238
  };
239
+ }
222
240
  case 'extension':
223
241
  return {
224
242
  projectInfo: await processProjectExtension({
@@ -226,15 +244,15 @@ export class ProjectAccess {
226
244
  defaultProjectInfo,
227
245
  projectInfo,
228
246
  manifest,
229
- getPackageJson: this.getPackageJson.bind(this),
247
+ getPackageJson: (filePath) => this.getPackageJson(filePath, fs),
230
248
  hasUI5Tooling: this.hasUI5Tooling.bind(this),
231
- readProjectExtensionSettings: this.readProjectExtensionSettings.bind(this),
249
+ readProjectExtensionSettings: (root) => this.readProjectExtensionSettings(root, fs),
232
250
  getExtensionProjectModuleName: this.getExtensionProjectModuleName.bind(this),
233
- getDestinationFromNeoApp: this.getDestinationFromNeoApp.bind(this),
234
- getFirstBackend: this.getFirstBackend.bind(this),
251
+ getDestinationFromNeoApp: (root, destinationIn) => this.getDestinationFromNeoApp(root, destinationIn, fs),
252
+ getFirstBackend: (root) => this.getFirstBackend(root, fs),
235
253
  getClientFromDestinationName: this.getClientFromDestinationName.bind(this),
236
- getFlpIntentFromHtml: this.getFlpIntentFromHtml.bind(this),
237
- getManifestJson: this.getManifestJson.bind(this)
254
+ getFlpIntentFromHtml: (htmlFilePath) => this.getFlpIntentFromHtml(htmlFilePath, fs),
255
+ getManifestJson: (root, webappPath) => this.getManifestJson(root, webappPath, fs)
238
256
  }),
239
257
  messages: []
240
258
  };
@@ -245,12 +263,12 @@ export class ProjectAccess {
245
263
  projectRoot,
246
264
  defaultProjectInfo,
247
265
  projectInfo,
248
- checkMigratableAdaptationProject: this.checkMigratableAdaptationProject.bind(this),
249
- getPackageJson: this.getPackageJson.bind(this),
266
+ checkMigratableAdaptationProject: (root) => this.checkMigratableAdaptationProject(root, fs),
267
+ getPackageJson: (filePath) => this.getPackageJson(filePath, fs),
250
268
  hasUI5Tooling: this.hasUI5Tooling.bind(this),
251
- getDestinationFromNeoApp: this.getDestinationFromNeoApp.bind(this),
269
+ getDestinationFromNeoApp: (root, destinationIn) => this.getDestinationFromNeoApp(root, destinationIn, fs),
252
270
  getClientFromDestinationName: this.getClientFromDestinationName.bind(this),
253
- getFirstBackend: this.getFirstBackend.bind(this)
271
+ getFirstBackend: (root) => this.getFirstBackend(root, fs)
254
272
  }),
255
273
  messages: []
256
274
  };
@@ -258,8 +276,8 @@ export class ProjectAccess {
258
276
  }
259
277
  // ========== Delegated Methods to Utilities ==========
260
278
  // These methods delegate to extracted utility modules for better organization
261
- static async getWebappPath(projectRoot, type, libPath) {
262
- return getWebappPath(projectRoot, type, libPath);
279
+ static async getWebappPath(projectRoot, type, libPath, fsEditor) {
280
+ return getWebappPath(fsEditor ?? createMemFsEditor(), projectRoot, type, libPath);
263
281
  }
264
282
  static getManifestPath(projectRoot, webappPath) {
265
283
  return getManifestPath(projectRoot, webappPath);
@@ -267,11 +285,11 @@ export class ProjectAccess {
267
285
  static getPackageJsonPath(projectRoot) {
268
286
  return getPackageJsonPath(projectRoot);
269
287
  }
270
- static getPackageJson(filePath) {
271
- return getPackageJson(filePath);
288
+ static getPackageJson(filePath, fsEditor) {
289
+ return getPackageJson(fsEditor ?? createMemFsEditor(), filePath);
272
290
  }
273
- static async getManifestJson(projectRoot, webappPath) {
274
- return getManifestJson(projectRoot, webappPath);
291
+ static async getManifestJson(projectRoot, webappPath, fsEditor) {
292
+ return getManifestJson(fsEditor ?? createMemFsEditor(), projectRoot, webappPath);
275
293
  }
276
294
  static getVersionFromManifest(manifest) {
277
295
  return getVersionFromManifest(manifest);
@@ -279,8 +297,8 @@ export class ProjectAccess {
279
297
  static getSemanticObjectAction(manifest) {
280
298
  return getSemanticObjectAction(manifest);
281
299
  }
282
- static async getFlpIntentFromHtml(htmlFilePath) {
283
- return getFlpIntentFromHtml(htmlFilePath);
300
+ static getFlpIntentFromHtml(htmlFilePath, fsEditor) {
301
+ return getFlpIntentFromHtml(fsEditor ?? createMemFsEditor(), htmlFilePath);
284
302
  }
285
303
  static getFloorPlan(manifest, feVersion) {
286
304
  return getFloorPlan(manifest, feVersion);
@@ -288,26 +306,26 @@ export class ProjectAccess {
288
306
  static getMainEntity(manifest) {
289
307
  return getMainEntity(manifest);
290
308
  }
291
- static getFirstBackend(projectRoot) {
292
- return getFirstBackend(projectRoot);
309
+ static getFirstBackend(projectRoot, fsEditor) {
310
+ return getFirstBackend(fsEditor ?? createMemFsEditor(), projectRoot);
293
311
  }
294
- static async checkMigratableAdaptationProject(projectRoot) {
295
- return checkMigratableAdaptationProject(projectRoot);
312
+ static async checkMigratableAdaptationProject(projectRoot, fsEditor) {
313
+ return checkMigratableAdaptationProject(fsEditor ?? createMemFsEditor(), projectRoot);
296
314
  }
297
- static async checkIfReuseLib(projectRoot, type, manifest) {
298
- return checkIfReuseLib(projectRoot, type, manifest);
315
+ static async checkIfReuseLib(projectRoot, type, manifest, fsEditor) {
316
+ return checkIfReuseLib(projectRoot, type, manifest, fsEditor ?? createMemFsEditor());
299
317
  }
300
- static async readProjectExtensionSettings(projectRoot) {
301
- return readProjectExtensionSettings(projectRoot);
318
+ static async readProjectExtensionSettings(projectRoot, fsEditor) {
319
+ return readProjectExtensionSettings(fsEditor ?? createMemFsEditor(), projectRoot);
302
320
  }
303
- static async checkIfProjectExtension(projectRoot) {
304
- return checkIfProjectExtension(projectRoot);
321
+ static async checkIfProjectExtension(projectRoot, fsEditor) {
322
+ return checkIfProjectExtension(fsEditor ?? createMemFsEditor(), projectRoot);
305
323
  }
306
- static async isFioriToolsAdaptationProject(projectRoot) {
307
- return isFioriToolsAdaptationProject(projectRoot);
324
+ static isFioriToolsAdaptationProject(projectRoot, fsEditor) {
325
+ return isFioriToolsAdaptationProject(fsEditor ?? createMemFsEditor(), projectRoot);
308
326
  }
309
- static async getReuseLibModuleName(projectRoot, workspaceFolders, manifest) {
310
- return getReuseLibModuleName(projectRoot, workspaceFolders, manifest);
327
+ static async getReuseLibModuleName(projectRoot, workspaceFolders, manifest, fsEditor) {
328
+ return getReuseLibModuleName(projectRoot, workspaceFolders, manifest, fsEditor ?? createMemFsEditor());
311
329
  }
312
330
  static getExtensionProjectModuleName(projectRoot, projectSettings, manifest) {
313
331
  return getExtensionProjectModuleName(projectRoot, projectSettings, manifest);
@@ -315,19 +333,20 @@ export class ProjectAccess {
315
333
  static getNeoAppData(neoAppJson, destination, isUiAdaptation = false) {
316
334
  return getNeoAppData(neoAppJson, destination, isUiAdaptation);
317
335
  }
318
- static async getDestinationFromNeoApp(projectRoot, destinationIn) {
319
- return getDestinationFromNeoApp(projectRoot, destinationIn);
336
+ static async getDestinationFromNeoApp(projectRoot, destinationIn, fsEditor) {
337
+ return getDestinationFromNeoApp(fsEditor ?? createMemFsEditor(), projectRoot, destinationIn);
320
338
  }
321
339
  static getClientFromDestinationName(destination) {
322
340
  return getClientFromDestinationName(destination);
323
341
  }
324
342
  // ========== Core Methods (Not Delegated) ==========
325
- static async getProjectRoots(workspaceRoots, sapuxRequired = false) {
343
+ static async getProjectRoots(workspaceRoots, sapuxRequired = false, fsEditor) {
344
+ const fs = fsEditor ?? createMemFsEditor();
326
345
  // find projects by package.json and pom.xml
327
346
  // Convert ProjectFolder[] to string[] for project-access functions
328
347
  const workspaceRootPaths = workspaceRoots.map((folder) => folder.uri.fsPath);
329
- const packageRoots = await findAllProjectRoots(workspaceRootPaths, sapuxRequired);
330
- const pomRoots = await findAllWebIDEProjectFolders(workspaceRoots);
348
+ const packageRoots = await findAllProjectRoots(fs, workspaceRootPaths, sapuxRequired);
349
+ const pomRoots = await findAllWebIDEProjectFolders(fs, workspaceRoots);
331
350
  // find projects by manifest.json in the root
332
351
  let manifestRoots = await findProjectsByManifest(workspaceRoots);
333
352
  // A root can be discovered by more than one strategy (e.g. a project with both package.json
@@ -347,7 +366,7 @@ export class ProjectAccess {
347
366
  };
348
367
  });
349
368
  // find reuse libs
350
- let reuseLibsRoots = (await getReuseLibs(rootFolders)).filter((lib) => lib.value.type === ReuseLibType.LIBRARY);
369
+ let reuseLibsRoots = (await getReuseLibs(fs, rootFolders)).filter((lib) => lib.value.type === ReuseLibType.LIBRARY);
351
370
  // Roots that are themselves reuse libraries must be emitted only as libraries, never also as
352
371
  // plain projects. getReuseLibs assigns a nested library to its nearest package/pom root, so a
353
372
  // freestyle app that merely embeds a library shares that libRoot — suppressing by libRoot alone
@@ -360,17 +379,18 @@ export class ProjectAccess {
360
379
  if (reuseLibsRoots.length) {
361
380
  const reuseLibRootPaths = new Set(reuseLibsRoots.map((lib) => lib.value.libRoot));
362
381
  const appRoots = [];
363
- // Fetch all project info in parallel for performance
364
- const projectInfoResults = await Promise.all(roots.map((root) => this.getProjectInfo(root)));
365
- for (let i = 0; i < roots.length; i++) {
366
- const root = roots[i];
367
- const result = projectInfoResults[i];
368
- const isApp = !!(result.projectInfo.FEVersion || result.projectInfo.isSAPApp);
382
+ // Resolve project info for all roots in parallel (independent lookups).
383
+ const rootInfos = await Promise.all(roots.map(async (root) => ({
384
+ root,
385
+ info: (await this.getProjectInfo(root, undefined, undefined, undefined, fs)).projectInfo
386
+ })));
387
+ for (const { root, info } of rootInfos) {
388
+ const isApp = !!(info.FEVersion || info.isSAPApp);
369
389
  if (isApp) {
370
390
  appRoots.push(root);
371
391
  }
372
392
  else if ([...reuseLibRootPaths].some((libRoot) => isSamePath(libRoot, root)) ||
373
- result.projectInfo.type === MigrationTypes.library) {
393
+ info.type === MigrationTypes.library) {
374
394
  libraryOnlyRoots.add(root);
375
395
  }
376
396
  }
@@ -388,14 +408,16 @@ export class ProjectAccess {
388
408
  });
389
409
  // A single library root can contain several nested manifest.json files (the library manifest
390
410
  // plus per-component manifests), each of which getReuseLibs resolves back to the same libRoot.
391
- // Emit one entry per library root so the webview does not list the same library multiple times.
392
- const seenLibPaths = [];
411
+ // The emitted entry is keyed on libRoot (root: lib.value.libRoot), so deduplicate on libRoot —
412
+ // not on lib.value.path, which is distinct per nested manifest and would let the same library
413
+ // through multiple times. Keep the first occurrence so libRoot/libPath stay paired (PR #39527).
414
+ const seenLibRoots = [];
393
415
  const reuseLibsUnique = reuseLibsRoots
394
416
  .filter((lib) => {
395
- if (seenLibPaths.some((path) => isSamePath(path, lib.value.path))) {
417
+ if (seenLibRoots.some((libRoot) => isSamePath(libRoot, lib.value.libRoot))) {
396
418
  return false;
397
419
  }
398
- seenLibPaths.push(lib.value.path);
420
+ seenLibRoots.push(lib.value.libRoot);
399
421
  return true;
400
422
  })
401
423
  .map((lib) => {
@@ -1,5 +1,6 @@
1
+ import type { Editor } from 'mem-fs-editor';
1
2
  import type { MigrationTypes } from './constants.js';
2
- export declare const readMigrationSettingsFile: () => Promise<any>;
3
- export declare const readMigrationSettingsFileIgnoreProjects: () => Promise<string[]>;
4
- export declare const checkForMigration: (projectRoot: string, type: MigrationTypes, libPath?: string) => Promise<boolean>;
3
+ export declare const readMigrationSettingsFile: (fsEditor?: Editor) => Promise<any>;
4
+ export declare const readMigrationSettingsFileIgnoreProjects: (fsEditor?: Editor) => Promise<string[]>;
5
+ export declare const checkForMigration: (projectRoot: string, type: MigrationTypes, libPath?: string, fsEditor?: Editor) => Promise<boolean>;
5
6
  //# sourceMappingURL=checkForMigration.d.ts.map
@@ -2,23 +2,26 @@ import { ProjectAccess } from './Project.js';
2
2
  import { join } from 'node:path';
3
3
  import { getFioriToolsDirectory } from '@sap-ux/store';
4
4
  import { FileName, FioriToolsSettings } from '@sap-ux/project-access';
5
+ import { createMemFsEditor } from './fs-adapter.js';
5
6
  import { hasDependency, readJSON, findProjectRoot } from './index.js';
6
- export const readMigrationSettingsFile = async () => {
7
+ export const readMigrationSettingsFile = async (fsEditor) => {
8
+ const fs = fsEditor ?? createMemFsEditor();
7
9
  let migrationSettingsFile;
8
10
  try {
9
- migrationSettingsFile = await readJSON(join(getFioriToolsDirectory(), FioriToolsSettings.migrationSettingsFile));
11
+ migrationSettingsFile = await readJSON(fs, join(getFioriToolsDirectory(), FioriToolsSettings.migrationSettingsFile));
10
12
  }
11
13
  catch (error) {
12
14
  migrationSettingsFile = {};
13
15
  }
14
16
  return migrationSettingsFile;
15
17
  };
16
- export const readMigrationSettingsFileIgnoreProjects = async () => {
17
- const migrationSettingsFile = await readMigrationSettingsFile();
18
+ export const readMigrationSettingsFileIgnoreProjects = async (fsEditor) => {
19
+ const migrationSettingsFile = await readMigrationSettingsFile(fsEditor);
18
20
  migrationSettingsFile.ignoreProjects = migrationSettingsFile?.ignoreProjects ?? [];
19
21
  return migrationSettingsFile?.ignoreProjects;
20
22
  };
21
- export const checkForMigration = async (projectRoot, type, libPath) => {
23
+ export const checkForMigration = async (projectRoot, type, libPath, fsEditor) => {
24
+ const fs = fsEditor ?? createMemFsEditor();
22
25
  let project = {
23
26
  projectInfo: {},
24
27
  messages: []
@@ -26,8 +29,8 @@ export const checkForMigration = async (projectRoot, type, libPath) => {
26
29
  let packageJson;
27
30
  let hasUi5Tooling = false;
28
31
  try {
29
- project = await ProjectAccess.getProjectInfo(projectRoot, type, undefined, libPath);
30
- packageJson = await readJSON(join(projectRoot, FileName.Package));
32
+ project = await ProjectAccess.getProjectInfo(projectRoot, type, undefined, libPath, fs);
33
+ packageJson = await readJSON(fs, join(projectRoot, FileName.Package));
31
34
  hasUi5Tooling =
32
35
  hasDependency(packageJson, '@sap-ux/ui5-tooling') || hasDependency(packageJson, '@sap/ux-ui5-tooling');
33
36
  }
@@ -48,7 +51,7 @@ export const checkForMigration = async (projectRoot, type, libPath) => {
48
51
  Array.isArray(project.projectInfo.sapux) ||
49
52
  project.messages.length > 0 ||
50
53
  hasUi5Tooling === true ||
51
- (await readMigrationSettingsFileIgnoreProjects()).includes(projectRoot) ||
54
+ (await readMigrationSettingsFileIgnoreProjects(fs)).includes(projectRoot) ||
52
55
  sapUXProjectRoot !== projectRoot);
53
56
  return migratable;
54
57
  };
@@ -1,58 +1,60 @@
1
1
  import type { Editor } from 'mem-fs-editor';
2
2
  /**
3
- * Read a text file
3
+ * Read a text file.
4
4
  *
5
- * @param pathOrFs - Path to file, or Editor instance
6
- * @param path - Path to file (if first param is Editor)
5
+ * @param fs - mem-fs editor
6
+ * @param path - Path to file
7
7
  * @returns File contents as string
8
8
  */
9
- export declare function readFile(pathOrFs: string | Editor, path?: string): string;
9
+ export declare function readFile(fs: Editor, path: string): string;
10
10
  /**
11
- * Read a JSON file
11
+ * Read and parse a JSON file, preserving indentation metadata (Symbol(indent))
12
+ * so that a later updateJSON can keep the original formatting.
12
13
  *
13
- * @param pathOrFs - Path to file, or Editor instance
14
- * @param path - Path to file (if first param is Editor)
14
+ * @param fs - mem-fs editor
15
+ * @param path - Path to file
15
16
  * @returns Parsed JSON object
16
17
  */
17
- export declare function readJSON<T = any>(pathOrFs: string | Editor, path?: string): Promise<T>;
18
+ export declare function readJSON<T = any>(fs: Editor, path: string): T;
18
19
  /**
19
- * Check if a file exists
20
+ * Check if a file or directory exists (staged in mem-fs or present on the real filesystem).
20
21
  *
21
- * @param pathOrFs - Path to file, or Editor instance
22
- * @param path - Path to file (if first param is Editor)
23
- * @returns true if file exists, false otherwise
22
+ * Unlike mem-fs's `editor.exists()`, this also reports directories as present — many migration
23
+ * checks test for folders (e.g. the webapp directory) — and reads through to the real filesystem
24
+ * so files not yet staged in mem-fs are still seen.
25
+ *
26
+ * @param fs - mem-fs editor
27
+ * @param path - Path to file or directory
28
+ * @returns true if the path exists
24
29
  */
25
- export declare function fileExists(pathOrFs: string | Editor, path?: string): Promise<boolean>;
30
+ export declare function fileExists(fs: Editor, path: string): boolean;
26
31
  /**
27
- * Write a text file
32
+ * Write a text file.
28
33
  *
29
- * @param pathOrFs - Path to file, or Editor instance
30
- * @param contentOrPath - Content to write, or path (if first param is Editor)
31
- * @param content - Content to write (if first param is Editor)
34
+ * @param fs - mem-fs editor
35
+ * @param path - Path to file
36
+ * @param content - Content to write
32
37
  */
33
- export declare function writeFile(pathOrFs: string | Editor, contentOrPath: string, content?: string): void;
38
+ export declare function writeFile(fs: Editor, path: string, content: string): void;
34
39
  /**
35
- * Update a text file
36
- * Alias for writeFile for backward compatibility
37
- *
38
- * @param pathOrFs - Path to file, or Editor instance
39
- * @param contentOrPath - Content to write, or path (if first param is Editor)
40
- * @param content - Content to write (if first param is Editor)
40
+ * Update a text file. Alias of writeFile; kept as a distinct name for call-site clarity
41
+ * (updating an existing file vs. creating a new one).
41
42
  */
42
- export declare function updateFile(pathOrFs: string | Editor, contentOrPath: string, content?: string): void;
43
+ export declare const updateFile: typeof writeFile;
43
44
  /**
44
- * Update a JSON file while preserving indentation
45
+ * Update a JSON file while preserving the indentation of the existing file.
46
+ * New files are written with 4-space indentation and a trailing newline.
45
47
  *
46
- * @param pathOrFs - Path to file, or Editor instance
47
- * @param contentOrPath - Object to write, or path (if first param is Editor)
48
- * @param _content - Object to write (if first param is Editor)
48
+ * @param fs - mem-fs editor
49
+ * @param path - Path to file
50
+ * @param content - Object to serialize
49
51
  */
50
- export declare function updateJSON(pathOrFs: string | Editor, contentOrPath: string | object, _content?: object): void;
52
+ export declare function updateJSON(fs: Editor, path: string, content: object): void;
51
53
  /**
52
- * Delete a file
54
+ * Delete a file.
53
55
  *
54
- * @param pathOrFs - Path to file, or Editor instance
55
- * @param path - Path to file (if first param is Editor)
56
+ * @param fs - mem-fs editor
57
+ * @param path - Path to file
56
58
  */
57
- export declare function deleteFile(pathOrFs: string | Editor, path?: string): void;
59
+ export declare function deleteFile(fs: Editor, path: string): void;
58
60
  //# sourceMappingURL=file-access.d.ts.map