@ui5/task-adaptation 1.6.5 → 1.6.7

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 (77) hide show
  1. package/CHANGELOG.md +9 -1
  2. package/README.md +0 -4
  3. package/dist/adapters/abapAdapter.d.ts +2 -1
  4. package/dist/adapters/abapAdapter.js +3 -0
  5. package/dist/adapters/adapter.d.ts +2 -1
  6. package/dist/adapters/cfAdapter.d.ts +2 -1
  7. package/dist/adapters/cfAdapter.js +14 -0
  8. package/dist/adapters/commands/command.d.ts +7 -7
  9. package/dist/adapters/commands/command.js +3 -2
  10. package/dist/adapters/commands/downloadAnnotationsCommand.d.ts +1 -1
  11. package/dist/adapters/commands/downloadAnnotationsCommand.js +4 -3
  12. package/dist/adapters/commands/fetchPreviewResourcesCommand.js +4 -2
  13. package/dist/adapters/commands/filterFilesCommand.d.ts +1 -1
  14. package/dist/adapters/commands/filterFilesCommand.js +2 -1
  15. package/dist/adapters/commands/i18nPropertiesMergeCommand.d.ts +1 -1
  16. package/dist/adapters/commands/i18nPropertiesMergeCommand.js +9 -6
  17. package/dist/adapters/commands/omitDeletedResourcesCommand.d.ts +1 -1
  18. package/dist/adapters/commands/omitDeletedResourcesCommand.js +1 -1
  19. package/dist/adapters/commands/processPreviewResourcesCommand.d.ts +1 -1
  20. package/dist/adapters/commands/processPreviewResourcesCommand.js +14 -7
  21. package/dist/adapters/commands/renameFilesCommand.d.ts +10 -2
  22. package/dist/adapters/commands/renameFilesCommand.js +45 -17
  23. package/dist/adapters/commands/renameManifestCommand.d.ts +14 -0
  24. package/dist/adapters/commands/renameManifestCommand.js +32 -0
  25. package/dist/adapters/commands/writeResourcesCommand.d.ts +1 -1
  26. package/dist/adapters/commands/xsAppJsonEnhanceRoutesCommand.d.ts +1 -1
  27. package/dist/adapters/commands/xsAppJsonEnhanceRoutesCommand.js +4 -3
  28. package/dist/adapters/commands/xsAppJsonMergeCommand.d.ts +1 -1
  29. package/dist/adapters/commands/xsAppJsonMergeCommand.js +4 -3
  30. package/dist/adapters/previewAdapter.d.ts +2 -1
  31. package/dist/adapters/previewAdapter.js +3 -0
  32. package/dist/annotations/abapAnnotationManager.js +1 -1
  33. package/dist/annotations/dataSource/dataSourceManager.d.ts +2 -1
  34. package/dist/annotations/dataSource/dataSourceManager.js +16 -8
  35. package/dist/appVariant.d.ts +4 -3
  36. package/dist/appVariant.js +14 -7
  37. package/dist/baseApp.d.ts +3 -3
  38. package/dist/baseApp.js +4 -4
  39. package/dist/bundle.js +24 -8
  40. package/dist/cache/cacheHolder.d.ts +11 -2
  41. package/dist/cache/cacheHolder.js +23 -1
  42. package/dist/index.d.ts +10 -1
  43. package/dist/index.js +24 -4
  44. package/dist/landscapeConfiguration.d.ts +3 -3
  45. package/dist/landscapeConfiguration.js +38 -28
  46. package/dist/model/types.d.ts +4 -1
  47. package/dist/repositories/abapProvider.js +2 -2
  48. package/dist/repositories/abapRepository.d.ts +1 -1
  49. package/dist/repositories/cacheRepository.d.ts +10 -0
  50. package/dist/repositories/cacheRepository.js +29 -0
  51. package/dist/repositories/html5Repository.d.ts +1 -1
  52. package/dist/repositories/localRepository.d.ts +1 -1
  53. package/dist/repositories/repository.d.ts +1 -1
  54. package/dist/util/commonUtil.d.ts +6 -1
  55. package/dist/util/commonUtil.js +16 -1
  56. package/dist/util/fsUtil.d.ts +2 -2
  57. package/dist/util/fsUtil.js +4 -4
  58. package/dist/util/manifestUtil.d.ts +6 -0
  59. package/dist/util/manifestUtil.js +15 -0
  60. package/dist/util/movingHandler/changeFileMoveHandler.d.ts +3 -3
  61. package/dist/util/movingHandler/changeFileMoveHandler.js +5 -4
  62. package/dist/util/renamingHandlers/jsonRenamingHandler.d.ts +6 -5
  63. package/dist/util/renamingHandlers/jsonRenamingHandler.js +15 -15
  64. package/dist/util/renamingUtil.d.ts +17 -0
  65. package/dist/util/renamingUtil.js +102 -81
  66. package/dist/util/resourceUtil.d.ts +15 -6
  67. package/dist/util/resourceUtil.js +45 -13
  68. package/dist/util/validator/abapConnectionValidator.d.ts +5 -0
  69. package/dist/util/validator/abapConnectionValidator.js +22 -0
  70. package/dist/util/validator/abapValidator.d.ts +2 -4
  71. package/dist/util/validator/abapValidator.js +4 -16
  72. package/dist/util/zipUtil.d.ts +1 -1
  73. package/dist/util/zipUtil.js +1 -1
  74. package/package.json +7 -7
  75. package/types/ui5.d.ts +2 -0
  76. package/dist/util/renamingHandlers/renamingHandler.d.ts +0 -4
  77. package/dist/util/renamingHandlers/renamingHandler.js +0 -2
@@ -0,0 +1,29 @@
1
+ import CacheHolder from "../cache/cacheHolder.js";
2
+ export default class CacheRepository {
3
+ configuration;
4
+ constructor(configuration) {
5
+ this.configuration = configuration;
6
+ }
7
+ async getAppVariantIdHierarchy(_appId) {
8
+ const { appName } = this.configuration;
9
+ if (!appName) {
10
+ throw new Error("previewManifest requires 'appName' in the ui5.yaml configuration");
11
+ }
12
+ return [{
13
+ appName,
14
+ cacheBusterToken: Promise.resolve("")
15
+ }];
16
+ }
17
+ async fetch(resource) {
18
+ const appName = resource.appName;
19
+ const cachedFiles = await CacheHolder.readLatest(appName);
20
+ if (cachedFiles.size === 0) {
21
+ throw new Error(`No cached base app found for '${appName}'. Please check the appName or run a full build first to populate the cache.`);
22
+ }
23
+ return cachedFiles;
24
+ }
25
+ downloadAnnotationFile(uri) {
26
+ throw new Error(`Download annotation file for uri: ${uri} is not available for CacheRepository.`);
27
+ }
28
+ }
29
+ //# sourceMappingURL=cacheRepository.js.map
@@ -10,7 +10,7 @@ export default class HTML5Repository implements IRepository {
10
10
  private validator;
11
11
  constructor(configuration: IConfiguration);
12
12
  getAppVariantIdHierarchy(_appId: string): Promise<IHtml5Resource[]>;
13
- fetch(resource: IHtml5Resource): Promise<Map<string, string>>;
13
+ fetch(resource: IHtml5Resource): Promise<Map<string, Buffer>>;
14
14
  getMetadata(): Promise<any>;
15
15
  private getHtml5RepoInfo;
16
16
  private getHTML5Credentials;
@@ -6,7 +6,7 @@ export interface ILocalResource extends ICachedResource {
6
6
  export default class LocalRepository implements IRepository {
7
7
  downloadAnnotationFile(_uri: string): Promise<Map<string, string>>;
8
8
  getAppVariantIdHierarchy(appId: string): Promise<ILocalResource[]>;
9
- fetch(resource: ILocalResource): Promise<Map<string, string>>;
9
+ fetch(resource: ILocalResource): Promise<Map<string, Buffer>>;
10
10
  private buildManifestIndex;
11
11
  private getLocalFilesDir;
12
12
  }
@@ -1,6 +1,6 @@
1
1
  import ICachedResource from "../cache/cachedResource.js";
2
2
  export default interface IRepository {
3
3
  getAppVariantIdHierarchy(appId: string): Promise<ICachedResource[]>;
4
- fetch(resource: ICachedResource): Promise<Map<string, string>>;
4
+ fetch(resource: ICachedResource): Promise<Map<string, Buffer>>;
5
5
  downloadAnnotationFile(uri: string): Promise<Map<string, string>>;
6
6
  }
@@ -2,6 +2,11 @@ import { IConfiguration } from "../model/types.js";
2
2
  import Language from "../model/language.js";
3
3
  import AppVariant from "../appVariant.js";
4
4
  export declare function dotToUnderscore(value: string): string;
5
+ export declare function bufferToString(buffer: Buffer): string;
6
+ export declare function bufferToJson(buffer: Buffer): any;
7
+ export declare function bufferToNormalizedString(buffer: Buffer): string;
8
+ export declare function stringToBuffer(value: string): Buffer;
9
+ export declare function jsonToBuffer(json: any): Buffer;
5
10
  export declare function validateConfiguration<T extends object>(options: T, properties: Array<keyof T>): void;
6
11
  export declare function validateObject<T extends object>(options: T, properties: Array<keyof T>, message: string): void;
7
12
  export declare function escapeRegex(update: string): string;
@@ -12,7 +17,7 @@ export declare function traverse(json: any, paths: string[], callback: (json: an
12
17
  export declare function logBuilderVersion(): void;
13
18
  export declare function logBetaUsage(): void;
14
19
  export declare function getUniqueName(existingNames: string[], template: string): string;
15
- export declare function isManifestChange(filename: string, content: string): boolean;
20
+ export declare function isManifestChange(filename: string, content: Buffer): boolean;
16
21
  /**
17
22
  * 4p. Reference map contains searchTerm as key and replacement as value. Base
18
23
  * id and app variant ids are renamed to adaptation project id.
@@ -9,6 +9,21 @@ const MANIFEST_CHANGE = "appdescr_";
9
9
  export function dotToUnderscore(value) {
10
10
  return value.replace(/\./g, "_");
11
11
  }
12
+ export function bufferToString(buffer) {
13
+ return buffer.toString("utf8");
14
+ }
15
+ export function bufferToJson(buffer) {
16
+ return JSON.parse(bufferToString(buffer));
17
+ }
18
+ export function bufferToNormalizedString(buffer) {
19
+ return bufferToString(buffer).replaceAll("\r\n", "\n");
20
+ }
21
+ export function stringToBuffer(value) {
22
+ return Buffer.from(value, "utf8");
23
+ }
24
+ export function jsonToBuffer(json) {
25
+ return stringToBuffer(JSON.stringify(json));
26
+ }
12
27
  export function validateConfiguration(options, properties) {
13
28
  validateObject(options, properties, "should be specified in ui5.yaml configuration");
14
29
  }
@@ -93,7 +108,7 @@ export function getUniqueName(existingNames, template) {
93
108
  }
94
109
  export function isManifestChange(filename, content) {
95
110
  if (filename.endsWith(CHANGES_EXT)) {
96
- const change = JSON.parse(content);
111
+ const change = bufferToJson(content);
97
112
  return change.changeType?.startsWith(MANIFEST_CHANGE);
98
113
  }
99
114
  return false;
@@ -1,10 +1,10 @@
1
1
  export default class FsUtil {
2
- static readInProject(filepath: string, encoding?: BufferEncoding): Promise<Buffer | string>;
2
+ static readInProject(filepath: string): Promise<Buffer>;
3
3
  /**
4
4
  * Check whether a file or directory exists (non-throwing).
5
5
  * @param filePath Absolute or relative path
6
6
  * @returns true if the path exists, false if not
7
7
  */
8
8
  private static fileExists;
9
- static readFilesRecursively(rootDirectory: string): Promise<Map<string, string>>;
9
+ static readFilesRecursively(rootDirectory: string): Promise<Map<string, Buffer>>;
10
10
  }
@@ -5,11 +5,11 @@ export default class FsUtil {
5
5
  * Read the file by filepath from the project root (folder where 'webapp',
6
6
  * 'package.json', 'ui5.yaml' located).
7
7
  * @param filepath The relative file path from the project root (e.g. 'ui5AppInfo.json').
8
- * @returns A promise that resolves to the file content as a string.
8
+ * @returns A promise that resolves to the file content as a Buffer.
9
9
  */
10
- static async readInProject(filepath, encoding) {
10
+ static async readInProject(filepath) {
11
11
  try {
12
- return await fs.readFile(path.join(process.cwd(), filepath), encoding);
12
+ return await fs.readFile(path.join(process.cwd(), filepath));
13
13
  }
14
14
  catch (error) {
15
15
  const isProjectRoot = await FsUtil.fileExists(path.join(process.cwd(), "ui5.yaml"));
@@ -44,7 +44,7 @@ export default class FsUtil {
44
44
  const parentPath = entry.parentPath ?? entry.path; // node v20.11.0 fallback
45
45
  const entryPath = path.join(parentPath, entry.name);
46
46
  const relativeFilePath = path.relative(rootDirectory, entryPath).replaceAll("\\", "/");
47
- const content = await fs.readFile(entryPath, "utf-8");
47
+ const content = await fs.readFile(entryPath);
48
48
  return [relativeFilePath, content];
49
49
  });
50
50
  return new Map(await Promise.all(fileReadTasks));
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Returns the SAPUI5 dependency ids (libs and components) declared under
3
+ * "sap.ui5"/dependencies of a parsed manifest. These point at other apps and
4
+ * must not be renamed, so callers add them to the renamer ignore list.
5
+ */
6
+ export declare function getSAPUI5DependencyIds(manifest: any): string[];
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Returns the SAPUI5 dependency ids (libs and components) declared under
3
+ * "sap.ui5"/dependencies of a parsed manifest. These point at other apps and
4
+ * must not be renamed, so callers add them to the renamer ignore list.
5
+ */
6
+ export function getSAPUI5DependencyIds(manifest) {
7
+ const dependencies = manifest?.["sap.ui5"]?.dependencies;
8
+ if (!dependencies) {
9
+ return [];
10
+ }
11
+ const libs = dependencies.libs ? Object.keys(dependencies.libs) : [];
12
+ const components = dependencies.components ? Object.keys(dependencies.components) : [];
13
+ return [...libs, ...components];
14
+ }
15
+ //# sourceMappingURL=manifestUtil.js.map
@@ -1,8 +1,8 @@
1
- export declare const moveFiles: (inputFiles: ReadonlyMap<string, string>, prefix: string, id: string) => {
2
- files: Map<string, string>;
1
+ export declare const moveFiles: (inputFiles: ReadonlyMap<string, Buffer>, prefix: string, id: string) => {
2
+ files: Map<string, Buffer<ArrayBufferLike>>;
3
3
  renamingPaths: Map<string, string>;
4
4
  };
5
- export declare const moveFile: (filename: string, content: string, prefix: string, id: string) => {
5
+ export declare const moveFile: (filename: string, content: Buffer, prefix: string, id: string) => {
6
6
  newFilename: string;
7
7
  renamingPath: Map<string, string>;
8
8
  };
@@ -1,4 +1,4 @@
1
- import { isManifestChange } from "../commonUtil.js";
1
+ import { bufferToString, isManifestChange } from "../commonUtil.js";
2
2
  const EXT_DIR = "ext/";
3
3
  const CHANGES_DIR = "changes/";
4
4
  const nameSpaceRegex = new RegExp(`(?<=ControllerExtension.extend\\(")([^"]*)(?=")`);
@@ -21,7 +21,7 @@ function shouldMove(filename, content) {
21
21
  * The namespace needs to be unique for clear identification
22
22
  * If controller extension have the same namespace the last one will be used
23
23
  * @param filename - The filename relative to the root
24
- * @returns
24
+ * @returns `true` if the file's namespace should be renamed, `false` otherwise
25
25
  */
26
26
  function shouldNamespaceRenamed(filename) {
27
27
  return filename.startsWith(CHANGES_DIR) && filename.endsWith(".js");
@@ -38,7 +38,8 @@ function shouldNamespaceRenamed(filename) {
38
38
  * the filename. Without 'changes' or 'ext' directory, we need to replace
39
39
  * only rest: coding/FixingDay.js to app_var_id1/coding/FixingDay.js
40
40
  * @param filename
41
- * @returns
41
+ * @returns The path with the leading directory and all file extensions removed,
42
+ * e.g. `"changes/coding/FixingDay.js"` → `"coding/FixingDay"`
42
43
  */
43
44
  function getPathWithoutExtensions(filename) {
44
45
  const [_dir, ...rest] = filename.split("/");
@@ -66,7 +67,7 @@ export const moveFile = (filename, content, prefix, id) => {
66
67
  renamingPath.set(restWOExtPath, [prefix, restWOExtPath].join("/"));
67
68
  }
68
69
  if (shouldNamespaceRenamed(filename)) {
69
- const namespaceMatch = nameSpaceRegex.exec(content.trim());
70
+ const namespaceMatch = nameSpaceRegex.exec(bufferToString(content).trim());
70
71
  const controllerExtensionPath = namespaceMatch ? namespaceMatch[0] : "";
71
72
  const fileName = controllerExtensionPath.replace(id, "");
72
73
  renamingPath.set(controllerExtensionPath, `${id}.${prefix}${fileName}`);
@@ -1,12 +1,13 @@
1
- import { IRenamingHandler } from "./renamingHandler.js";
2
- export default abstract class JsonRenamingHandler implements IRenamingHandler {
1
+ export default abstract class JsonRenamingHandler {
3
2
  private original;
4
3
  protected abstract filePath: string;
5
4
  protected abstract jsonPathsToRestore: string[];
6
- before(files: ReadonlyMap<string, string>): void;
7
- after(files: Map<string, string>): void;
5
+ accept(filename: string): boolean;
6
+ before(json: any): void;
7
+ after(json: any): void;
8
+ snapshot(json: any): void;
9
+ restore(obj: any): void;
8
10
  protected store(obj: any, path: string): void;
9
- protected restore(obj: any): void;
10
11
  private getByPath;
11
12
  private setByPath;
12
13
  }
@@ -1,26 +1,26 @@
1
1
  export default class JsonRenamingHandler {
2
2
  original = new Map();
3
- before(files) {
4
- const content = files.get(this.filePath);
5
- if (content) {
6
- const json = JSON.parse(content);
7
- this.jsonPathsToRestore.forEach(path => this.store(json, path));
8
- }
3
+ accept(filename) {
4
+ return filename.endsWith(this.filePath);
9
5
  }
10
- after(files) {
11
- const content = files.get(this.filePath);
12
- if (content) {
13
- const json = JSON.parse(content);
14
- this.restore(json);
15
- files.set(this.filePath, JSON.stringify(json));
16
- }
6
+ before(json) {
7
+ this.jsonPathsToRestore.forEach(path => this.store(json, path));
17
8
  }
18
- store(obj, path) {
19
- this.original.set(path, this.getByPath(obj, path.split("/")));
9
+ after(json) {
10
+ this.restore(json);
11
+ }
12
+ snapshot(json) {
13
+ this.jsonPathsToRestore.forEach(path => this.store(json, path));
20
14
  }
21
15
  restore(obj) {
22
16
  this.original.forEach((value, path) => this.setByPath(obj, value, path.split("/")));
23
17
  }
18
+ store(obj, path) {
19
+ // Clone so the snapshot is decoupled from `obj`: when the same object is
20
+ // mutated in-place (JSON renaming), the stored value stays untouched.
21
+ const value = this.getByPath(obj, path.split("/"));
22
+ this.original.set(path, value === undefined ? undefined : structuredClone(value));
23
+ }
24
24
  getByPath(obj, path) {
25
25
  return path.reduce((o, p) => (o && o[p]) || undefined, obj);
26
26
  }
@@ -1,3 +1,20 @@
1
1
  export declare function rename(content: string, references: string[], replacement: string): string;
2
+ /**
3
+ * A precompiled renamer: the regex, replacement lookup and ignore set are built
4
+ * once and can be applied to many strings via `replace` without recompilation.
5
+ */
6
+ export interface Renamer {
7
+ replace(content: string): string;
8
+ }
9
+ /**
10
+ * Builds a reusable {@link Renamer} from the given references and ignore list.
11
+ * The expensive work (regex compilation, lookup construction) happens here once;
12
+ * the returned `replace` is a cheap single pass over each input string. Prefer
13
+ * this over calling {@link renameMap} repeatedly with the same references.
14
+ *
15
+ * Note: mutates `references` by dropping entries whose dot-count doesn't match.
16
+ */
17
+ export declare function compileRenamer(references: Map<string, string>, ignoreInStrings: string[]): Renamer;
2
18
  export declare function renameMap(content: string, references: Map<string, string>, ignoreInStrings: string[]): string;
19
+ export declare function renameJson(json: Record<string, unknown>, references: Map<string, string>, ignoreInStrings: string[]): void;
3
20
  export declare function renameResources(files: ReadonlyMap<string, string>, search: string[], replacement: string): Map<string, string>;
@@ -1,12 +1,23 @@
1
1
  import { getLogger } from "@ui5/logger";
2
2
  const log = getLogger("@ui5/task-adaptation::RenamingUtil");
3
+ const dotToSlash = (string) => string.replaceAll(".", "/");
4
+ const escapeRegex = (string) => string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3
5
  export function rename(content, references, replacement) {
4
6
  return renameMap(content, new Map(references.map(ref => [ref, replacement])), [replacement]);
5
7
  }
6
- export function renameMap(content, references, ignoreInStrings) {
8
+ /**
9
+ * Builds a reusable {@link Renamer} from the given references and ignore list.
10
+ * The expensive work (regex compilation, lookup construction) happens here once;
11
+ * the returned `replace` is a cheap single pass over each input string. Prefer
12
+ * this over calling {@link renameMap} repeatedly with the same references.
13
+ *
14
+ * Note: mutates `references` by dropping entries whose dot-count doesn't match.
15
+ */
16
+ export function compileRenamer(references, ignoreInStrings) {
7
17
  const ignoredReferenceKeys = [];
8
18
  for (const [key, value] of references) {
9
19
  if (value.includes(".") !== key.includes(".")) {
20
+ log.info(`Ignoring renaming of "${key}" → "${value}" because the dot count does not match.`);
10
21
  ignoredReferenceKeys.push(key);
11
22
  references.delete(key);
12
23
  }
@@ -15,97 +26,107 @@ export function renameMap(content, references, ignoreInStrings) {
15
26
  log.info(`Ignored renaming: ${ignoredReferenceKeys.join(", ")}`);
16
27
  }
17
28
  const searchTerms = [...references.keys()].filter(key => key.length > 0);
18
- if (!content || !searchTerms || searchTerms.length === 0) {
19
- return content;
29
+ if (searchTerms.length === 0) {
30
+ return {
31
+ replace: (content) => content
32
+ };
20
33
  }
21
- const dotToSlash = (str) => str.replaceAll(".", "\/");
22
- // We don't want to replace in adaptation project ids
23
- ignoreInStrings.push(...ignoreInStrings, ...ignoreInStrings.map(dotToSlash));
24
- let start = 0;
25
- while (true) {
26
- // If we don't replace some strings in the content - we find all of them
27
- // and then don't replace inside their start and end indices.
28
- const ignoredStrings = ignoreInStrings.map(string => {
29
- return findAllOccurrences(content, string, start).map(i => ({ start: i, end: i + string.length }));
30
- }).filter(arr => arr.length > 0) || [];
31
- // We find the next search index with dots and slashes. Then we replace
32
- // the nearest one and start search again in the next loop step.
33
- const indices = new Array();
34
- for (const searchTerm of searchTerms) {
35
- const searchTermSlash = dotToSlash(searchTerm);
36
- indices.push({
37
- i: content.indexOf(searchTerm, start),
38
- replacement: references.get(searchTerm),
39
- searchTerm
40
- }, {
41
- i: content.indexOf(searchTermSlash, start),
42
- replacement: dotToSlash(references.get(searchTerm)),
43
- searchTerm: searchTermSlash
44
- });
34
+ // Build replacement lookup: pattern → replacement string (both dot and slash variants).
35
+ const replacementMap = new Map();
36
+ for (const term of searchTerms) {
37
+ const replacement = references.get(term);
38
+ replacementMap.set(term, replacement);
39
+ const slashTerm = dotToSlash(term);
40
+ if (slashTerm !== term) {
41
+ replacementMap.set(slashTerm, dotToSlash(replacement));
45
42
  }
46
- const found = indices.filter(({ i }) => i > -1);
47
- if (found.length === 0) {
48
- return content;
43
+ }
44
+ // Build deduplicated ignore set with slash variants
45
+ const ignoreSet = new Set();
46
+ for (const ignoredString of ignoreInStrings) {
47
+ if (ignoredString) {
48
+ ignoreSet.add(ignoredString);
49
+ ignoreSet.add(dotToSlash(ignoredString));
49
50
  }
50
- const inBetween = (intervals, i) => {
51
- for (const interval of intervals) {
52
- for (const { start, end } of interval) {
53
- if (i >= start && i <= end) {
54
- return { start, end };
55
- }
56
- }
51
+ }
52
+ // When a string appears in both replacementMap and ignoreSet, ignoreSet
53
+ // takes precedence — the match is skipped rather than replaced. Remove such
54
+ // keys from replacementMap so the intent is explicit instead of relying on
55
+ // runtime check order.
56
+ for (const ignored of ignoreSet) {
57
+ replacementMap.delete(ignored);
58
+ }
59
+ // Collect all unique patterns sorted by length descending (longest match first).
60
+ // V8 compiles alternation of literal strings into an Aho-Corasick automaton
61
+ // internally, giving us O(n + P + z) single-pass matching in native C++ code.
62
+ const allPatterns = new Set([...replacementMap.keys(), ...ignoreSet]);
63
+ const sorted = [...allPatterns].sort((a, b) => b.length - a.length);
64
+ // Reused across `replace` calls; `lastIndex` is reset at the start of each run.
65
+ const regex = new RegExp(sorted.map(escapeRegex).join("|"), "g");
66
+ return {
67
+ replace(content) {
68
+ if (!content) {
69
+ return content;
57
70
  }
58
- };
59
- // If we found two strings with the same index, we take the longest one
60
- // to replace, e.g.: app.variant and app.variant2 has the same index,
61
- // but we don't want to replace the first one with customer.app.variant,
62
- // otherwise we get customer.app.variant2, which we don't need. We need
63
- // to replace the whole app.variant2 with customer.app.variant.
64
- const findCurrentReplace = (found) => {
65
- const result = new Map();
66
- for (const entry of found) {
67
- const existing = result.get(entry.i);
68
- if (!existing || entry.searchTerm.length >= existing.searchTerm.length) {
69
- result.set(entry.i, entry);
71
+ regex.lastIndex = 0;
72
+ // Single-pass: find all matches, skip ignored ones, batch-replace the rest
73
+ const parts = [];
74
+ let lastEnd = 0;
75
+ let match;
76
+ while ((match = regex.exec(content)) !== null) {
77
+ const matched = match[0];
78
+ if (ignoreSet.has(matched)) {
79
+ // ignore takes priority over replacement
80
+ continue;
81
+ }
82
+ const replacement = replacementMap.get(matched);
83
+ if (replacement !== undefined) {
84
+ parts.push(content.substring(lastEnd, match.index));
85
+ parts.push(replacement);
86
+ lastEnd = match.index + matched.length;
70
87
  }
71
88
  }
72
- return [...result.values()].sort((a, b) => a.i - b.i)[0];
73
- };
74
- // Ignore if search is in i18n key: replace "id" in "{{id.key}}" with
75
- // "customer.id" and we need only the next one in string
76
- found.forEach(index => index.inBetween = inBetween(ignoredStrings, index.i));
77
- // There might be a situation when we found something in ignored
78
- // substrings and the index could be the nearest, but after that the
79
- // next index might be the actual find to replace, so we first of all
80
- // ignore all the ignored substring findings and get the next actual
81
- // replacement. But if there are only ignored substrings found, we take
82
- // the first one just to skip it and go further.
83
- const currentReplace = findCurrentReplace(found);
84
- if (currentReplace.inBetween) {
85
- start = currentReplace.inBetween.end;
89
+ // Nothing matched: return the original string without allocating a join.
90
+ if (lastEnd === 0) {
91
+ return content;
92
+ }
93
+ parts.push(content.substring(lastEnd));
94
+ return parts.join("");
86
95
  }
87
- else {
88
- content = content.substring(0, currentReplace.i)
89
- + currentReplace.replacement
90
- + content.substring(currentReplace.i + currentReplace.searchTerm.length);
91
- start = currentReplace.i + currentReplace.replacement.length;
96
+ };
97
+ }
98
+ export function renameMap(content, references, ignoreInStrings) {
99
+ return compileRenamer(references, ignoreInStrings).replace(content);
100
+ }
101
+ export function renameJson(json, references, ignoreInStrings) {
102
+ const renamer = compileRenamer(references, ignoreInStrings);
103
+ function processObject(obj) {
104
+ for (const key of Object.keys(obj)) {
105
+ const renamedKey = renamer.replace(key);
106
+ const value = obj[key];
107
+ if (renamedKey !== key) {
108
+ obj[renamedKey] = value;
109
+ delete obj[key];
110
+ }
111
+ processValue(obj, renamedKey);
112
+ }
113
+ }
114
+ function processValue(container, index) {
115
+ const target = container;
116
+ const value = target[index];
117
+ if (typeof value === "string") {
118
+ target[index] = renamer.replace(value);
119
+ }
120
+ else if (Array.isArray(value)) {
121
+ value.forEach((_, i) => processValue(value, i));
122
+ }
123
+ else if (value !== null && typeof value === "object") {
124
+ processObject(value);
92
125
  }
93
126
  }
127
+ processObject(json);
94
128
  }
95
129
  export function renameResources(files, search, replacement) {
96
130
  return new Map([...files].map(([filepath, content]) => [filepath, rename(content, search, replacement)]));
97
131
  }
98
- function findAllOccurrences(string, substring, start) {
99
- if (!substring) {
100
- return [];
101
- }
102
- const indices = [];
103
- let index = start;
104
- while ((index = string.indexOf(substring, index)) !== -1) {
105
- indices.push(index);
106
- index += substring.length; // shift from current finding
107
- }
108
- return indices;
109
- }
110
- ;
111
132
  //# sourceMappingURL=renamingUtil.js.map
@@ -1,14 +1,23 @@
1
+ /**
2
+ * Extensions of files whose contents are processed by the adaptation
3
+ * pipeline as text. Anything outside this whitelist is treated as binary
4
+ * (e.g. images, fonts) and is saved to the workspace verbatim by WorkspaceManager.
5
+ *
6
+ * Kept in sync with EXTENSIONS_TO_PROCESS in appVariant.ts.
7
+ */
8
+ export declare const TEXT_EXTENSIONS: ReadonlySet<string>;
1
9
  export default class ResourceUtil {
2
10
  static getRootFolder(projectNamespace?: string): string;
3
11
  static relativeToRoot(resourcePath: string, projectNamespace?: string): string;
4
12
  static getResourcePath(projectNamespace?: string, ...paths: string[]): string;
5
- static write(dir: string, files: Map<string, string>): Promise<void[]>;
6
- static writeInProject(dir: string, files: Map<string, string>): Promise<void[]>;
7
- static byGlob(dir: string, pattern: string, excludes?: string[]): Promise<Map<string, string>>;
8
- static byGlobInProject(pattern: string, excludes?: string[]): Promise<Map<string, string>>;
13
+ static write(dir: string, files: Map<string, Buffer>): Promise<void[]>;
14
+ static writeInProject(dir: string, files: Map<string, Buffer>): Promise<void[]>;
15
+ static byGlob(dir: string, pattern: string, excludes?: string[]): Promise<Map<string, Buffer>>;
16
+ static byGlobInProject(pattern: string, excludes?: string[]): Promise<Map<string, Buffer>>;
9
17
  static getString(resource: any): Promise<string>;
10
18
  static getJson(resource: any): Promise<any>;
11
19
  static setString(resource: any, str: string): void;
12
- static createResource(filename: string, projectNamespace: string, content: string): any;
13
- static toFileMap(resources: ReadonlyArray<Resource>, projectNamespace?: string): Promise<Map<string, string>>;
20
+ static createResource(filename: string, projectNamespace: string, content: string | Buffer): any;
21
+ private static createResourceFromContent;
22
+ static toFileMap(resources: ReadonlyArray<Resource>, projectNamespace?: string): Promise<Map<string, Buffer>>;
14
23
  }