apphx 2.0.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -8
- package/dist/cli.js +41 -11
- package/dist/cli.js.map +1 -1
- package/dist/config.js +4 -2
- package/dist/config.js.map +1 -1
- package/dist/integrations.js +154 -95
- package/dist/integrations.js.map +1 -1
- package/dist/notify.js +267 -0
- package/dist/notify.js.map +1 -0
- package/dist/runner.js +119 -20
- package/dist/runner.js.map +1 -1
- package/dist/schemas.js +57 -2
- package/dist/schemas.js.map +1 -1
- package/dist/vendor/obfuscator/dist/config.js +119 -6
- package/dist/vendor/obfuscator/dist/config.js.map +1 -1
- package/dist/vendor/obfuscator/dist/dfa.js +528 -0
- package/dist/vendor/obfuscator/dist/dfa.js.map +1 -0
- package/dist/vendor/obfuscator/dist/flutter.js +3266 -0
- package/dist/vendor/obfuscator/dist/flutter.js.map +1 -0
- package/dist/vendor/obfuscator/dist/image-utils.js +335 -0
- package/dist/vendor/obfuscator/dist/image-utils.js.map +1 -0
- package/dist/vendor/obfuscator/dist/junk.js +1501 -0
- package/dist/vendor/obfuscator/dist/junk.js.map +1 -0
- package/dist/vendor/obfuscator/dist/lexicon-data.js +972 -0
- package/dist/vendor/obfuscator/dist/lexicon-data.js.map +1 -0
- package/dist/vendor/obfuscator/dist/obfuscate.js +51 -2
- package/dist/vendor/obfuscator/dist/obfuscate.js.map +1 -1
- package/dist/vendor/obfuscator/dist/objc-symbols.js +1819 -0
- package/dist/vendor/obfuscator/dist/objc-symbols.js.map +1 -0
- package/dist/vendor/obfuscator/dist/project-identity.js +852 -0
- package/dist/vendor/obfuscator/dist/project-identity.js.map +1 -0
- package/dist/vendor/obfuscator/dist/string-encrypt.js +330 -0
- package/dist/vendor/obfuscator/dist/string-encrypt.js.map +1 -0
- package/examples/android-java.factory.json +40 -5
- package/examples/expo.factory.json +68 -5
- package/examples/flutter.factory.json +40 -4
- package/examples/luggage.json +1 -0
- package/examples/multi-role-agents.md +129 -0
- package/examples/mygo.factory.json +54 -4
- package/examples/queue-loop-demo.md +146 -0
- package/examples/react-native.factory.json +58 -5
- package/examples/roles.example.json +31 -0
- package/examples/swift.factory.json +60 -5
- package/examples/uni-app.factory.json +58 -5
- package/package.json +1 -1
- package/skills/apphx/SKILL.md +39 -4
- package/skills/apphx/resources/requirement.json +1 -0
- package/skills/apphx-obfuscator/LICENSE +21 -0
- package/skills/apphx-obfuscator/SKILL.md +72 -0
|
@@ -0,0 +1,852 @@
|
|
|
1
|
+
import { classifyLanguage, deriveHash, isImageFile, isTextFile, seededInt, seededRandom } from "./common.js";
|
|
2
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { basename, dirname, join, relative, sep } from "node:path";
|
|
4
|
+
// src/project-identity.ts
|
|
5
|
+
/** Same list the original uses to refuse renaming directories. */
|
|
6
|
+
const IGNORE_FOLDERS = [
|
|
7
|
+
".xcassets",
|
|
8
|
+
"Tests",
|
|
9
|
+
"UITests",
|
|
10
|
+
"Products",
|
|
11
|
+
"Pods",
|
|
12
|
+
"Frameworks",
|
|
13
|
+
".xcworkspace",
|
|
14
|
+
".xcodeproj",
|
|
15
|
+
".lproj",
|
|
16
|
+
".git",
|
|
17
|
+
".DS_Store",
|
|
18
|
+
".idea",
|
|
19
|
+
"Icon\r"
|
|
20
|
+
];
|
|
21
|
+
const XIB_KEY_WORDS = ["initialViewController", "sceneID"];
|
|
22
|
+
const XIB_PASS_KEY_WORDS = [
|
|
23
|
+
"id",
|
|
24
|
+
"destination",
|
|
25
|
+
"firstItem",
|
|
26
|
+
"secondItem"
|
|
27
|
+
];
|
|
28
|
+
/** ISO language codes (keys), plus the literal 'zh'. */
|
|
29
|
+
const LANGUAGE_CODES = [
|
|
30
|
+
"af",
|
|
31
|
+
"sq",
|
|
32
|
+
"ar",
|
|
33
|
+
"be",
|
|
34
|
+
"bg",
|
|
35
|
+
"ca",
|
|
36
|
+
"zh-CN",
|
|
37
|
+
"zh-TW",
|
|
38
|
+
"hr",
|
|
39
|
+
"cs",
|
|
40
|
+
"da",
|
|
41
|
+
"nl",
|
|
42
|
+
"en",
|
|
43
|
+
"eo",
|
|
44
|
+
"et",
|
|
45
|
+
"tl",
|
|
46
|
+
"fi",
|
|
47
|
+
"fr",
|
|
48
|
+
"gl",
|
|
49
|
+
"de",
|
|
50
|
+
"el",
|
|
51
|
+
"iw",
|
|
52
|
+
"hi",
|
|
53
|
+
"hu",
|
|
54
|
+
"is",
|
|
55
|
+
"id",
|
|
56
|
+
"ga",
|
|
57
|
+
"it",
|
|
58
|
+
"ja",
|
|
59
|
+
"ko",
|
|
60
|
+
"la",
|
|
61
|
+
"lv",
|
|
62
|
+
"lt",
|
|
63
|
+
"mk",
|
|
64
|
+
"ms",
|
|
65
|
+
"mt",
|
|
66
|
+
"no",
|
|
67
|
+
"fa",
|
|
68
|
+
"pl",
|
|
69
|
+
"pt",
|
|
70
|
+
"ro",
|
|
71
|
+
"ru",
|
|
72
|
+
"sr",
|
|
73
|
+
"sk",
|
|
74
|
+
"sl",
|
|
75
|
+
"es",
|
|
76
|
+
"sw",
|
|
77
|
+
"sv",
|
|
78
|
+
"th",
|
|
79
|
+
"tr",
|
|
80
|
+
"uk",
|
|
81
|
+
"vi",
|
|
82
|
+
"cy",
|
|
83
|
+
"yi",
|
|
84
|
+
"zh"
|
|
85
|
+
];
|
|
86
|
+
/**
|
|
87
|
+
* Small deterministic lexicon stand-in. Only the shape matters (a clean
|
|
88
|
+
* lowercase word), not the exact entry.
|
|
89
|
+
*/
|
|
90
|
+
const LEXICON = [
|
|
91
|
+
"amber",
|
|
92
|
+
"badger",
|
|
93
|
+
"cobalt",
|
|
94
|
+
"dune",
|
|
95
|
+
"ember",
|
|
96
|
+
"falcon",
|
|
97
|
+
"granite",
|
|
98
|
+
"harbor",
|
|
99
|
+
"indigo",
|
|
100
|
+
"juniper",
|
|
101
|
+
"kestrel",
|
|
102
|
+
"lumen",
|
|
103
|
+
"mica",
|
|
104
|
+
"nimbus",
|
|
105
|
+
"onyx",
|
|
106
|
+
"prairie",
|
|
107
|
+
"quartz",
|
|
108
|
+
"russet",
|
|
109
|
+
"sable",
|
|
110
|
+
"tundra",
|
|
111
|
+
"umber",
|
|
112
|
+
"valley",
|
|
113
|
+
"willow",
|
|
114
|
+
"xenon",
|
|
115
|
+
"yarrow",
|
|
116
|
+
"zephyr",
|
|
117
|
+
"acorn",
|
|
118
|
+
"birch",
|
|
119
|
+
"cedar",
|
|
120
|
+
"delta",
|
|
121
|
+
"eagle",
|
|
122
|
+
"frost",
|
|
123
|
+
"grove",
|
|
124
|
+
"hollow",
|
|
125
|
+
"iron",
|
|
126
|
+
"jade",
|
|
127
|
+
"kelp",
|
|
128
|
+
"larch",
|
|
129
|
+
"meadow",
|
|
130
|
+
"north",
|
|
131
|
+
"oak",
|
|
132
|
+
"pine",
|
|
133
|
+
"quill",
|
|
134
|
+
"ridge",
|
|
135
|
+
"stone",
|
|
136
|
+
"thicket",
|
|
137
|
+
"violet",
|
|
138
|
+
"wheat"
|
|
139
|
+
];
|
|
140
|
+
function isContainChinese(value) {
|
|
141
|
+
return /[一-鿿]/.test(value);
|
|
142
|
+
}
|
|
143
|
+
function isNumberString(value) {
|
|
144
|
+
return /[0-9]+/.test(value);
|
|
145
|
+
}
|
|
146
|
+
function isDigitString(value) {
|
|
147
|
+
return value.length > 0 && /^[0-9]+$/.test(value);
|
|
148
|
+
}
|
|
149
|
+
/** Python str.title() for a single alphanumeric fragment. */
|
|
150
|
+
function titleCase(value) {
|
|
151
|
+
return value.length ? value[0].toUpperCase() + value.slice(1).toLowerCase() : value;
|
|
152
|
+
}
|
|
153
|
+
/** Python str.capitalize() for a single alphanumeric fragment. */
|
|
154
|
+
function capitalize(value) {
|
|
155
|
+
return value.length ? value[0].toUpperCase() + value.slice(1) : value;
|
|
156
|
+
}
|
|
157
|
+
function isLowerAscii(value) {
|
|
158
|
+
return /^[a-z]$/.test(value);
|
|
159
|
+
}
|
|
160
|
+
function randomChars(seed, salt, alphabet, length) {
|
|
161
|
+
const random = seededRandom(deriveHash(seed, salt));
|
|
162
|
+
let out = "";
|
|
163
|
+
for (let index = 0; index < length; index += 1) out += alphabet[Math.floor(random() * alphabet.length)];
|
|
164
|
+
return out;
|
|
165
|
+
}
|
|
166
|
+
const ALNUM = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
167
|
+
const LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
168
|
+
function randomStringWithNumber(seed, salt, length) {
|
|
169
|
+
return randomChars(seed, salt, ALNUM, length);
|
|
170
|
+
}
|
|
171
|
+
function randomStringNoNumber(seed, salt, length) {
|
|
172
|
+
return randomChars(seed, salt, LETTERS, length);
|
|
173
|
+
}
|
|
174
|
+
function randomWord(seed, salt) {
|
|
175
|
+
return LEXICON[seededInt(seed, salt, 0, LEXICON.length - 1)];
|
|
176
|
+
}
|
|
177
|
+
function randomWordTitle(seed, salt) {
|
|
178
|
+
return capitalize(randomWord(seed, salt));
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Folder rename: strip a "(...)" suffix, replace dots with
|
|
182
|
+
* underscores, remove digits, split camelCase on uppercase boundaries and keep
|
|
183
|
+
* only the last meaningful fragment, then prepend a seed-derived title word.
|
|
184
|
+
*/
|
|
185
|
+
function getMissFolderName(folderName, seed) {
|
|
186
|
+
if (folderName.includes("(")) folderName = folderName.slice(0, folderName.indexOf("("));
|
|
187
|
+
if (folderName.includes(".")) folderName = folderName.replace(/\./g, "_");
|
|
188
|
+
if (isContainChinese(folderName)) return capitalize(folderName.replace(/[^\u4e00-\u9fa5a-zA-Z0-9]/g, "")) || randomWordTitle(seed, "folder:chinese");
|
|
189
|
+
for (let num = 0; num < 10; num += 1) folderName = folderName.split(String(num)).join("");
|
|
190
|
+
let folderNameList = folderName.replace(/[A-Z]/g, (match) => `_${match}`).split("_").filter((item) => item.length > 1);
|
|
191
|
+
if (folderNameList.length > 2) folderNameList = folderNameList.slice(-1);
|
|
192
|
+
folderNameList = [randomWordTitle(seed, `folder:${folderName}`), ...folderNameList];
|
|
193
|
+
return folderNameList.join("");
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Parse the PBXGroup section of a project.pbxproj file into
|
|
197
|
+
* `childrenNames -> row`.
|
|
198
|
+
*/
|
|
199
|
+
function parsePbxGroups(content, seed, folderPrefix) {
|
|
200
|
+
const map = /* @__PURE__ */ new Map();
|
|
201
|
+
const lines = content.split("\n");
|
|
202
|
+
let inGroup = false;
|
|
203
|
+
let row = "";
|
|
204
|
+
for (const line of lines) {
|
|
205
|
+
if (line.includes("/* Begin PBXGroup section */")) {
|
|
206
|
+
inGroup = true;
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (line.includes("/* End PBXGroup section */")) {
|
|
210
|
+
inGroup = false;
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if (inGroup && !line.includes("/* Begin PBXGroup section */")) {
|
|
214
|
+
row += line;
|
|
215
|
+
if (line.includes("};")) {
|
|
216
|
+
const parsed = parsePbxGroupOneRow(row, seed, folderPrefix);
|
|
217
|
+
if (parsed) map.set(parsed.childrenNames, parsed);
|
|
218
|
+
row = "";
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return map;
|
|
223
|
+
}
|
|
224
|
+
function parsePbxGroupOneRow(row, seed, folderPrefix) {
|
|
225
|
+
let udid = "";
|
|
226
|
+
let name = "";
|
|
227
|
+
let isa = "";
|
|
228
|
+
let children = "";
|
|
229
|
+
let childrenNames = "";
|
|
230
|
+
let path = "";
|
|
231
|
+
let sourceTree = "";
|
|
232
|
+
const flat = row.replace(/\n/g, "").split(/\s+/).join("");
|
|
233
|
+
const udidName = flat.slice(0, flat.indexOf("="));
|
|
234
|
+
if (udidName.includes("/*") && udidName.includes("*/")) {
|
|
235
|
+
udid = udidName.slice(0, udidName.indexOf("/*"));
|
|
236
|
+
name = udidName.slice(udidName.indexOf("/*") + 2, udidName.indexOf("*/"));
|
|
237
|
+
} else udid = udidName;
|
|
238
|
+
if (name.length === 0) return void 0;
|
|
239
|
+
const body = flat.slice(flat.indexOf("={") + 2, flat.indexOf("};"));
|
|
240
|
+
for (const value of body.split(";")) {
|
|
241
|
+
if (value.includes("isa=")) {
|
|
242
|
+
isa = value.split("isa=").pop();
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
if (value.includes("children=")) {
|
|
246
|
+
children = value.split("children=").pop().slice(1, -2);
|
|
247
|
+
const names = [];
|
|
248
|
+
for (const item of children.split(",")) if (item.includes("/*") && item.includes("*/")) names.push(item.slice(item.indexOf("/*") + 2, item.indexOf("*/")));
|
|
249
|
+
childrenNames = names.join(",");
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
if (value.includes("path=")) {
|
|
253
|
+
path = value.split("path=").pop();
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (value.includes("sourceTree=")) {
|
|
257
|
+
sourceTree = value.split("sourceTree=").pop();
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
const missName = folderPrefix + getMissFolderName(name, seed);
|
|
262
|
+
return {
|
|
263
|
+
udid,
|
|
264
|
+
name,
|
|
265
|
+
missName,
|
|
266
|
+
isa,
|
|
267
|
+
children,
|
|
268
|
+
childrenNames,
|
|
269
|
+
path,
|
|
270
|
+
sourceTree
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Image-name generation, with `random.sample`/`random.choice` and
|
|
275
|
+
* `random_word_from_lexicon` replaced by a single seed-derived stream.
|
|
276
|
+
*/
|
|
277
|
+
function createMissImageName(imageName, seed) {
|
|
278
|
+
const random = seededRandom(deriveHash(seed, "image", imageName));
|
|
279
|
+
const word = () => LEXICON[Math.floor(random() * LEXICON.length)];
|
|
280
|
+
const choice = (items) => items[Math.floor(random() * items.length)];
|
|
281
|
+
const sampleTwo = (items) => {
|
|
282
|
+
const copy = items.slice();
|
|
283
|
+
for (let index = 0; index < 2 && index < copy.length - 1; index += 1) {
|
|
284
|
+
const target = index + Math.floor(random() * (copy.length - index));
|
|
285
|
+
[copy[index], copy[target]] = [copy[target], copy[index]];
|
|
286
|
+
}
|
|
287
|
+
return copy.slice(0, 2);
|
|
288
|
+
};
|
|
289
|
+
let result = imageName.replace(/-/g, "_");
|
|
290
|
+
let isLine = false;
|
|
291
|
+
if (isContainChinese(result)) {
|
|
292
|
+
const chinese = result.match(/[一-鿿]+/g) ?? [];
|
|
293
|
+
for (const item of chinese) result = result.split(item).join(word());
|
|
294
|
+
}
|
|
295
|
+
if (/\d/.test(result)) result = result.replace(/\d/g, "");
|
|
296
|
+
if (result.includes("_")) {
|
|
297
|
+
isLine = true;
|
|
298
|
+
if (result.endsWith("_")) result = result.slice(0, -1);
|
|
299
|
+
const parts = result.split("_");
|
|
300
|
+
result = "";
|
|
301
|
+
for (const item of parts) if (!isNumberString(item)) result += titleCase(item);
|
|
302
|
+
}
|
|
303
|
+
if (result.includes(" ")) {
|
|
304
|
+
if (result.endsWith(" ")) result = result.slice(0, -1);
|
|
305
|
+
const parts = result.split(" ");
|
|
306
|
+
result = "";
|
|
307
|
+
for (const item of parts) if (!isNumberString(item)) result += item;
|
|
308
|
+
}
|
|
309
|
+
let splitSource = result.replace(/[A-Z]/g, (match) => `==${match}`);
|
|
310
|
+
if (splitSource.startsWith("==")) splitSource = splitSource.slice(2);
|
|
311
|
+
let nameList = splitSource.split("==");
|
|
312
|
+
if (nameList.length >= 2) nameList = sampleTwo(nameList);
|
|
313
|
+
else {
|
|
314
|
+
const imageCode = choice([
|
|
315
|
+
"icon",
|
|
316
|
+
"img",
|
|
317
|
+
"image",
|
|
318
|
+
"pic",
|
|
319
|
+
"bg",
|
|
320
|
+
"name",
|
|
321
|
+
"back",
|
|
322
|
+
"add",
|
|
323
|
+
"left",
|
|
324
|
+
"right",
|
|
325
|
+
word()
|
|
326
|
+
]);
|
|
327
|
+
nameList.splice(-1, 0, imageCode);
|
|
328
|
+
}
|
|
329
|
+
const last = nameList.length - 1;
|
|
330
|
+
if (!isLine) {
|
|
331
|
+
nameList[last] = titleCase(nameList[last]);
|
|
332
|
+
nameList[last - 1] = titleCase(nameList[last - 1]);
|
|
333
|
+
} else {
|
|
334
|
+
nameList[last] = nameList[last].toLowerCase();
|
|
335
|
+
nameList[last - 1] = nameList[last - 1].toLowerCase();
|
|
336
|
+
}
|
|
337
|
+
nameList.splice(0, 0, word());
|
|
338
|
+
return isLine ? nameList.join("_") : nameList.join("");
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* New image-name generation. Preserves count/language-suffixed variants
|
|
342
|
+
* (counted variants are left unchanged).
|
|
343
|
+
*/
|
|
344
|
+
function getNewImageName(imageName, imageNamePrefix, seed, algorithm = 0) {
|
|
345
|
+
if (!imageName) return imageName;
|
|
346
|
+
const imageNameList = imageName.split("_");
|
|
347
|
+
if (imageNameList.length >= 2) {
|
|
348
|
+
const last = imageNameList.at(-1);
|
|
349
|
+
if (isDigitString(last)) return imageName;
|
|
350
|
+
if (!isDigitString(last) && LANGUAGE_CODES.includes(last)) return imageName;
|
|
351
|
+
}
|
|
352
|
+
let missImageName;
|
|
353
|
+
if (imageNamePrefix && imageNamePrefix.length > 0 && imageNamePrefix.endsWith("#")) return `${imageNamePrefix.split("#")[0]}${imageName}`;
|
|
354
|
+
if (algorithm === 0) missImageName = createMissImageName(imageName, seed);
|
|
355
|
+
else if (algorithm === 3) missImageName = randomStringNoNumber(seed, `img-len:${imageName}`, imageName.length);
|
|
356
|
+
else missImageName = createMissImageName(imageName, seed);
|
|
357
|
+
let prefix = imageNamePrefix ?? "";
|
|
358
|
+
if (prefix.length > 0 && imageName.includes("_")) {
|
|
359
|
+
if (!prefix.endsWith("_")) return `${prefix}_${missImageName}`;
|
|
360
|
+
return `${prefix}${missImageName}`;
|
|
361
|
+
}
|
|
362
|
+
let firstCharacter = missImageName.slice(0, 1);
|
|
363
|
+
const surplus = missImageName.slice(1);
|
|
364
|
+
firstCharacter = isLowerAscii(imageName.slice(0, 1)) ? firstCharacter.toLowerCase() : capitalize(firstCharacter);
|
|
365
|
+
return `${prefix}${firstCharacter}${surplus}`;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Xib id/destination rename: three hyphen-separated segments,
|
|
369
|
+
* no Chinese, no colon, replaced with same-length alphanumeric segments.
|
|
370
|
+
*/
|
|
371
|
+
function getIdDestinationMissName(name, seed) {
|
|
372
|
+
const parts = name.split("-");
|
|
373
|
+
if (parts.length === 2 && name.startsWith("-")) return null;
|
|
374
|
+
if (!name.includes("-")) return null;
|
|
375
|
+
if (parts.length !== 3) return null;
|
|
376
|
+
if (isContainChinese(name)) return null;
|
|
377
|
+
if (name.includes(":")) return null;
|
|
378
|
+
return parts.map((item, index) => randomStringWithNumber(seed, `xib:${name}:${index}`, item.length)).join("-");
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Xib/storyboard id+destination map. Returns the attribute map
|
|
382
|
+
* and the (possibly extended) keyword list used to discover custom attributes.
|
|
383
|
+
*/
|
|
384
|
+
function getXibStoryboardMap(line, keywords, passKeywords) {
|
|
385
|
+
let newLine = line.trim();
|
|
386
|
+
if (line.includes("com.apple.InterfaceBuilder")) return null;
|
|
387
|
+
if (newLine.startsWith("<") && !newLine.startsWith("</") && newLine.endsWith(">")) newLine = newLine.slice(newLine.indexOf(" ") + 1, newLine.indexOf(">"));
|
|
388
|
+
else return null;
|
|
389
|
+
const nextKeywords = keywords.slice();
|
|
390
|
+
const map = /* @__PURE__ */ new Map();
|
|
391
|
+
for (let item of newLine.split(" ")) {
|
|
392
|
+
if (item.endsWith("/")) item = item.slice(0, -1);
|
|
393
|
+
item = item.replace(/"/g, "");
|
|
394
|
+
const pair = item.split("=");
|
|
395
|
+
if (pair.length === 2 && pair[1].includes("-") && pair[1].split("-").length === 3 && !nextKeywords.includes(pair[0])) {
|
|
396
|
+
if (passKeywords.length !== 0 && !passKeywords.includes(pair[0])) nextKeywords.push(pair[0]);
|
|
397
|
+
}
|
|
398
|
+
if (item.includes("=") && nextKeywords.includes(item.split("=")[0])) {
|
|
399
|
+
const kv = item.split("=");
|
|
400
|
+
map.set(kv[0], kv[1]);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return {
|
|
404
|
+
map,
|
|
405
|
+
keywords: nextKeywords
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Xib/storyboard line transform. Pure per-line transform; the caller
|
|
410
|
+
* threads the `missMap` (xs_miss_map_list) and keyword list between lines.
|
|
411
|
+
*/
|
|
412
|
+
function replaceXibStoryboardLine(line, keywords, passKeywords, missMap, seed) {
|
|
413
|
+
const parsed = getXibStoryboardMap(line, keywords, passKeywords);
|
|
414
|
+
if (!parsed) return {
|
|
415
|
+
line,
|
|
416
|
+
keywords: keywords.slice(),
|
|
417
|
+
missMap
|
|
418
|
+
};
|
|
419
|
+
let missLine = line;
|
|
420
|
+
for (const item of parsed.keywords) {
|
|
421
|
+
const value = parsed.map.get(item);
|
|
422
|
+
if (value === void 0) continue;
|
|
423
|
+
let missValue = missMap.get(value);
|
|
424
|
+
if (missValue === void 0) {
|
|
425
|
+
const generated = getIdDestinationMissName(value, seed);
|
|
426
|
+
if (generated !== null) missMap.set(value, generated);
|
|
427
|
+
missValue = missMap.get(value);
|
|
428
|
+
}
|
|
429
|
+
if (missValue !== void 0) missLine = missLine.split(value).join(missValue);
|
|
430
|
+
}
|
|
431
|
+
return {
|
|
432
|
+
line: missLine,
|
|
433
|
+
keywords: parsed.keywords,
|
|
434
|
+
missMap
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
/** Extract keyword matches from a .strings line. */
|
|
438
|
+
function getStringKeywords(line) {
|
|
439
|
+
const matches = [...line.matchAll(/"([^"]*-[^"]*)"/g)].map((match) => match[1]);
|
|
440
|
+
if (matches.length === 0) return null;
|
|
441
|
+
return matches.map((match) => match.split(".")[0]);
|
|
442
|
+
}
|
|
443
|
+
/** Replace keywords in a .strings line. */
|
|
444
|
+
function replaceStringKeywords(line, missMap) {
|
|
445
|
+
const keywords = getStringKeywords(line);
|
|
446
|
+
if (!keywords || keywords.length === 0) return line;
|
|
447
|
+
let out = line;
|
|
448
|
+
for (const keyword of keywords) {
|
|
449
|
+
const value = missMap.get(keyword);
|
|
450
|
+
if (value !== void 0) out = out.split(keyword).join(value);
|
|
451
|
+
}
|
|
452
|
+
return out;
|
|
453
|
+
}
|
|
454
|
+
/** True when `propertyName` is not preceded by an identifier character. */
|
|
455
|
+
function judgeFrontSpecialCharacter(line, propertyName) {
|
|
456
|
+
const allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
|
|
457
|
+
if (line.includes(propertyName)) {
|
|
458
|
+
const index = line.indexOf(propertyName);
|
|
459
|
+
const previous = line.slice(index - 1, index);
|
|
460
|
+
if (previous.length > 0 && allowed.includes(previous)) return false;
|
|
461
|
+
}
|
|
462
|
+
return true;
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* project.pbxproj project-name rewrite, as a per-line pure transform.
|
|
466
|
+
*/
|
|
467
|
+
function replaceProjectNameInLine(line, original, replacement, oldBridge, newBridge) {
|
|
468
|
+
const skipHeader = line.includes(".h") && !line.includes("Pods-");
|
|
469
|
+
const skipSource = line.includes(".m") && !line.includes("Tests.m") && !line.includes("Pods-");
|
|
470
|
+
const skipPod = line.includes("pod '");
|
|
471
|
+
if (!skipHeader && !skipSource && !skipPod && judgeFrontSpecialCharacter(line, original)) line = line.split(original).join(replacement);
|
|
472
|
+
if (line.includes(oldBridge)) line = line.split(oldBridge).join(newBridge);
|
|
473
|
+
if (line.includes("SWIFT_OBJC_BRIDGING_HEADER") && line.includes(`${original}/`)) line = line.split(`${original}/`).join(`${replacement}/`);
|
|
474
|
+
return line;
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Image-reference line rewrite. `imageType` is sourceTypeMap[imageName]
|
|
478
|
+
* (e.g. ".png", "@2x.png", or a comma list); pass undefined for plain references.
|
|
479
|
+
*/
|
|
480
|
+
function replaceImageNameLine(line, imageName, newImageName, filePath, imageType) {
|
|
481
|
+
let prevWord = "";
|
|
482
|
+
if (filePath.endsWith(".xcodeproj/project.pbxproj")) prevWord = "/* ";
|
|
483
|
+
if (filePath.endsWith(".swift")) {
|
|
484
|
+
if (line.includes(`"${imageName}"`)) return line.split(`"${imageName}"`).join(`"${newImageName}"`);
|
|
485
|
+
if (line.includes(`"${imageName}.`)) return line.split(`"${imageName}.`).join(`"${newImageName}.`);
|
|
486
|
+
return line;
|
|
487
|
+
}
|
|
488
|
+
if (filePath.endsWith(".cpp")) {
|
|
489
|
+
if (line.includes(`"${imageName}"`)) return line.split(imageName).join(newImageName);
|
|
490
|
+
if (line.includes(`/${imageName}.`)) return line.split(imageName).join(newImageName);
|
|
491
|
+
if (line.includes(`"${imageName}.`)) return line.split(imageName).join(newImageName);
|
|
492
|
+
return line;
|
|
493
|
+
}
|
|
494
|
+
if (imageType !== void 0 && !line.includes("imageNamed:@\"") && !line.includes("imageFromBundle:@\"")) {
|
|
495
|
+
if (imageType.includes(",")) {
|
|
496
|
+
for (const type of imageType.split(",")) {
|
|
497
|
+
if (line.includes(`${imageName}${type}`)) {
|
|
498
|
+
line = line.split(`${prevWord}${imageName}${type}`).join(`${prevWord}${newImageName}${type}`);
|
|
499
|
+
line = line.split(`"${imageName}${type}`).join(`"${newImageName}${type}`);
|
|
500
|
+
line = line.split(`path = ${imageName}${type}`).join(`path = ${newImageName}${type}`);
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
if (line.includes(`"${imageName}.`)) {
|
|
504
|
+
line = line.split(`"${imageName}.`).join(`"${newImageName}.`);
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
if (line.includes(`"${imageName}"`)) {
|
|
508
|
+
line = line.split(`"${imageName}"`).join(`"${newImageName}"`);
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
if (line.includes(`"${imageName}@2x"`)) {
|
|
512
|
+
line = line.split(`"${imageName}@2x"`).join(`"${newImageName}@2x"`);
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
515
|
+
if (line.includes(`"${imageName}@3x"`)) line = line.split(`"${imageName}@3x"`).join(`"${newImageName}@3x"`);
|
|
516
|
+
}
|
|
517
|
+
return line;
|
|
518
|
+
}
|
|
519
|
+
if (line.includes(`${imageName}${imageType}`)) {
|
|
520
|
+
line = line.split(`${prevWord}${imageName}${imageType}`).join(`${prevWord}${newImageName}${imageType}`);
|
|
521
|
+
line = line.split(`"${imageName}${imageType}`).join(`"${newImageName}${imageType}`);
|
|
522
|
+
line = line.split(`path = ${imageName}${imageType}`).join(`path = ${newImageName}${imageType}`);
|
|
523
|
+
return line;
|
|
524
|
+
}
|
|
525
|
+
if (line.includes(`"${imageName}"`)) return line.split(`"${imageName}"`).join(`"${newImageName}"`);
|
|
526
|
+
if (line.includes(`"${imageName}@2x"`)) return line.split(`"${imageName}@2x"`).join(`"${newImageName}@2x"`);
|
|
527
|
+
if (line.includes(`"${imageName}@3x"`)) return line.split(`"${imageName}@3x"`).join(`"${newImageName}@3x"`);
|
|
528
|
+
return line;
|
|
529
|
+
}
|
|
530
|
+
if (line.includes("imageNamed:@\"") && line.includes(imageName)) return line.split(`imageNamed:@"${imageName}`).join(`imageNamed:@"${newImageName}`);
|
|
531
|
+
if (line.includes("imageFromBundle:@\"") && line.includes(imageName)) return line.split(`imageFromBundle:@"${imageName}`).join(`imageFromBundle:@"${newImageName}`);
|
|
532
|
+
if (line.includes(`"${imageName}"`)) return line.split(`"${imageName}"`).join(`"${newImageName}"`);
|
|
533
|
+
if (line.includes(`"${imageName}@2x"`)) return line.split(`"${imageName}@2x"`).join(`"${newImageName}@2x"`);
|
|
534
|
+
if (line.includes(`"${imageName}@3x"`)) return line.split(`"${imageName}@3x"`).join(`"${newImageName}@3x"`);
|
|
535
|
+
if (line.includes(`/* ${imageName}@`)) return line.split(`/* ${imageName}@`).join(`/* ${newImageName}@`);
|
|
536
|
+
return line;
|
|
537
|
+
}
|
|
538
|
+
/** Resolve a child's renamed folder from its parent name. */
|
|
539
|
+
function getFatherMissName(pbxGroups, fatherName, childName) {
|
|
540
|
+
for (const partMap of pbxGroups.values()) if (fatherName === partMap.name && partMap.children.includes(childName)) return partMap.missName;
|
|
541
|
+
return "";
|
|
542
|
+
}
|
|
543
|
+
function isReadableText(relativePathValue) {
|
|
544
|
+
return isTextFile(relativePathValue) || classifyLanguage(relativePathValue) !== void 0;
|
|
545
|
+
}
|
|
546
|
+
function commonRoot(files) {
|
|
547
|
+
if (files.length === 0) return process.cwd();
|
|
548
|
+
const parts = files.map((entry) => entry.absolute.split(sep));
|
|
549
|
+
let common = parts[0].slice();
|
|
550
|
+
for (const next of parts.slice(1)) {
|
|
551
|
+
let index = 0;
|
|
552
|
+
while (index < common.length && index < next.length && common[index] === next[index]) index += 1;
|
|
553
|
+
common = common.slice(0, index);
|
|
554
|
+
}
|
|
555
|
+
return common.join(sep) || sep;
|
|
556
|
+
}
|
|
557
|
+
/** True when a directory must not be renamed. */
|
|
558
|
+
function ignoreFolderAbs(absolute) {
|
|
559
|
+
return IGNORE_FOLDERS.some((item) => absolute.endsWith(item) || absolute.includes(item));
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Path/child map: walk the real directory tree once, recording
|
|
563
|
+
* every child under its parent's "last two components" key and collecting the
|
|
564
|
+
* directories that are eligible for renaming (i.e. not ignored).
|
|
565
|
+
*/
|
|
566
|
+
function buildPathChildMap(root) {
|
|
567
|
+
const pathChildMap = /* @__PURE__ */ new Map();
|
|
568
|
+
const renamableDirs = [];
|
|
569
|
+
const add = (keyPath, child) => {
|
|
570
|
+
if (!pathChildMap.has(keyPath)) pathChildMap.set(keyPath, /* @__PURE__ */ new Set());
|
|
571
|
+
pathChildMap.get(keyPath).add(child);
|
|
572
|
+
};
|
|
573
|
+
const visit = (directory) => {
|
|
574
|
+
for (const name of readdirSync(directory).sort()) {
|
|
575
|
+
const absolute = join(directory, name);
|
|
576
|
+
const childName = name;
|
|
577
|
+
const parent = dirname(absolute);
|
|
578
|
+
const keyPath = `${basename(dirname(parent))}/${basename(parent)}`;
|
|
579
|
+
if (!childName.includes(".DS_Store") && !childName.includes("Icon\r") && !childName.includes(".git")) add(keyPath, childName);
|
|
580
|
+
if (!lstatSync(absolute).isDirectory()) continue;
|
|
581
|
+
if (!ignoreFolderAbs(absolute)) {
|
|
582
|
+
renamableDirs.push(absolute);
|
|
583
|
+
visit(absolute);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
visit(root);
|
|
588
|
+
return {
|
|
589
|
+
pathChildMap,
|
|
590
|
+
renamableDirs
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
/** Project-name folder rename skips these subtrees. */
|
|
594
|
+
function skipProjectRenamePath(absolute) {
|
|
595
|
+
if (absolute.includes("/.git")) return true;
|
|
596
|
+
if (absolute.includes(".json")) return true;
|
|
597
|
+
if (absolute.includes("/.DS_Store")) return true;
|
|
598
|
+
if (absolute.includes("/Pods") && !absolute.includes("/Pods-")) return true;
|
|
599
|
+
return false;
|
|
600
|
+
}
|
|
601
|
+
function setEquals(left, right) {
|
|
602
|
+
const rightSet = new Set(right);
|
|
603
|
+
return left.size === rightSet.size && [...left].every((item) => rightSet.has(item));
|
|
604
|
+
}
|
|
605
|
+
/** Resolve a directory's renamed name from the path map. */
|
|
606
|
+
function getThisPathMissName(absolute, replacePathMap) {
|
|
607
|
+
const thisPathName = basename(absolute);
|
|
608
|
+
const fatherPathName = basename(dirname(absolute));
|
|
609
|
+
for (const [keyName, row] of replacePathMap) if (keyName.includes(`&&${thisPathName}`) && (fatherPathName === row.fatherName || fatherPathName === row.fatherMissName)) return row.missName;
|
|
610
|
+
}
|
|
611
|
+
/** Remap a file path; its final component (the file name) is never remapped. */
|
|
612
|
+
function remapAbsolute(absolute, dirRenames) {
|
|
613
|
+
const parts = absolute.split(sep);
|
|
614
|
+
const out = [];
|
|
615
|
+
let originalPrefix = "";
|
|
616
|
+
for (let index = 0; index < parts.length; index += 1) {
|
|
617
|
+
if (index === 0) {
|
|
618
|
+
out.push("");
|
|
619
|
+
continue;
|
|
620
|
+
}
|
|
621
|
+
const originalAbs = originalPrefix === "" ? `${sep}${parts[index]}` : `${originalPrefix}${sep}${parts[index]}`;
|
|
622
|
+
if (index === parts.length - 1) {
|
|
623
|
+
out.push(parts[index]);
|
|
624
|
+
break;
|
|
625
|
+
}
|
|
626
|
+
const target = dirRenames.get(originalAbs);
|
|
627
|
+
out.push(target ? basename(target) : parts[index]);
|
|
628
|
+
originalPrefix = originalAbs;
|
|
629
|
+
}
|
|
630
|
+
return out.join(sep);
|
|
631
|
+
}
|
|
632
|
+
/** Remap a directory path, including its final component. */
|
|
633
|
+
function remapDirectoryAbsolute(absolute, dirRenames) {
|
|
634
|
+
const parts = absolute.split(sep);
|
|
635
|
+
const out = [];
|
|
636
|
+
let originalPrefix = "";
|
|
637
|
+
for (let index = 0; index < parts.length; index += 1) {
|
|
638
|
+
if (index === 0) {
|
|
639
|
+
out.push("");
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
642
|
+
const originalAbs = originalPrefix === "" ? `${sep}${parts[index]}` : `${originalPrefix}${sep}${parts[index]}`;
|
|
643
|
+
const target = dirRenames.get(originalAbs);
|
|
644
|
+
out.push(target ? basename(target) : parts[index]);
|
|
645
|
+
originalPrefix = originalAbs;
|
|
646
|
+
}
|
|
647
|
+
return out.join(sep);
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Batch entry point: applies the full project-identity rename pipeline to an
|
|
651
|
+
* already-walked project. Directory and image-file renames are applied on disk;
|
|
652
|
+
* text references are rewritten in place. The same seed yields identical output.
|
|
653
|
+
*/
|
|
654
|
+
function renameProjectIdentity(files, options, seed) {
|
|
655
|
+
const changedPaths = [];
|
|
656
|
+
if (files.length === 0) return {
|
|
657
|
+
changed: 0,
|
|
658
|
+
changedPaths
|
|
659
|
+
};
|
|
660
|
+
const root = commonRoot(files);
|
|
661
|
+
const folderPrefix = options.folderPrefix ?? "";
|
|
662
|
+
const imagePrefix = options.imagePrefix ?? "";
|
|
663
|
+
const imageAlgorithm = options.imageAlgorithm ?? 0;
|
|
664
|
+
const originalFiles = files.slice();
|
|
665
|
+
const content = /* @__PURE__ */ new Map();
|
|
666
|
+
for (const entry of files) if (isReadableText(entry.relative) && !isImageFile(entry.relative)) content.set(entry.absolute, readFileSync(entry.absolute, "utf8"));
|
|
667
|
+
const { pathChildMap, renamableDirs } = buildPathChildMap(root);
|
|
668
|
+
const dirRenames = /* @__PURE__ */ new Map();
|
|
669
|
+
const fileRenames = /* @__PURE__ */ new Map();
|
|
670
|
+
const pbxEntry = files.find((entry) => entry.relative.endsWith("/project.pbxproj") && !entry.relative.includes("/Pods/"));
|
|
671
|
+
const originalProjectName = (pbxEntry?.relative.match(/(?:^|\/)([^/]+)\.xcodeproj\/project\.pbxproj$/))?.[1];
|
|
672
|
+
if (originalProjectName && pbxEntry) {
|
|
673
|
+
const replacement = options.projectName ?? capitalize(deriveHash(seed, "project-name", originalProjectName).slice(0, 10));
|
|
674
|
+
const oldBridge = `${originalProjectName}-Bridging-Header.h`;
|
|
675
|
+
const newBridge = `${replacement}-Bridging-Header.h`;
|
|
676
|
+
const projectDirs = /* @__PURE__ */ new Set();
|
|
677
|
+
for (const entry of files) {
|
|
678
|
+
let directory = dirname(entry.absolute);
|
|
679
|
+
while (directory.startsWith(root) && directory !== root) {
|
|
680
|
+
projectDirs.add(directory);
|
|
681
|
+
const parent = dirname(directory);
|
|
682
|
+
if (parent === directory) break;
|
|
683
|
+
directory = parent;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
for (const directory of [...projectDirs].sort((a, b) => b.length - a.length)) {
|
|
687
|
+
if (skipProjectRenamePath(directory)) continue;
|
|
688
|
+
const name = basename(directory);
|
|
689
|
+
if (name.includes(originalProjectName)) dirRenames.set(directory, join(dirname(directory), name.split(originalProjectName).join(replacement)));
|
|
690
|
+
}
|
|
691
|
+
for (const entry of files) {
|
|
692
|
+
if (skipProjectRenamePath(entry.absolute)) continue;
|
|
693
|
+
const name = basename(entry.absolute);
|
|
694
|
+
if (name.includes(originalProjectName) && !name.endsWith(".h") && !name.endsWith(".m")) fileRenames.set(entry.absolute, join(dirname(entry.absolute), name.split(originalProjectName).join(replacement)));
|
|
695
|
+
else if (name === oldBridge) fileRenames.set(entry.absolute, join(dirname(entry.absolute), newBridge));
|
|
696
|
+
}
|
|
697
|
+
const pbxContent = content.get(pbxEntry.absolute) ?? readFileSync(pbxEntry.absolute, "utf8");
|
|
698
|
+
content.set(pbxEntry.absolute, pbxContent.split("\n").map((line) => replaceProjectNameInLine(line, originalProjectName, replacement, oldBridge, newBridge)).join("\n"));
|
|
699
|
+
}
|
|
700
|
+
const pbxPath = pbxEntry?.absolute;
|
|
701
|
+
const missPbxprojMap = /* @__PURE__ */ new Map();
|
|
702
|
+
if (pbxPath && content.has(pbxPath)) {
|
|
703
|
+
const pbxGroups = parsePbxGroups(content.get(pbxPath), seed, folderPrefix);
|
|
704
|
+
const replacePathMap = /* @__PURE__ */ new Map();
|
|
705
|
+
for (const [keyPath, childList] of pathChildMap) for (const [childrenNames, partMap] of pbxGroups) {
|
|
706
|
+
if (!setEquals(childList, childrenNames.split(","))) continue;
|
|
707
|
+
const name = partMap.name;
|
|
708
|
+
const fatherName = keyPath.split("/")[0] ?? "";
|
|
709
|
+
const keyName = `${fatherName}&&${name}`;
|
|
710
|
+
const fatherMissName = getFatherMissName(pbxGroups, fatherName, name);
|
|
711
|
+
replacePathMap.set(keyName, {
|
|
712
|
+
name,
|
|
713
|
+
missName: partMap.missName,
|
|
714
|
+
fatherName,
|
|
715
|
+
fatherMissName
|
|
716
|
+
});
|
|
717
|
+
missPbxprojMap.set(partMap.udid, [name, partMap.missName]);
|
|
718
|
+
}
|
|
719
|
+
for (const directory of renamableDirs) {
|
|
720
|
+
const missName = getThisPathMissName(directory, replacePathMap);
|
|
721
|
+
if (missName !== void 0) dirRenames.set(directory, join(dirname(directory), missName));
|
|
722
|
+
}
|
|
723
|
+
content.set(pbxPath, rewritePbxprojForFolders(content.get(pbxPath), missPbxprojMap));
|
|
724
|
+
}
|
|
725
|
+
const sourceMap = /* @__PURE__ */ new Map();
|
|
726
|
+
const sourceTypeMap = /* @__PURE__ */ new Map();
|
|
727
|
+
for (const entry of originalFiles) {
|
|
728
|
+
if (!entry.relative.includes(".imageset/")) continue;
|
|
729
|
+
if (!isImageFile(entry.relative)) continue;
|
|
730
|
+
const parts = entry.absolute.split(sep);
|
|
731
|
+
const imagesetIndex = parts.findIndex((part) => part.endsWith(".imageset"));
|
|
732
|
+
if (imagesetIndex === -1) continue;
|
|
733
|
+
const imagesetAbs = parts.slice(0, imagesetIndex + 1).join(sep);
|
|
734
|
+
const fileName = parts.at(-1);
|
|
735
|
+
let stem;
|
|
736
|
+
let type;
|
|
737
|
+
if (fileName.includes("@")) {
|
|
738
|
+
stem = fileName.split("@")[0];
|
|
739
|
+
type = `@${fileName.split("@").at(-1)}`;
|
|
740
|
+
} else {
|
|
741
|
+
stem = fileName.split(".")[0];
|
|
742
|
+
type = `.${fileName.split(".").at(-1)}`;
|
|
743
|
+
}
|
|
744
|
+
if (!sourceMap.has(stem)) sourceMap.set(stem, getNewImageName(stem, imagePrefix, seed, imageAlgorithm));
|
|
745
|
+
const newStem = sourceMap.get(stem);
|
|
746
|
+
const existingType = sourceTypeMap.get(stem);
|
|
747
|
+
sourceTypeMap.set(stem, existingType && existingType !== type ? `${existingType},${type}` : type);
|
|
748
|
+
const nextName = fileName.includes("@") ? `${newStem}@${fileName.split("@").at(-1)}` : `${newStem}.${fileName.split(".").at(-1)}`;
|
|
749
|
+
fileRenames.set(entry.absolute, join(dirname(entry.absolute), nextName));
|
|
750
|
+
const contentsAbs = join(imagesetAbs, "Contents.json");
|
|
751
|
+
const contentsBefore = content.get(contentsAbs) ?? (existsSync(contentsAbs) ? readFileSync(contentsAbs, "utf8") : "");
|
|
752
|
+
if (contentsBefore) {
|
|
753
|
+
let contentsAfter = contentsBefore;
|
|
754
|
+
for (const [oldName, newName] of sourceMap) contentsAfter = contentsAfter.split(`"${oldName}.`).join(`"${newName}.`);
|
|
755
|
+
content.set(contentsAbs, contentsAfter);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
for (const [stem, newStem] of sourceMap) for (const entry of originalFiles) {
|
|
759
|
+
if (!entry.relative.includes(".imageset/")) continue;
|
|
760
|
+
const parts = entry.absolute.split(sep);
|
|
761
|
+
const imagesetIndex = parts.findIndex((part) => part.endsWith(".imageset"));
|
|
762
|
+
if (imagesetIndex === -1) continue;
|
|
763
|
+
const imagesetAbs = parts.slice(0, imagesetIndex + 1).join(sep);
|
|
764
|
+
if (basename(imagesetAbs) === `${stem}.imageset`) dirRenames.set(imagesetAbs, join(dirname(imagesetAbs), `${newStem}.imageset`));
|
|
765
|
+
}
|
|
766
|
+
for (const entry of originalFiles) {
|
|
767
|
+
if (!isReadableText(entry.relative) || isImageFile(entry.relative)) continue;
|
|
768
|
+
const before = content.get(entry.absolute);
|
|
769
|
+
if (before === void 0) continue;
|
|
770
|
+
let after = before;
|
|
771
|
+
for (const [imageName, newImageName] of sourceMap) after = after.split("\n").map((line) => replaceImageNameLine(line, imageName, newImageName, entry.relative, sourceTypeMap.get(imageName))).join("\n");
|
|
772
|
+
content.set(entry.absolute, after);
|
|
773
|
+
}
|
|
774
|
+
const missMap = /* @__PURE__ */ new Map();
|
|
775
|
+
let keywords = XIB_KEY_WORDS.slice();
|
|
776
|
+
for (const entry of originalFiles) {
|
|
777
|
+
if (!entry.relative.endsWith(".xib") && !entry.relative.endsWith(".storyboard")) continue;
|
|
778
|
+
const after = (content.get(entry.absolute) ?? readFileSync(entry.absolute, "utf8")).split("\n").map((line) => {
|
|
779
|
+
const result = replaceXibStoryboardLine(line, keywords, XIB_PASS_KEY_WORDS, missMap, seed);
|
|
780
|
+
keywords = result.keywords;
|
|
781
|
+
return result.line;
|
|
782
|
+
}).join("\n");
|
|
783
|
+
content.set(entry.absolute, after);
|
|
784
|
+
}
|
|
785
|
+
for (const entry of originalFiles) {
|
|
786
|
+
if (!entry.relative.endsWith(".strings")) continue;
|
|
787
|
+
const before = content.get(entry.absolute) ?? readFileSync(entry.absolute, "utf8");
|
|
788
|
+
content.set(entry.absolute, replaceStringKeywords(before, missMap));
|
|
789
|
+
}
|
|
790
|
+
const orderedDirRenames = [...dirRenames.entries()].sort((a, b) => b[0].split(sep).length - a[0].split(sep).length);
|
|
791
|
+
for (const [from, to] of orderedDirRenames) {
|
|
792
|
+
if (from === to || !existsSync(from) || existsSync(to)) continue;
|
|
793
|
+
renameSync(from, to);
|
|
794
|
+
changedPaths.push(to);
|
|
795
|
+
}
|
|
796
|
+
for (const [from, to] of fileRenames) {
|
|
797
|
+
const finalFrom = remapAbsolute(from, dirRenames);
|
|
798
|
+
const finalTo = join(remapDirectoryAbsolute(dirname(from), dirRenames), basename(to));
|
|
799
|
+
if (finalFrom === finalTo || !existsSync(finalFrom) || existsSync(finalTo)) continue;
|
|
800
|
+
mkdirSync(dirname(finalTo), { recursive: true });
|
|
801
|
+
renameSync(finalFrom, finalTo);
|
|
802
|
+
changedPaths.push(finalTo);
|
|
803
|
+
}
|
|
804
|
+
for (const entry of originalFiles) {
|
|
805
|
+
const rewritten = content.get(entry.absolute);
|
|
806
|
+
if (rewritten === void 0) continue;
|
|
807
|
+
const finalAbs = remapAbsolute(entry.absolute, dirRenames);
|
|
808
|
+
if ((existsSync(finalAbs) ? readFileSync(finalAbs, "utf8") : "") !== rewritten) {
|
|
809
|
+
mkdirSync(dirname(finalAbs), { recursive: true });
|
|
810
|
+
writeFileSync(finalAbs, rewritten);
|
|
811
|
+
changedPaths.push(finalAbs);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
return {
|
|
815
|
+
changed: changedPaths.length,
|
|
816
|
+
changedPaths: [...new Set(changedPaths)].map((absolute) => relative(root, absolute).split(sep).join("/"))
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* PBX folder rename, reduced to the
|
|
821
|
+
* deterministic core: PBXGroup comment/path renames and relay of `path =` lines.
|
|
822
|
+
* Header-path rewriting (pch/plist/bridging) is folded into the caller only when
|
|
823
|
+
* those paths are part of the folder rename; here the relay covers every group.
|
|
824
|
+
*/
|
|
825
|
+
function rewritePbxprojForFolders(content, missMap) {
|
|
826
|
+
let relay = null;
|
|
827
|
+
const out = [];
|
|
828
|
+
for (const line of content.split("\n")) {
|
|
829
|
+
if (line.includes("/*") && line.includes("*/")) {
|
|
830
|
+
const lineUdid = line.slice(0, line.indexOf("/*")).split(/\s+/).join("");
|
|
831
|
+
const lineName = line.slice(line.indexOf("/*") + 2, line.indexOf("*/")).split(/\s+/).join("");
|
|
832
|
+
let next = line;
|
|
833
|
+
if (missMap.has(lineUdid)) {
|
|
834
|
+
const pair = missMap.get(lineUdid);
|
|
835
|
+
next = line.split(lineName).join(pair[1]);
|
|
836
|
+
if (next.includes("*/ = {")) relay = pair;
|
|
837
|
+
}
|
|
838
|
+
out.push(next);
|
|
839
|
+
continue;
|
|
840
|
+
}
|
|
841
|
+
if (line.includes("path =") && relay !== null) {
|
|
842
|
+
out.push(line.split(relay[0]).join(relay[1]));
|
|
843
|
+
continue;
|
|
844
|
+
}
|
|
845
|
+
if (line.includes("};")) relay = null;
|
|
846
|
+
out.push(line);
|
|
847
|
+
}
|
|
848
|
+
return out.join("\n");
|
|
849
|
+
}
|
|
850
|
+
export { IGNORE_FOLDERS, LANGUAGE_CODES, XIB_KEY_WORDS, XIB_PASS_KEY_WORDS, createMissImageName, getIdDestinationMissName, getMissFolderName, getNewImageName, getStringKeywords, getXibStoryboardMap, parsePbxGroups, renameProjectIdentity, replaceImageNameLine, replaceProjectNameInLine, replaceStringKeywords, replaceXibStoryboardLine };
|
|
851
|
+
|
|
852
|
+
//# sourceMappingURL=project-identity.js.map
|