apphx 1.0.0 → 2.0.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.
Files changed (126) hide show
  1. package/README.md +52 -138
  2. package/benchmarks/peer-context-worker.mjs +186 -0
  3. package/benchmarks/peer-context.mjs +150 -0
  4. package/dist/apple-types.js.map +1 -1
  5. package/dist/cli.js +373 -121
  6. package/dist/cli.js.map +1 -1
  7. package/dist/config.js +42 -4
  8. package/dist/config.js.map +1 -1
  9. package/dist/dependencies.js +455 -16
  10. package/dist/dependencies.js.map +1 -1
  11. package/dist/export-options.js +44 -2
  12. package/dist/export-options.js.map +1 -1
  13. package/dist/flutter.js +13 -25
  14. package/dist/flutter.js.map +1 -1
  15. package/dist/go-dependencies.js +389 -0
  16. package/dist/go-dependencies.js.map +1 -0
  17. package/dist/inbox.js +7 -8
  18. package/dist/inbox.js.map +1 -1
  19. package/dist/integrations.js +29 -29
  20. package/dist/integrations.js.map +1 -1
  21. package/dist/isolation.js +190 -0
  22. package/dist/isolation.js.map +1 -0
  23. package/dist/macos.js +57 -0
  24. package/dist/macos.js.map +1 -0
  25. package/dist/mygo-build.js +55 -0
  26. package/dist/mygo-build.js.map +1 -0
  27. package/dist/policy.js +35 -0
  28. package/dist/policy.js.map +1 -0
  29. package/dist/preflight.js +1 -1
  30. package/dist/process.js +3 -3
  31. package/dist/project-profiles.js +163 -0
  32. package/dist/project-profiles.js.map +1 -0
  33. package/dist/proof.js +33 -0
  34. package/dist/proof.js.map +1 -0
  35. package/dist/remote.js +1 -1
  36. package/dist/runner.js +269 -287
  37. package/dist/runner.js.map +1 -1
  38. package/dist/scheduler.js +0 -3
  39. package/dist/scheduler.js.map +1 -1
  40. package/dist/schemas.js +156 -44
  41. package/dist/schemas.js.map +1 -1
  42. package/dist/store.js +166 -44
  43. package/dist/store.js.map +1 -1
  44. package/dist/vendor/obfuscator/dist/cli.js +72 -0
  45. package/dist/vendor/obfuscator/dist/cli.js.map +1 -0
  46. package/dist/vendor/obfuscator/dist/code.js +585 -0
  47. package/dist/vendor/obfuscator/dist/code.js.map +1 -0
  48. package/dist/vendor/obfuscator/dist/common.js +262 -0
  49. package/dist/vendor/obfuscator/dist/common.js.map +1 -0
  50. package/dist/vendor/obfuscator/dist/config.js +123 -0
  51. package/dist/vendor/obfuscator/dist/config.js.map +1 -0
  52. package/dist/vendor/obfuscator/dist/content.js +89 -0
  53. package/dist/vendor/obfuscator/dist/content.js.map +1 -0
  54. package/dist/vendor/obfuscator/dist/obfuscate.js +93 -0
  55. package/dist/vendor/obfuscator/dist/obfuscate.js.map +1 -0
  56. package/dist/vendor/obfuscator/dist/png.js +283 -0
  57. package/dist/vendor/obfuscator/dist/png.js.map +1 -0
  58. package/dist/vendor/obfuscator/dist/project.js +159 -0
  59. package/dist/vendor/obfuscator/dist/project.js.map +1 -0
  60. package/dist/vendor/obfuscator/dist/resources.js +335 -0
  61. package/dist/vendor/obfuscator/dist/resources.js.map +1 -0
  62. package/dist/vendor/obfuscator/package.json +22 -0
  63. package/dist/workspace.js +113 -0
  64. package/dist/workspace.js.map +1 -0
  65. package/evals/control.mjs +73 -0
  66. package/evals/expectations.mjs +37 -0
  67. package/evals/host.mjs +51 -0
  68. package/evals/oracle.mjs +80 -0
  69. package/evals/policy.mjs +268 -0
  70. package/examples/android-java.factory.json +18 -0
  71. package/examples/android-java.json +16 -0
  72. package/examples/existing-project.factory.json +18 -0
  73. package/examples/existing-project.json +17 -0
  74. package/examples/expo.eas.json +17 -0
  75. package/examples/expo.factory.json +18 -0
  76. package/examples/expo.json +16 -0
  77. package/examples/factory.json +0 -2
  78. package/examples/flutter.factory.json +13 -0
  79. package/examples/flutter.json +16 -0
  80. package/examples/luggage.json +5 -3
  81. package/examples/mygo-web.factory.json +18 -0
  82. package/examples/mygo-web.json +16 -0
  83. package/examples/mygo.factory.json +18 -0
  84. package/examples/mygo.json +16 -0
  85. package/examples/react-native.factory.json +18 -0
  86. package/examples/react-native.json +16 -0
  87. package/examples/remote.json +1 -1
  88. package/examples/runtime/android-java/app/build.gradle +7 -0
  89. package/examples/runtime/android-java/app/src/main/AndroidManifest.xml +8 -0
  90. package/examples/runtime/android-java/app/src/main/java/dev/apphx/runtime/MainActivity.java +44 -0
  91. package/examples/runtime/android-java/sdk-build.mjs +29 -0
  92. package/examples/runtime/android-java/settings.gradle +2 -0
  93. package/examples/runtime/android-ui.mjs +70 -0
  94. package/examples/runtime/mygo-native/go.mod +13 -0
  95. package/examples/runtime/mygo-native/go.sum +10 -0
  96. package/examples/runtime/mygo-native/main.go +173 -0
  97. package/examples/runtime/mygo-native/main_test.go +14 -0
  98. package/examples/runtime/mygo-native/mygo.json +1 -0
  99. package/examples/runtime/mygo-web/bun.lock +15 -0
  100. package/examples/runtime/mygo-web/go.mod +9 -0
  101. package/examples/runtime/mygo-web/go.sum +8 -0
  102. package/examples/runtime/mygo-web/index.html +1 -0
  103. package/examples/runtime/mygo-web/main.go +60 -0
  104. package/examples/runtime/mygo-web/mygo.config.ts +10 -0
  105. package/examples/runtime/mygo-web/package.json +1 -0
  106. package/examples/runtime/mygo-web/src/main.ts +23 -0
  107. package/examples/runtime/mygo-web/src/mygo.ts +14 -0
  108. package/examples/swift.factory.json +18 -0
  109. package/examples/swift.json +16 -0
  110. package/examples/uni-app.factory.json +18 -0
  111. package/examples/uni-app.json +16 -0
  112. package/package.json +12 -5
  113. package/scripts/bundle-obfuscator.mjs +19 -0
  114. package/scripts/runtime-smoke.mjs +209 -0
  115. package/skills/apphx/SKILL.md +180 -0
  116. package/skills/apphx/resources/google-play-research-result.json +6 -0
  117. package/skills/apphx/resources/local-research-result.json +6 -0
  118. package/skills/{apple-app-factory → apphx}/resources/requirement.json +5 -8
  119. package/skills/apphx/resources/research-result.json +12 -0
  120. package/skills/apphx/resources/review-result.json +7 -0
  121. package/dist/apple.js +0 -900
  122. package/dist/apple.js.map +0 -1
  123. package/skills/apple-app-factory/SKILL.md +0 -103
  124. package/skills/apple-app-factory/resources/research-result.json +0 -21
  125. package/skills/apple-app-factory/resources/review-result.json +0 -7
  126. /package/skills/{apple-app-factory → apphx}/LICENSE +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code.js","names":[],"sources":["../src/code.ts"],"sourcesContent":["import type { Language } from './common.js';\nimport { deriveHash, deriveToken, replaceWholeWords, seededInt } from './common.js';\nimport type { CodeOptions } from './config.js';\n\n/**\n * Deterministic in-place code obfuscation primitives. Every exported transform is pure:\n * identical content + language + options + seed produce byte-identical output, and no\n * wall-clock time, randomness or locale state is ever consulted.\n */\n\ninterface MaskedParts {\n text: string;\n isPlaceholder: boolean;\n placeholderIndex: number;\n placeholderKind: 'string' | 'comment';\n}\n\ninterface Masked {\n masked: string;\n /** Original text of every masked string literal, in placeholder order. */\n strings: string[];\n /** Kind of every masked string: 'single', 'double' or 'other'. */\n stringKinds: Array<'single' | 'double' | 'other'>;\n /** Original text of every masked comment, in placeholder order. */\n comments: string[];\n /** Whether each masked comment is a `#!` shebang line. */\n commentKinds: boolean[];\n stringSep: string;\n commentSep: string;\n}\n\n/** Control characters are illegal inside real identifiers/expressions, so they cannot collide with code. */\nconst SEPARATOR_CANDIDATES = ['\\u0002', '\\u0003', '\\u0004', '\\u0005', '\\u0006', '\\u0007', '\\u0008', '\\u000e', '\\u000f'];\n\n/** Pick two placeholder characters that do not occur anywhere in the content. */\nfunction pickSeparators(content: string): { stringSep: string; commentSep: string } {\n let first: string | undefined;\n let second: string | undefined;\n for (const candidate of SEPARATOR_CANDIDATES) {\n if (content.includes(candidate)) continue;\n if (first === undefined) { first = candidate; continue; }\n second = candidate;\n break;\n }\n if (first === undefined || second === undefined) {\n // Source text containing every candidate control char is pathological; fall back to\n // private-use characters, which the same collision logic cannot enter through placeholders.\n return { stringSep: '\\ue000', commentSep: '\\ue001' };\n }\n return { stringSep: first, commentSep: second };\n}\n\n/** Index of the first '\\n' or '\\r' at or after `start` (content length when absent). */\nfunction lineEnd(content: string, start: number): number {\n const nextLf = content.indexOf('\\n', start);\n const nextCr = content.indexOf('\\r', start);\n if (nextLf === -1) return nextCr === -1 ? content.length : nextCr;\n if (nextCr === -1) return nextLf;\n return Math.min(nextLf, nextCr);\n}\n\n/** Scan a quoted literal body. Raw literals have no escapes; single-line literals end at a newline. */\nfunction scanQuoted(content: string, start: number, quote: string, isRaw: boolean, multiline: boolean): number {\n let index = start;\n while (index < content.length) {\n const char = content[index]!;\n if (!multiline && (char === '\\n' || char === '\\r')) return index;\n if (!isRaw && char === '\\\\') { index += 2; continue; }\n if (char === quote) return index + 1;\n index += 1;\n }\n return content.length;\n}\n\n/** Scan a triple-quoted literal body: closes only at the full delimiter, never at a lone quote. */\nfunction scanTripleQuoted(content: string, start: number, quote: string, isRaw: boolean): number {\n const delimiter = quote.repeat(3);\n let index = start;\n while (index < content.length) {\n if (!isRaw && content[index] === '\\\\') { index += 2; continue; }\n if (content.startsWith(delimiter, index)) return index + 3;\n index += 1;\n }\n return content.length;\n}\n\n/**\n * Mask every string literal and comment with inert placeholders, left to right, so later\n * replacement passes can never touch literal/comment text. The scanner recognizes, per language:\n * - comments: `//` to end of line, `/* ... *\\/` blocks (non-nesting);\n * - strings: 'single' and \"double\" everywhere; JavaScript backtick templates with `${}`;\n * Dart raw `r'...'`/`r\"...\"` and triple-quoted `'''...'''`/`\"\"\"...\"\"\"` (with `$` interpolation);\n * Kotlin `\"\"\"...\"\"\"`; Swift raw `#\"...\"#` (and deeper `##`/`###` nesting).\n * A backslash skips the next character inside a normal string; a newline before the closing\n * quote ends a single-line string. The `#!` shebang on line 1 of javascript files is treated\n * as a comment so transforms leave it alone.\n */\nfunction maskContent(content: string, language: Language): Masked {\n const { stringSep, commentSep } = pickSeparators(content);\n const parts: MaskedParts[] = [];\n const strings: string[] = [];\n const stringKinds: Array<'single' | 'double' | 'other'> = [];\n const comments: string[] = [];\n const commentKinds: boolean[] = [];\n const javascript = language === 'javascript';\n\n let index = 0;\n while (index < content.length) {\n // Shebang: line 1 of javascript files.\n if (javascript && index === 0 && content.startsWith('#!')) {\n const end = lineEnd(content, index);\n parts.push({ text: content.slice(index, end), isPlaceholder: true, placeholderIndex: comments.length, placeholderKind: 'comment' });\n comments.push(content.slice(index, end));\n commentKinds.push(true);\n index = end;\n continue;\n }\n // Line comment.\n if (content.startsWith('//', index)) {\n const end = lineEnd(content, index);\n parts.push({ text: content.slice(index, end), isPlaceholder: true, placeholderIndex: comments.length, placeholderKind: 'comment' });\n comments.push(content.slice(index, end));\n commentKinds.push(false);\n index = end;\n continue;\n }\n // Block comment (non-nesting).\n if (content.startsWith('/*', index)) {\n const close = content.indexOf('*/', index + 2);\n const end = close === -1 ? content.length : close + 2;\n parts.push({ text: content.slice(index, end), isPlaceholder: true, placeholderIndex: comments.length, placeholderKind: 'comment' });\n comments.push(content.slice(index, end));\n commentKinds.push(false);\n index = end;\n continue;\n }\n\n // Dart raw strings.\n if (language === 'dart' && (content.startsWith(\"r'''\", index) || content.startsWith('r\"\"\"', index))) {\n const quote = content[index + 1]!;\n const end = scanTripleQuoted(content, index + 4, quote, true);\n const text = content.slice(index, end);\n parts.push({ text, isPlaceholder: true, placeholderIndex: strings.length, placeholderKind: 'string' });\n strings.push(text);\n stringKinds.push('other');\n index = end;\n continue;\n }\n if (language === 'dart' && (content.startsWith(\"r'\", index) || content.startsWith('r\"', index))) {\n const quote = content[index + 1]!;\n const end = scanQuoted(content, index + 2, quote, true, false);\n const text = content.slice(index, end);\n parts.push({ text, isPlaceholder: true, placeholderIndex: strings.length, placeholderKind: 'string' });\n strings.push(text);\n stringKinds.push('other');\n index = end;\n continue;\n }\n // Dart and Kotlin triple-quoted strings.\n if ((language === 'dart' || language === 'kotlin') && content.startsWith('\"\"\"', index)) {\n const end = scanTripleQuoted(content, index + 3, '\"', false);\n const text = content.slice(index, end);\n parts.push({ text, isPlaceholder: true, placeholderIndex: strings.length, placeholderKind: 'string' });\n strings.push(text);\n stringKinds.push('other');\n index = end;\n continue;\n }\n if (language === 'dart' && content.startsWith(\"'''\", index)) {\n const end = scanTripleQuoted(content, index + 3, \"'\", false);\n const text = content.slice(index, end);\n parts.push({ text, isPlaceholder: true, placeholderIndex: strings.length, placeholderKind: 'string' });\n strings.push(text);\n stringKinds.push('other');\n index = end;\n continue;\n }\n // JavaScript backtick templates (whole template, including ${...}).\n if (javascript && content[index] === '`') {\n const end = scanQuoted(content, index + 1, '`', false, true);\n const text = content.slice(index, end);\n parts.push({ text, isPlaceholder: true, placeholderIndex: strings.length, placeholderKind: 'string' });\n strings.push(text);\n stringKinds.push('other');\n index = end;\n continue;\n }\n // Swift raw strings: #\"...\"# with deeper # nesting.\n if (language === 'swift' && content[index] === '#') {\n let hashEnd = index;\n while (content[hashEnd] === '#') hashEnd += 1;\n const quote = content[hashEnd];\n if (quote === '\"') {\n let cursor = hashEnd + 1;\n let end = -1;\n while (cursor < content.length) {\n const close = content.indexOf('\"', cursor);\n if (close === -1) { cursor = content.length; break; }\n let closeEnd = close + 1;\n while (content[closeEnd] === '#') closeEnd += 1;\n if (closeEnd - close - 1 === hashEnd - index) { end = closeEnd; break; }\n cursor = close + 1;\n }\n if (end === -1) end = content.length;\n const text = content.slice(index, end);\n parts.push({ text, isPlaceholder: true, placeholderIndex: strings.length, placeholderKind: 'string' });\n strings.push(text);\n stringKinds.push('other');\n index = end;\n continue;\n }\n }\n // Ordinary quoted strings (every language).\n const char = content[index];\n if (char === \"'\" || char === '\"') {\n const end = scanQuoted(content, index + 1, char, false, false);\n const text = content.slice(index, end);\n parts.push({ text, isPlaceholder: true, placeholderIndex: strings.length, placeholderKind: 'string' });\n strings.push(text);\n stringKinds.push(char === \"'\" ? 'single' : 'double');\n index = end;\n continue;\n }\n\n // Plain code run: scan ahead to the next character that can open a comment or string form.\n const end = indexOfAnyFrom(content, index + 1, [\"'\", '\"', '/', '`', '#', 'r']);\n parts.push({ text: content.slice(index, end), isPlaceholder: false, placeholderIndex: -1, placeholderKind: 'string' });\n index = end;\n }\n\n const masked = parts\n .map((part) => (part.isPlaceholder\n ? part.placeholderKind === 'string'\n ? `${stringSep}${part.placeholderIndex}${stringSep}`\n : `${commentSep}${part.placeholderIndex}${commentSep}`\n : part.text))\n .join('');\n return { masked, strings, stringKinds, comments, commentKinds, stringSep, commentSep };\n}\n\n/** First index of any search character at or after `start` (content length when absent). */\nfunction indexOfAnyFrom(content: string, start: number, chars: string[]): number {\n let best = content.length;\n for (const char of chars) {\n const found = content.indexOf(char, start);\n if (found !== -1 && found < best) best = found;\n }\n return best;\n}\n\n/** Split masked text into alternating code and placeholder parts. */\nfunction splitMasked(masked: Masked): MaskedParts[] {\n const separatorPattern = new RegExp(\n `${escapeRegExpChar(masked.stringSep)}(\\\\d+)${escapeRegExpChar(masked.stringSep)}|${escapeRegExpChar(masked.commentSep)}(\\\\d+)${escapeRegExpChar(masked.commentSep)}`,\n 'g',\n );\n const parts: MaskedParts[] = [];\n let last = 0;\n for (const match of masked.masked.matchAll(separatorPattern)) {\n if (match.index! > last) {\n parts.push({ text: masked.masked.slice(last, match.index!), isPlaceholder: false, placeholderIndex: -1, placeholderKind: 'string' });\n }\n const commentGroup = match[2];\n const isComment = commentGroup !== undefined;\n parts.push({\n text: match[0],\n isPlaceholder: true,\n placeholderIndex: Number(isComment ? commentGroup : match[1]),\n placeholderKind: isComment ? 'comment' : 'string',\n });\n last = match.index! + match[0].length;\n }\n if (last < masked.masked.length) {\n parts.push({ text: masked.masked.slice(last), isPlaceholder: false, placeholderIndex: -1, placeholderKind: 'string' });\n }\n return parts;\n}\n\n/** Escape a single separator character for interpolation into a RegExp source. */\nfunction escapeRegExpChar(char: string): string {\n return /[.*+?^${}()|[\\]\\\\]/.test(char) ? `\\\\${char}` : char;\n}\n\n/** Restore placeholders from `rebuiltMasked`, substituting `rebuildString` output for each string. */\nfunction unmask(rebuiltMasked: string, masked: Masked, rebuildString: (index: number) => string): string {\n const stringPattern = new RegExp(`${escapeRegExpChar(masked.stringSep)}(\\\\d+)${escapeRegExpChar(masked.stringSep)}`, 'g');\n return rebuiltMasked.replace(stringPattern, (_whole, digits: string) => rebuildString(Number(digits)));\n}\n\n/**\n * Remove comments while keeping every newline they contained, so line numbering stays stable.\n * The `#!` shebang on line 1 of javascript files is preserved verbatim.\n */\nexport function stripComments(content: string, language: Language): string {\n const masked = maskContent(content, language);\n const parts = splitMasked(masked);\n const rebuilt = parts\n .map((part) => {\n if (!part.isPlaceholder) return part.text;\n if (part.placeholderKind === 'string') return `${masked.stringSep}${part.placeholderIndex}${masked.stringSep}`;\n const original = masked.comments[part.placeholderIndex]!;\n if (masked.commentKinds[part.placeholderIndex]) return original;\n return original.replace(/[^\\r\\n]/g, '');\n })\n .join('');\n return unmask(rebuilt, masked, (index) => masked.strings[index]!);\n}\n\n/** Whether a quoted literal is eligible for runtime-decode encryption. */\nfunction isEligible(content: string, quote: string, excludes: readonly string[]): boolean {\n const body = content.slice(1, -1);\n if (body.length === 0) return false;\n if (body.includes('%') || body.includes('$')) return false;\n if (body.startsWith('./') || body.startsWith('../') || /^[A-Za-z][A-Za-z0-9+.-]*:\\/\\//.test(body)) return false;\n for (const exclude of excludes) {\n if (new RegExp(exclude).test(body)) return false;\n }\n let index = 0;\n while (index < body.length) {\n if (body[index] === '\\\\') {\n const next = body[index + 1];\n if (next !== '\\\\' && next !== quote && next !== 'n' && next !== 't' && next !== 'r') return false;\n index += 2;\n continue;\n }\n if (body[index] === quote) return false;\n index += 1;\n }\n return true;\n}\n\n/** UTF-16 code units for dart/javascript decode expressions (surrogate pairs for astral code points). */\nfunction utf16Units(content: string): number[] {\n const units: number[] = [];\n for (const char of content) {\n const codePoint = char.codePointAt(0)!;\n if (codePoint > 0xffff) {\n const offset = codePoint - 0x10000;\n units.push(0xd800 + (offset >> 10), 0xdc00 + (offset & 0x3ff));\n } else {\n units.push(codePoint);\n }\n }\n return units;\n}\n\n/** Runtime-decode expression replacing the literal, per language. */\nfunction encodeString(content: string, language: Language): string {\n if (language === 'dart') return `String.fromCharCodes(const [${utf16Units(content).join(', ')}])`;\n if (language === 'javascript') return `String.fromCharCode(${utf16Units(content).join(', ')})`;\n if (language === 'java') return `new String(new byte[]{${Array.from(Buffer.from(content, 'utf8')).join(',')}}, java.nio.charset.StandardCharsets.UTF_8)`;\n if (language === 'kotlin') return `String(byteArrayOf(${Array.from(Buffer.from(content, 'utf8')).join(',')}), Charsets.UTF_8)`;\n if (language === 'swift') return `String(bytes: [${Array.from(Buffer.from(content, 'utf8')).map((byte) => `UInt8(${byte})`).join(', ')}], encoding: .utf8)!`;\n return `string([]byte{${Array.from(Buffer.from(content, 'utf8')).join(',')}})`;\n}\n\n/**\n * Replace each eligible string literal with a deterministic runtime-decode expression.\n * Eligible literals are non-empty, single/double-quoted (templates, raw and triple-quoted\n * literals are never touched), contain no `%`, no `$`, no path/URL prefix, no disallowed\n * backslash escapes, and match none of the `excludes` regexes.\n */\nexport function encryptStringLiterals(content: string, language: Language, excludes: readonly string[], seed: string): string {\n void seed; // unit arrays are fully determined by the literal text; the seed is reserved for cross-file salt.\n const masked = maskContent(content, language);\n const encrypted = new Map<number, string>();\n masked.strings.forEach((literal, index) => {\n const kind = masked.stringKinds[index]!;\n if (kind === 'single' || kind === 'double') {\n const quote = kind === 'single' ? \"'\" : '\"';\n if (isEligible(literal, quote, excludes)) encrypted.set(index, encodeString(literal.slice(1, -1), language));\n }\n });\n return unmask(masked.masked, masked, (index) => encrypted.get(index) ?? masked.strings[index]!);\n}\n\n/**\n * Rename identifiers in one combined pass: whole-word `mappings` (longest key first) run before\n * `patterns` (regular expressions whose replacements may use $1-style group references). Both\n * operate only on code; string literals and comments are masked and restored untouched.\n */\nexport function renameIdentifiers(\n content: string,\n language: Language,\n mappings: Readonly<Record<string, string>> = {},\n patterns: ReadonlyArray<{ match: string; replacement: string }> = [],\n): string {\n const masked = maskContent(content, language);\n const parts = splitMasked(masked);\n let rebuilt = '';\n for (const part of parts) {\n if (!part.isPlaceholder) {\n // Whole-word keys never span placeholder boundaries (placeholders are control\n // characters outside the identifier class), so per-part replacement is exact.\n let text = replaceWholeWords(part.text, mappings).text;\n for (const pattern of patterns) text = text.replace(new RegExp(pattern.match, 'g'), pattern.replacement);\n rebuilt += text;\n } else if (part.placeholderKind === 'string') {\n rebuilt += `${masked.stringSep}${part.placeholderIndex}${masked.stringSep}`;\n } else {\n rebuilt += `${masked.commentSep}${part.placeholderIndex}${masked.commentSep}`;\n }\n }\n return unmask(rebuilt, masked, (index) => masked.strings[index]!);\n}\n\n/**\n * Whole-word replacement of every `keywords` mapping. Applies inside code and inside string\n * literals, but never inside comments.\n */\nexport function obfuscateKeywords(content: string, language: Language, keywords: Readonly<Record<string, string>>): string {\n const masked = maskContent(content, language);\n const replaced = replaceWholeWords(masked.masked, keywords).text;\n return unmask(replaced, masked, (index) => masked.strings[index]!);\n}\n\nconst JUNK_FORMATS: Record<Language, (name: string, value: number) => string> = {\n dart: (name, value) => `final ${name} = ${value};`,\n java: (name, value) => `class ${name.charAt(0).toUpperCase()}${name.slice(1)} { static { int j = ${value}; } }`,\n kotlin: (name, value) => `val ${name} = ${value}`,\n // Top-level code is only legal in main.swift; a caseless enum keeps junk compilable in any file.\n swift: (name, value) => `private enum ${name.charAt(0).toUpperCase()}${name.slice(1)} { static let j = ${value} }`,\n javascript: (name, value) => `const ${name} = ${value}; void ${name};`,\n go: (name, value) => `var ${name} = ${value}`,\n};\n\n/**\n * Conservative per-language declaration scanners for seed-generated renames. Every pattern\n * anchors at a line start so indented methods (framework callbacks like Flutter's `build`)\n * are never mistaken for declarations; statement call sites such as `return foo(` are\n * filtered out afterwards.\n */\nconst TYPE_DECLARATION = /(?:^|\\n)(?:(?:abstract|base|final|interface|sealed|open|data|public|private|protected|internal|static)\\s+)*(?:class|interface|enum|struct|object|union|mixin|extension|protocol|record)\\s+([A-Za-z_$][\\w$]*)/g;\nconst TOP_LEVEL_FUNCTION = /(?:^|\\n)(?:[A-Za-z_$][\\w$<>.,?\\[\\]]*\\s+)+([A-Za-z_$][\\w$]*)\\s*\\(/g;\nconst STATEMENT_PREFIXES: Readonly<Record<string, true>> = {\n return: true, throw: true, case: true, yield: true, await: true, else: true, do: true, goto: true,\n};\n/** Names that must never be generated as rename targets (entrypoints, keywords, common false positives). */\nconst NEVER_RENAME: Readonly<Record<string, true>> = {\n main: true, init: true, on: true, get: true, set: true, build: true,\n new: true, if: true, else: true, for: true, while: true, do: true, switch: true, return: true,\n class: true, struct: true, enum: true, func: true, fun: true, function: true, void: true,\n var: true, val: true, let: true, const: true, final: true, static: true, import: true, export: true,\n package: true, type: true, case: true, default: true, break: true, continue: true, try: true,\n catch: true, throw: true, async: true, await: true, in: true, is: true, as: true, of: true,\n to: true, from: true, this: true, super: true, null: true, true: true, false: true,\n};\nconst DECLARATION_PATTERNS: Record<Language, RegExp[]> = {\n dart: [TYPE_DECLARATION, TOP_LEVEL_FUNCTION],\n java: [TYPE_DECLARATION],\n kotlin: [TYPE_DECLARATION, /(?:^|\\n)fun\\s+(?:<[^>\\n]*>\\s*)?([A-Za-z_$][\\w$]*)\\s*\\(/g],\n swift: [TYPE_DECLARATION, /(?:^|\\n)func\\s+([A-Za-z_$][\\w$]*)\\s*\\(/g],\n javascript: [TYPE_DECLARATION, /(?:^|\\n)(?:async\\s+)?function\\s+([A-Za-z_$][\\w$]*)\\s*\\(/g, /(?:^|\\n)const\\s+([A-Za-z_$][\\w$]*)\\s*=\\s*(?:async\\s*)?(?:\\(|function)/g],\n go: [/(?:^|\\n)type\\s+([A-Za-z_]\\w*)\\s+(?:struct|interface)\\b/g, /(?:^|\\n)func\\s+([A-Za-z_]\\w*)\\s*\\(/g],\n};\n\n/** Extract declared type/function names worth renaming; methods and statement sites stay untouched. */\nexport function extractDeclaredIdentifiers(content: string, language: Language): string[] {\n const names: string[] = [];\n for (const pattern of DECLARATION_PATTERNS[language]) {\n pattern.lastIndex = 0;\n for (const match of content.matchAll(pattern)) {\n const name = match[1]!;\n if (name in NEVER_RENAME) continue;\n if (pattern === TOP_LEVEL_FUNCTION) {\n const lineStart = content.lastIndexOf('\\n', match.index!) + 1;\n const prefix = content.slice(lineStart, match.index!).trim().split(/\\s+/).pop();\n if (prefix && prefix in STATEMENT_PREFIXES) continue;\n }\n names.push(name);\n }\n }\n return names;\n}\n\n/**\n * Deterministically pick `count` of the declared names (seeded hash ordering) and map each\n * to a seed-derived token. Identical (declared, count, seed) always yields the same mapping;\n * a different seed yields a different selection and different tokens.\n */\nexport function generateIdentifierMappings(declared: readonly string[], count: number, seed: string): Record<string, string> {\n const unique = [...new Set(declared)].filter(name => !(name in NEVER_RENAME));\n const selected = unique\n .map(name => ({ name, score: deriveHash(seed, 'identifier-select', name) }))\n .sort((a, b) => a.score < b.score ? -1 : a.score > b.score ? 1 : 0)\n .slice(0, Math.max(0, count));\n const mappings: Record<string, string> = {};\n for (const { name } of selected) mappings[name] = deriveToken(seed, 'identifier', name);\n return mappings;\n}\n\n/**\n * Append `lines` deterministic junk statements at end of file, ensuring exactly one trailing\n * newline before them. Names and values derive from the seed and the project-relative file\n * path, so two files never share junk names. Non-positive `lines` returns the input unchanged.\n */\nexport function insertJunkCode(content: string, language: Language, lines: number, seed: string, filePath: string): string {\n if (content.length === 0 || lines <= 0) return content;\n const base = content.replace(/[\\t \\r\\n]+$/, '');\n const statements: string[] = [];\n for (let i = 0; i < lines; i += 1) {\n const name = deriveToken(seed, filePath, 'junk', `${i}`);\n const value = seededInt(seed, `${filePath}:junk:${i}`, 0, 0x7fffffff);\n statements.push(JUNK_FORMATS[language](name, value));\n }\n return `${base}\\n${statements.join('\\n')}\\n`;\n}\n\n/**\n * Apply the code transforms in fixed order — stripComments, encryptStringLiterals,\n * renameIdentifiers, obfuscateKeywords, insertJunkCode — each only when enabled in `options`.\n * Deterministic: identical arguments yield byte-identical output. Idempotence is not a\n * contract; the orchestrator restores pristine sources between runs.\n */\nexport function transformCode(content: string, language: Language, options: CodeOptions, seed: string, filePath: string): string {\n let result = content;\n if (options.removeComments === true) result = stripComments(result, language);\n if (options.encryptStrings) result = encryptStringLiterals(result, language, options.encryptStrings.excludes, seed);\n if (options.renameIdentifiers) {\n result = renameIdentifiers(result, language, options.renameIdentifiers.mappings, options.renameIdentifiers.patterns);\n }\n if (options.keywords && Object.keys(options.keywords).length > 0) result = obfuscateKeywords(result, language, options.keywords);\n if (options.junkCode) result = insertJunkCode(result, language, options.junkCode.lines, seed, filePath);\n return result;\n}\n"],"mappings":";;;AAgCA,MAAM,uBAAuB;CAAC;CAAU;CAAU;CAAU;CAAU;CAAU;CAAU;CAAU;CAAU;AAAQ;;AAGtH,SAAS,eAAe,SAA4D;CAClF,IAAI;CACJ,IAAI;CACJ,KAAK,MAAM,aAAa,sBAAsB;EAC5C,IAAI,QAAQ,SAAS,SAAS,GAAG;EACjC,IAAI,UAAU,KAAA,GAAW;GAAE,QAAQ;GAAW;EAAU;EACxD,SAAS;EACT;CACF;CACA,IAAI,UAAU,KAAA,KAAa,WAAW,KAAA,GAGpC,OAAO;EAAE,WAAW;EAAU,YAAY;CAAS;CAErD,OAAO;EAAE,WAAW;EAAO,YAAY;CAAO;AAChD;;AAGA,SAAS,QAAQ,SAAiB,OAAuB;CACvD,MAAM,SAAS,QAAQ,QAAQ,MAAM,KAAK;CAC1C,MAAM,SAAS,QAAQ,QAAQ,MAAM,KAAK;CAC1C,IAAI,WAAW,IAAI,OAAO,WAAW,KAAK,QAAQ,SAAS;CAC3D,IAAI,WAAW,IAAI,OAAO;CAC1B,OAAO,KAAK,IAAI,QAAQ,MAAM;AAChC;;AAGA,SAAS,WAAW,SAAiB,OAAe,OAAe,OAAgB,WAA4B;CAC7G,IAAI,QAAQ;CACZ,OAAO,QAAQ,QAAQ,QAAQ;EAC7B,MAAM,OAAO,QAAQ;EACrB,IAAI,CAAC,cAAc,SAAS,QAAQ,SAAS,OAAO,OAAO;EAC3D,IAAI,CAAC,SAAS,SAAS,MAAM;GAAE,SAAS;GAAG;EAAU;EACrD,IAAI,SAAS,OAAO,OAAO,QAAQ;EACnC,SAAS;CACX;CACA,OAAO,QAAQ;AACjB;;AAGA,SAAS,iBAAiB,SAAiB,OAAe,OAAe,OAAwB;CAC/F,MAAM,YAAY,MAAM,OAAO,CAAC;CAChC,IAAI,QAAQ;CACZ,OAAO,QAAQ,QAAQ,QAAQ;EAC7B,IAAI,CAAC,SAAS,QAAQ,WAAW,MAAM;GAAE,SAAS;GAAG;EAAU;EAC/D,IAAI,QAAQ,WAAW,WAAW,KAAK,GAAG,OAAO,QAAQ;EACzD,SAAS;CACX;CACA,OAAO,QAAQ;AACjB;;;;;;;;;;;;AAaA,SAAS,YAAY,SAAiB,UAA4B;CAChE,MAAM,EAAE,WAAW,eAAe,eAAe,OAAO;CACxD,MAAM,QAAuB,CAAC;CAC9B,MAAM,UAAoB,CAAC;CAC3B,MAAM,cAAoD,CAAC;CAC3D,MAAM,WAAqB,CAAC;CAC5B,MAAM,eAA0B,CAAC;CACjC,MAAM,aAAa,aAAa;CAEhC,IAAI,QAAQ;CACZ,OAAO,QAAQ,QAAQ,QAAQ;EAE7B,IAAI,cAAc,UAAU,KAAK,QAAQ,WAAW,IAAI,GAAG;GACzD,MAAM,MAAM,QAAQ,SAAS,KAAK;GAClC,MAAM,KAAK;IAAE,MAAM,QAAQ,MAAM,OAAO,GAAG;IAAG,eAAe;IAAM,kBAAkB,SAAS;IAAQ,iBAAiB;GAAU,CAAC;GAClI,SAAS,KAAK,QAAQ,MAAM,OAAO,GAAG,CAAC;GACvC,aAAa,KAAK,IAAI;GACtB,QAAQ;GACR;EACF;EAEA,IAAI,QAAQ,WAAW,MAAM,KAAK,GAAG;GACnC,MAAM,MAAM,QAAQ,SAAS,KAAK;GAClC,MAAM,KAAK;IAAE,MAAM,QAAQ,MAAM,OAAO,GAAG;IAAG,eAAe;IAAM,kBAAkB,SAAS;IAAQ,iBAAiB;GAAU,CAAC;GAClI,SAAS,KAAK,QAAQ,MAAM,OAAO,GAAG,CAAC;GACvC,aAAa,KAAK,KAAK;GACvB,QAAQ;GACR;EACF;EAEA,IAAI,QAAQ,WAAW,MAAM,KAAK,GAAG;GACnC,MAAM,QAAQ,QAAQ,QAAQ,MAAM,QAAQ,CAAC;GAC7C,MAAM,MAAM,UAAU,KAAK,QAAQ,SAAS,QAAQ;GACpD,MAAM,KAAK;IAAE,MAAM,QAAQ,MAAM,OAAO,GAAG;IAAG,eAAe;IAAM,kBAAkB,SAAS;IAAQ,iBAAiB;GAAU,CAAC;GAClI,SAAS,KAAK,QAAQ,MAAM,OAAO,GAAG,CAAC;GACvC,aAAa,KAAK,KAAK;GACvB,QAAQ;GACR;EACF;EAGA,IAAI,aAAa,WAAW,QAAQ,WAAW,QAAQ,KAAK,KAAK,QAAQ,WAAW,WAAQ,KAAK,IAAI;GACnG,MAAM,QAAQ,QAAQ,QAAQ;GAC9B,MAAM,MAAM,iBAAiB,SAAS,QAAQ,GAAG,OAAO,IAAI;GAC5D,MAAM,OAAO,QAAQ,MAAM,OAAO,GAAG;GACrC,MAAM,KAAK;IAAE;IAAM,eAAe;IAAM,kBAAkB,QAAQ;IAAQ,iBAAiB;GAAS,CAAC;GACrG,QAAQ,KAAK,IAAI;GACjB,YAAY,KAAK,OAAO;GACxB,QAAQ;GACR;EACF;EACA,IAAI,aAAa,WAAW,QAAQ,WAAW,MAAM,KAAK,KAAK,QAAQ,WAAW,OAAM,KAAK,IAAI;GAC/F,MAAM,QAAQ,QAAQ,QAAQ;GAC9B,MAAM,MAAM,WAAW,SAAS,QAAQ,GAAG,OAAO,MAAM,KAAK;GAC7D,MAAM,OAAO,QAAQ,MAAM,OAAO,GAAG;GACrC,MAAM,KAAK;IAAE;IAAM,eAAe;IAAM,kBAAkB,QAAQ;IAAQ,iBAAiB;GAAS,CAAC;GACrG,QAAQ,KAAK,IAAI;GACjB,YAAY,KAAK,OAAO;GACxB,QAAQ;GACR;EACF;EAEA,KAAK,aAAa,UAAU,aAAa,aAAa,QAAQ,WAAW,UAAO,KAAK,GAAG;GACtF,MAAM,MAAM,iBAAiB,SAAS,QAAQ,GAAG,MAAK,KAAK;GAC3D,MAAM,OAAO,QAAQ,MAAM,OAAO,GAAG;GACrC,MAAM,KAAK;IAAE;IAAM,eAAe;IAAM,kBAAkB,QAAQ;IAAQ,iBAAiB;GAAS,CAAC;GACrG,QAAQ,KAAK,IAAI;GACjB,YAAY,KAAK,OAAO;GACxB,QAAQ;GACR;EACF;EACA,IAAI,aAAa,UAAU,QAAQ,WAAW,OAAO,KAAK,GAAG;GAC3D,MAAM,MAAM,iBAAiB,SAAS,QAAQ,GAAG,KAAK,KAAK;GAC3D,MAAM,OAAO,QAAQ,MAAM,OAAO,GAAG;GACrC,MAAM,KAAK;IAAE;IAAM,eAAe;IAAM,kBAAkB,QAAQ;IAAQ,iBAAiB;GAAS,CAAC;GACrG,QAAQ,KAAK,IAAI;GACjB,YAAY,KAAK,OAAO;GACxB,QAAQ;GACR;EACF;EAEA,IAAI,cAAc,QAAQ,WAAW,KAAK;GACxC,MAAM,MAAM,WAAW,SAAS,QAAQ,GAAG,KAAK,OAAO,IAAI;GAC3D,MAAM,OAAO,QAAQ,MAAM,OAAO,GAAG;GACrC,MAAM,KAAK;IAAE;IAAM,eAAe;IAAM,kBAAkB,QAAQ;IAAQ,iBAAiB;GAAS,CAAC;GACrG,QAAQ,KAAK,IAAI;GACjB,YAAY,KAAK,OAAO;GACxB,QAAQ;GACR;EACF;EAEA,IAAI,aAAa,WAAW,QAAQ,WAAW,KAAK;GAClD,IAAI,UAAU;GACd,OAAO,QAAQ,aAAa,KAAK,WAAW;GAE5C,IADc,QAAQ,aACR,MAAK;IACjB,IAAI,SAAS,UAAU;IACvB,IAAI,MAAM;IACV,OAAO,SAAS,QAAQ,QAAQ;KAC9B,MAAM,QAAQ,QAAQ,QAAQ,MAAK,MAAM;KACzC,IAAI,UAAU,IAAI;MAAE,SAAS,QAAQ;MAAQ;KAAO;KACpD,IAAI,WAAW,QAAQ;KACvB,OAAO,QAAQ,cAAc,KAAK,YAAY;KAC9C,IAAI,WAAW,QAAQ,MAAM,UAAU,OAAO;MAAE,MAAM;MAAU;KAAO;KACvE,SAAS,QAAQ;IACnB;IACA,IAAI,QAAQ,IAAI,MAAM,QAAQ;IAC9B,MAAM,OAAO,QAAQ,MAAM,OAAO,GAAG;IACrC,MAAM,KAAK;KAAE;KAAM,eAAe;KAAM,kBAAkB,QAAQ;KAAQ,iBAAiB;IAAS,CAAC;IACrG,QAAQ,KAAK,IAAI;IACjB,YAAY,KAAK,OAAO;IACxB,QAAQ;IACR;GACF;EACF;EAEA,MAAM,OAAO,QAAQ;EACrB,IAAI,SAAS,OAAO,SAAS,MAAK;GAChC,MAAM,MAAM,WAAW,SAAS,QAAQ,GAAG,MAAM,OAAO,KAAK;GAC7D,MAAM,OAAO,QAAQ,MAAM,OAAO,GAAG;GACrC,MAAM,KAAK;IAAE;IAAM,eAAe;IAAM,kBAAkB,QAAQ;IAAQ,iBAAiB;GAAS,CAAC;GACrG,QAAQ,KAAK,IAAI;GACjB,YAAY,KAAK,SAAS,MAAM,WAAW,QAAQ;GACnD,QAAQ;GACR;EACF;EAGA,MAAM,MAAM,eAAe,SAAS,QAAQ,GAAG;GAAC;GAAK;GAAK;GAAK;GAAK;GAAK;EAAG,CAAC;EAC7E,MAAM,KAAK;GAAE,MAAM,QAAQ,MAAM,OAAO,GAAG;GAAG,eAAe;GAAO,kBAAkB;GAAI,iBAAiB;EAAS,CAAC;EACrH,QAAQ;CACV;CASA,OAAO;EAAE,QAPM,MACZ,KAAK,SAAU,KAAK,gBACjB,KAAK,oBAAoB,WACvB,GAAG,YAAY,KAAK,mBAAmB,cACvC,GAAG,aAAa,KAAK,mBAAmB,eAC1C,KAAK,IAAK,CAAC,CACd,KAAK,EACM;EAAG;EAAS;EAAa;EAAU;EAAc;EAAW;CAAW;AACvF;;AAGA,SAAS,eAAe,SAAiB,OAAe,OAAyB;CAC/E,IAAI,OAAO,QAAQ;CACnB,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,QAAQ,QAAQ,QAAQ,MAAM,KAAK;EACzC,IAAI,UAAU,MAAM,QAAQ,MAAM,OAAO;CAC3C;CACA,OAAO;AACT;;AAGA,SAAS,YAAY,QAA+B;CAClD,MAAM,mBAAmB,IAAI,OAC3B,GAAG,iBAAiB,OAAO,SAAS,EAAE,QAAQ,iBAAiB,OAAO,SAAS,EAAE,GAAG,iBAAiB,OAAO,UAAU,EAAE,QAAQ,iBAAiB,OAAO,UAAU,KAClK,GACF;CACA,MAAM,QAAuB,CAAC;CAC9B,IAAI,OAAO;CACX,KAAK,MAAM,SAAS,OAAO,OAAO,SAAS,gBAAgB,GAAG;EAC5D,IAAI,MAAM,QAAS,MACjB,MAAM,KAAK;GAAE,MAAM,OAAO,OAAO,MAAM,MAAM,MAAM,KAAM;GAAG,eAAe;GAAO,kBAAkB;GAAI,iBAAiB;EAAS,CAAC;EAErI,MAAM,eAAe,MAAM;EAC3B,MAAM,YAAY,iBAAiB,KAAA;EACnC,MAAM,KAAK;GACT,MAAM,MAAM;GACZ,eAAe;GACf,kBAAkB,OAAO,YAAY,eAAe,MAAM,EAAE;GAC5D,iBAAiB,YAAY,YAAY;EAC3C,CAAC;EACD,OAAO,MAAM,QAAS,MAAM,EAAE,CAAC;CACjC;CACA,IAAI,OAAO,OAAO,OAAO,QACvB,MAAM,KAAK;EAAE,MAAM,OAAO,OAAO,MAAM,IAAI;EAAG,eAAe;EAAO,kBAAkB;EAAI,iBAAiB;CAAS,CAAC;CAEvH,OAAO;AACT;;AAGA,SAAS,iBAAiB,MAAsB;CAC9C,OAAO,qBAAqB,KAAK,IAAI,IAAI,KAAK,SAAS;AACzD;;AAGA,SAAS,OAAO,eAAuB,QAAgB,eAAkD;CACvG,MAAM,gBAAgB,IAAI,OAAO,GAAG,iBAAiB,OAAO,SAAS,EAAE,QAAQ,iBAAiB,OAAO,SAAS,KAAK,GAAG;CACxH,OAAO,cAAc,QAAQ,gBAAgB,QAAQ,WAAmB,cAAc,OAAO,MAAM,CAAC,CAAC;AACvG;;;;;AAMA,SAAgB,cAAc,SAAiB,UAA4B;CACzE,MAAM,SAAS,YAAY,SAAS,QAAQ;CAW5C,OAAO,OAVO,YAAY,MACN,CAAC,CAClB,KAAK,SAAS;EACb,IAAI,CAAC,KAAK,eAAe,OAAO,KAAK;EACrC,IAAI,KAAK,oBAAoB,UAAU,OAAO,GAAG,OAAO,YAAY,KAAK,mBAAmB,OAAO;EACnG,MAAM,WAAW,OAAO,SAAS,KAAK;EACtC,IAAI,OAAO,aAAa,KAAK,mBAAmB,OAAO;EACvD,OAAO,SAAS,QAAQ,YAAY,EAAE;CACxC,CAAC,CAAC,CACD,KAAK,EACY,GAAG,SAAS,UAAU,OAAO,QAAQ,MAAO;AAClE;;AAGA,SAAS,WAAW,SAAiB,OAAe,UAAsC;CACxF,MAAM,OAAO,QAAQ,MAAM,GAAG,EAAE;CAChC,IAAI,KAAK,WAAW,GAAG,OAAO;CAC9B,IAAI,KAAK,SAAS,GAAG,KAAK,KAAK,SAAS,GAAG,GAAG,OAAO;CACrD,IAAI,KAAK,WAAW,IAAI,KAAK,KAAK,WAAW,KAAK,KAAK,gCAAgC,KAAK,IAAI,GAAG,OAAO;CAC1G,KAAK,MAAM,WAAW,UACpB,IAAI,IAAI,OAAO,OAAO,CAAC,CAAC,KAAK,IAAI,GAAG,OAAO;CAE7C,IAAI,QAAQ;CACZ,OAAO,QAAQ,KAAK,QAAQ;EAC1B,IAAI,KAAK,WAAW,MAAM;GACxB,MAAM,OAAO,KAAK,QAAQ;GAC1B,IAAI,SAAS,QAAQ,SAAS,SAAS,SAAS,OAAO,SAAS,OAAO,SAAS,KAAK,OAAO;GAC5F,SAAS;GACT;EACF;EACA,IAAI,KAAK,WAAW,OAAO,OAAO;EAClC,SAAS;CACX;CACA,OAAO;AACT;;AAGA,SAAS,WAAW,SAA2B;CAC7C,MAAM,QAAkB,CAAC;CACzB,KAAK,MAAM,QAAQ,SAAS;EAC1B,MAAM,YAAY,KAAK,YAAY,CAAC;EACpC,IAAI,YAAY,OAAQ;GACtB,MAAM,SAAS,YAAY;GAC3B,MAAM,KAAK,SAAU,UAAU,KAAK,SAAU,SAAS,KAAM;EAC/D,OACE,MAAM,KAAK,SAAS;CAExB;CACA,OAAO;AACT;;AAGA,SAAS,aAAa,SAAiB,UAA4B;CACjE,IAAI,aAAa,QAAQ,OAAO,+BAA+B,WAAW,OAAO,CAAC,CAAC,KAAK,IAAI,EAAE;CAC9F,IAAI,aAAa,cAAc,OAAO,uBAAuB,WAAW,OAAO,CAAC,CAAC,KAAK,IAAI,EAAE;CAC5F,IAAI,aAAa,QAAQ,OAAO,yBAAyB,MAAM,KAAK,OAAO,KAAK,SAAS,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE;CAC5G,IAAI,aAAa,UAAU,OAAO,sBAAsB,MAAM,KAAK,OAAO,KAAK,SAAS,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE;CAC3G,IAAI,aAAa,SAAS,OAAO,kBAAkB,MAAM,KAAK,OAAO,KAAK,SAAS,MAAM,CAAC,CAAC,CAAC,KAAK,SAAS,SAAS,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE;CACvI,OAAO,iBAAiB,MAAM,KAAK,OAAO,KAAK,SAAS,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE;AAC7E;;;;;;;AAQA,SAAgB,sBAAsB,SAAiB,UAAoB,UAA6B,MAAsB;CAE5H,MAAM,SAAS,YAAY,SAAS,QAAQ;CAC5C,MAAM,4BAAY,IAAI,IAAoB;CAC1C,OAAO,QAAQ,SAAS,SAAS,UAAU;EACzC,MAAM,OAAO,OAAO,YAAY;EAChC,IAAI,SAAS,YAAY,SAAS,UAE5B;OAAA,WAAW,SADD,SAAS,WAAW,MAAM,MACT,QAAQ,GAAG,UAAU,IAAI,OAAO,aAAa,QAAQ,MAAM,GAAG,EAAE,GAAG,QAAQ,CAAC;EAAA;CAE/G,CAAC;CACD,OAAO,OAAO,OAAO,QAAQ,SAAS,UAAU,UAAU,IAAI,KAAK,KAAK,OAAO,QAAQ,MAAO;AAChG;;;;;;AAOA,SAAgB,kBACd,SACA,UACA,WAA6C,CAAC,GAC9C,WAAkE,CAAC,GAC3D;CACR,MAAM,SAAS,YAAY,SAAS,QAAQ;CAC5C,MAAM,QAAQ,YAAY,MAAM;CAChC,IAAI,UAAU;CACd,KAAK,MAAM,QAAQ,OACjB,IAAI,CAAC,KAAK,eAAe;EAGvB,IAAI,OAAO,kBAAkB,KAAK,MAAM,QAAQ,CAAC,CAAC;EAClD,KAAK,MAAM,WAAW,UAAU,OAAO,KAAK,QAAQ,IAAI,OAAO,QAAQ,OAAO,GAAG,GAAG,QAAQ,WAAW;EACvG,WAAW;CACb,OAAO,IAAI,KAAK,oBAAoB,UAClC,WAAW,GAAG,OAAO,YAAY,KAAK,mBAAmB,OAAO;MAEhE,WAAW,GAAG,OAAO,aAAa,KAAK,mBAAmB,OAAO;CAGrE,OAAO,OAAO,SAAS,SAAS,UAAU,OAAO,QAAQ,MAAO;AAClE;;;;;AAMA,SAAgB,kBAAkB,SAAiB,UAAoB,UAAoD;CACzH,MAAM,SAAS,YAAY,SAAS,QAAQ;CAC5C,MAAM,WAAW,kBAAkB,OAAO,QAAQ,QAAQ,CAAC,CAAC;CAC5D,OAAO,OAAO,UAAU,SAAS,UAAU,OAAO,QAAQ,MAAO;AACnE;AAEA,MAAM,eAA0E;CAC9E,OAAO,MAAM,UAAU,SAAS,KAAK,KAAK,MAAM;CAChD,OAAO,MAAM,UAAU,SAAS,KAAK,OAAO,CAAC,CAAC,CAAC,YAAY,IAAI,KAAK,MAAM,CAAC,EAAE,sBAAsB,MAAM;CACzG,SAAS,MAAM,UAAU,OAAO,KAAK,KAAK;CAE1C,QAAQ,MAAM,UAAU,gBAAgB,KAAK,OAAO,CAAC,CAAC,CAAC,YAAY,IAAI,KAAK,MAAM,CAAC,EAAE,oBAAoB,MAAM;CAC/G,aAAa,MAAM,UAAU,SAAS,KAAK,KAAK,MAAM,SAAS,KAAK;CACpE,KAAK,MAAM,UAAU,OAAO,KAAK,KAAK;AACxC;;;;;;;AAQA,MAAM,mBAAmB;AACzB,MAAM,qBAAqB;AAC3B,MAAM,qBAAqD;CACzD,QAAQ;CAAM,OAAO;CAAM,MAAM;CAAM,OAAO;CAAM,OAAO;CAAM,MAAM;CAAM,IAAI;CAAM,MAAM;AAC/F;;AAEA,MAAM,eAA+C;CACnD,MAAM;CAAM,MAAM;CAAM,IAAI;CAAM,KAAK;CAAM,KAAK;CAAM,OAAO;CAC/D,KAAK;CAAM,IAAI;CAAM,MAAM;CAAM,KAAK;CAAM,OAAO;CAAM,IAAI;CAAM,QAAQ;CAAM,QAAQ;CACzF,OAAO;CAAM,QAAQ;CAAM,MAAM;CAAM,MAAM;CAAM,KAAK;CAAM,UAAU;CAAM,MAAM;CACpF,KAAK;CAAM,KAAK;CAAM,KAAK;CAAM,OAAO;CAAM,OAAO;CAAM,QAAQ;CAAM,QAAQ;CAAM,QAAQ;CAC/F,SAAS;CAAM,MAAM;CAAM,MAAM;CAAM,SAAS;CAAM,OAAO;CAAM,UAAU;CAAM,KAAK;CACxF,OAAO;CAAM,OAAO;CAAM,OAAO;CAAM,OAAO;CAAM,IAAI;CAAM,IAAI;CAAM,IAAI;CAAM,IAAI;CACtF,IAAI;CAAM,MAAM;CAAM,MAAM;CAAM,OAAO;CAAM,MAAM;CAAM,MAAM;CAAM,OAAO;AAChF;AACA,MAAM,uBAAmD;CACvD,MAAM,CAAC,kBAAkB,kBAAkB;CAC3C,MAAM,CAAC,gBAAgB;CACvB,QAAQ,CAAC,kBAAkB,yDAAyD;CACpF,OAAO,CAAC,kBAAkB,yCAAyC;CACnE,YAAY;EAAC;EAAkB;EAA4D;CAAwE;CACnK,IAAI,CAAC,2DAA2D,qCAAqC;AACvG;;AAGA,SAAgB,2BAA2B,SAAiB,UAA8B;CACxF,MAAM,QAAkB,CAAC;CACzB,KAAK,MAAM,WAAW,qBAAqB,WAAW;EACpD,QAAQ,YAAY;EACpB,KAAK,MAAM,SAAS,QAAQ,SAAS,OAAO,GAAG;GAC7C,MAAM,OAAO,MAAM;GACnB,IAAI,QAAQ,cAAc;GAC1B,IAAI,YAAY,oBAAoB;IAClC,MAAM,YAAY,QAAQ,YAAY,MAAM,MAAM,KAAM,IAAI;IAC5D,MAAM,SAAS,QAAQ,MAAM,WAAW,MAAM,KAAM,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,IAAI;IAC9E,IAAI,UAAU,UAAU,oBAAoB;GAC9C;GACA,MAAM,KAAK,IAAI;EACjB;CACF;CACA,OAAO;AACT;;;;;;AAOA,SAAgB,2BAA2B,UAA6B,OAAe,MAAsC;CAE3H,MAAM,WADS,CAAC,GAAG,IAAI,IAAI,QAAQ,CAAC,CAAC,CAAC,QAAO,SAAQ,EAAE,QAAQ,aACzC,CAAC,CACpB,KAAI,UAAS;EAAE;EAAM,OAAO,WAAW,MAAM,qBAAqB,IAAI;CAAE,EAAE,CAAC,CAC3E,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,QAAQ,KAAK,EAAE,QAAQ,EAAE,QAAQ,IAAI,CAAC,CAAC,CAClE,MAAM,GAAG,KAAK,IAAI,GAAG,KAAK,CAAC;CAC9B,MAAM,WAAmC,CAAC;CAC1C,KAAK,MAAM,EAAE,UAAU,UAAU,SAAS,QAAQ,YAAY,MAAM,cAAc,IAAI;CACtF,OAAO;AACT;;;;;;AAOA,SAAgB,eAAe,SAAiB,UAAoB,OAAe,MAAc,UAA0B;CACzH,IAAI,QAAQ,WAAW,KAAK,SAAS,GAAG,OAAO;CAC/C,MAAM,OAAO,QAAQ,QAAQ,eAAe,EAAE;CAC9C,MAAM,aAAuB,CAAC;CAC9B,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,KAAK,GAAG;EACjC,MAAM,OAAO,YAAY,MAAM,UAAU,QAAQ,GAAG,GAAG;EACvD,MAAM,QAAQ,UAAU,MAAM,GAAG,SAAS,QAAQ,KAAK,GAAG,UAAU;EACpE,WAAW,KAAK,aAAa,SAAS,CAAC,MAAM,KAAK,CAAC;CACrD;CACA,OAAO,GAAG,KAAK,IAAI,WAAW,KAAK,IAAI,EAAE;AAC3C;;;;;;;AAQA,SAAgB,cAAc,SAAiB,UAAoB,SAAsB,MAAc,UAA0B;CAC/H,IAAI,SAAS;CACb,IAAI,QAAQ,mBAAmB,MAAM,SAAS,cAAc,QAAQ,QAAQ;CAC5E,IAAI,QAAQ,gBAAgB,SAAS,sBAAsB,QAAQ,UAAU,QAAQ,eAAe,UAAU,IAAI;CAClH,IAAI,QAAQ,mBACV,SAAS,kBAAkB,QAAQ,UAAU,QAAQ,kBAAkB,UAAU,QAAQ,kBAAkB,QAAQ;CAErH,IAAI,QAAQ,YAAY,OAAO,KAAK,QAAQ,QAAQ,CAAC,CAAC,SAAS,GAAG,SAAS,kBAAkB,QAAQ,UAAU,QAAQ,QAAQ;CAC/H,IAAI,QAAQ,UAAU,SAAS,eAAe,QAAQ,UAAU,QAAQ,SAAS,OAAO,MAAM,QAAQ;CACtG,OAAO;AACT"}
@@ -0,0 +1,262 @@
1
+ import { lstatSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { createHash } from "node:crypto";
4
+ // src/common.ts
5
+ const FRAMEWORKS = [
6
+ "flutter",
7
+ "android-java",
8
+ "swift",
9
+ "react-native",
10
+ "expo",
11
+ "uni-app",
12
+ "mygo"
13
+ ];
14
+ /** Conservative generated/ignored directory names; the tool never writes outside the walked set. */
15
+ const SKIPPED_DIRECTORIES = {
16
+ ".git": true,
17
+ ".svn": true,
18
+ ".hg": true,
19
+ ".idea": true,
20
+ ".vscode": true,
21
+ ".factory-input": true,
22
+ ".factory-cache": true,
23
+ ".dart_tool": true,
24
+ "node_modules": true,
25
+ "Pods": true,
26
+ ".gradle": true,
27
+ ".build": true,
28
+ "build": true,
29
+ "dist": true,
30
+ "DerivedData": true,
31
+ ".mygo": true,
32
+ ".expo": true,
33
+ "unpackage": true,
34
+ ".yarn": true,
35
+ ".turbo": true,
36
+ "coverage": true,
37
+ ".cargo": true
38
+ };
39
+ const SKIPPED_FILES = {
40
+ "Generated.xcconfig": true,
41
+ "flutter_export_environment.sh": true,
42
+ ".DS_Store": true
43
+ };
44
+ const IMAGE_EXTENSIONS = {
45
+ png: true,
46
+ jpg: true,
47
+ jpeg: true,
48
+ webp: true,
49
+ gif: true,
50
+ svg: true,
51
+ ico: true,
52
+ bmp: true,
53
+ heic: true
54
+ };
55
+ const RESOURCE_EXTENSIONS = {
56
+ png: true,
57
+ jpg: true,
58
+ jpeg: true,
59
+ webp: true,
60
+ gif: true,
61
+ svg: true,
62
+ ico: true,
63
+ bmp: true,
64
+ heic: true,
65
+ ttf: true,
66
+ otf: true,
67
+ woff: true,
68
+ woff2: true,
69
+ mp3: true,
70
+ wav: true,
71
+ m4a: true,
72
+ aac: true,
73
+ mp4: true,
74
+ mov: true,
75
+ m4v: true,
76
+ json: true,
77
+ plist: true,
78
+ strings: true,
79
+ xml: true,
80
+ storyboard: true,
81
+ xib: true,
82
+ arb: true,
83
+ po: true,
84
+ lproj: true,
85
+ car: true,
86
+ dat: true,
87
+ bin: true
88
+ };
89
+ const TEXT_EXTENSIONS = {
90
+ txt: true,
91
+ md: true,
92
+ json: true,
93
+ yaml: true,
94
+ yml: true,
95
+ plist: true,
96
+ strings: true,
97
+ xml: true,
98
+ html: true,
99
+ htm: true,
100
+ csv: true,
101
+ tsv: true,
102
+ po: true,
103
+ arb: true,
104
+ vue: true,
105
+ ini: true,
106
+ env: true,
107
+ pbxproj: true,
108
+ entitlements: true,
109
+ css: true,
110
+ scss: true,
111
+ sass: true,
112
+ less: true
113
+ };
114
+ const LANGUAGE_EXTENSIONS = {
115
+ dart: "dart",
116
+ java: "java",
117
+ kt: "kotlin",
118
+ kts: "kotlin",
119
+ swift: "swift",
120
+ js: "javascript",
121
+ jsx: "javascript",
122
+ ts: "javascript",
123
+ tsx: "javascript",
124
+ mjs: "javascript",
125
+ cjs: "javascript",
126
+ go: "go"
127
+ };
128
+ function extension(relativePath) {
129
+ const base = relativePath.slice(relativePath.lastIndexOf("/") + 1);
130
+ const dot = base.lastIndexOf(".");
131
+ return dot < 1 ? "" : base.slice(dot + 1).toLowerCase();
132
+ }
133
+ function basenameWithoutExtension(relativePath) {
134
+ const base = relativePath.slice(relativePath.lastIndexOf("/") + 1);
135
+ const dot = base.lastIndexOf(".");
136
+ return dot < 1 ? base : base.slice(0, dot);
137
+ }
138
+ function isImageFile(relativePath) {
139
+ return extension(relativePath) in IMAGE_EXTENSIONS;
140
+ }
141
+ function isResourceFile(relativePath) {
142
+ return extension(relativePath) in RESOURCE_EXTENSIONS;
143
+ }
144
+ function isTextFile(relativePath) {
145
+ return extension(relativePath) in TEXT_EXTENSIONS;
146
+ }
147
+ function classifyLanguage(relativePath) {
148
+ return LANGUAGE_EXTENSIONS[extension(relativePath)];
149
+ }
150
+ /** Text or code files that can carry path/name references worth rewriting during renames. */
151
+ function isReferenceCarrier(relativePath) {
152
+ return isTextFile(relativePath) || classifyLanguage(relativePath) !== void 0;
153
+ }
154
+ /**
155
+ * Code generated by build tooling (json_serializable, freezed, protobuf, …). Seed-generated
156
+ * renames skip these files so generated serializers keep referencing the original names.
157
+ */
158
+ function isGeneratedCode(relativePath) {
159
+ return /(?:^|\/)[^/]*\.(?:g|freezed|pb|grpc)\.dart$/.test(relativePath) || /(?:^|\/)[^/]*\.g\.(?:java|kt|kts)$/.test(relativePath);
160
+ }
161
+ /** Walk owned project files deterministically, skipping generated/ignored paths and symlinks. */
162
+ function walkProject(root) {
163
+ const files = [];
164
+ let skipped = 0;
165
+ function visit(directory, prefix) {
166
+ for (const name of readdirSync(directory).sort()) {
167
+ const absolute = join(directory, name);
168
+ const relativePath = prefix ? `${prefix}/${name}` : name;
169
+ const stat = lstatSync(absolute);
170
+ if (stat.isSymbolicLink()) {
171
+ skipped += 1;
172
+ continue;
173
+ }
174
+ if (stat.isDirectory()) {
175
+ if (name in SKIPPED_DIRECTORIES) {
176
+ skipped += 1;
177
+ continue;
178
+ }
179
+ visit(absolute, relativePath);
180
+ } else if (stat.isFile()) {
181
+ if (name in SKIPPED_FILES) {
182
+ skipped += 1;
183
+ continue;
184
+ }
185
+ files.push({
186
+ absolute,
187
+ relative: relativePath
188
+ });
189
+ } else skipped += 1;
190
+ }
191
+ }
192
+ const rootStat = lstatSync(root);
193
+ if (!rootStat.isDirectory() || rootStat.isSymbolicLink()) throw new Error(`Project root is not a real directory: ${root}`);
194
+ visit(root, "");
195
+ return {
196
+ files,
197
+ skipped
198
+ };
199
+ }
200
+ /** Deterministic hash of the joined parts. */
201
+ function deriveHash(...parts) {
202
+ return createHash("sha256").update(parts.join("\0")).digest("hex");
203
+ }
204
+ /** Deterministic short identifier token: 'obf' + 8 lowercase alphanumeric chars. */
205
+ function deriveToken(seed, ...parts) {
206
+ const hash = deriveHash(seed, ...parts);
207
+ let value = "";
208
+ for (let index = 0; index < 8; index += 1) value += "0123456789abcdefghijklmnopqrstuvwxyz"[parseInt(hash.slice(index * 2, index * 2 + 2), 16) % 36];
209
+ return `obf${value}`;
210
+ }
211
+ /** Deterministic 24-char uppercase hex token (Xcode UUID shape). */
212
+ function deriveUuid(seed, original) {
213
+ const hash = deriveHash(seed, "uuid", original);
214
+ const value = hash.slice(0, 24).toUpperCase();
215
+ return value === original ? hash.slice(24, 48).toUpperCase() : value;
216
+ }
217
+ function seededRandom(seed) {
218
+ const hash = deriveHash(seed);
219
+ let state = (parseInt(hash.slice(0, 8), 16) ^ parseInt(hash.slice(8, 16), 16)) >>> 0 || 2654435769;
220
+ return () => {
221
+ state = state + 1831565813 >>> 0;
222
+ let t = state;
223
+ t = Math.imul(t ^ t >>> 15, t | 1);
224
+ t ^= t + Math.imul(t ^ t >>> 7, t | 61);
225
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
226
+ };
227
+ }
228
+ function seededInt(seed, salt, min, max) {
229
+ const random = seededRandom(deriveHash(seed, salt));
230
+ return min + Math.floor(random() * (max - min + 1));
231
+ }
232
+ function escapeRegExp(value) {
233
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
234
+ }
235
+ /**
236
+ * Whole-word replacement. Word characters are ASCII identifier chars; CJK and other scripts
237
+ * match because their neighbors fall outside the class. Returns the replacement count.
238
+ */
239
+ function replaceWholeWords(text, words) {
240
+ let result = text;
241
+ let count = 0;
242
+ for (const key of Object.keys(words).sort((a, b) => b.length - a.length)) {
243
+ if (!key) continue;
244
+ const pattern = new RegExp(`(?<![A-Za-z0-9_$])${escapeRegExp(key)}(?![A-Za-z0-9_$])`, "g");
245
+ result = result.replace(pattern, () => {
246
+ count += 1;
247
+ return words[key];
248
+ });
249
+ }
250
+ return {
251
+ text: result,
252
+ count
253
+ };
254
+ }
255
+ function writeIfChanged(entry, content) {
256
+ if (readFileSync(entry.absolute).equals(Buffer.from(content, "utf8"))) return false;
257
+ writeFileSync(entry.absolute, content);
258
+ return true;
259
+ }
260
+ export { FRAMEWORKS, IMAGE_EXTENSIONS, RESOURCE_EXTENSIONS, TEXT_EXTENSIONS, basenameWithoutExtension, classifyLanguage, deriveHash, deriveToken, deriveUuid, escapeRegExp, extension, isGeneratedCode, isImageFile, isReferenceCarrier, isResourceFile, isTextFile, replaceWholeWords, seededInt, seededRandom, walkProject, writeIfChanged };
261
+
262
+ //# sourceMappingURL=common.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"common.js","names":[],"sources":["../src/common.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport { lstatSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nexport type Framework = 'flutter' | 'android-java' | 'swift' | 'react-native' | 'expo' | 'uni-app' | 'mygo';\nexport const FRAMEWORKS: readonly Framework[] = ['flutter', 'android-java', 'swift', 'react-native', 'expo', 'uni-app', 'mygo'];\nexport type Language = 'dart' | 'java' | 'kotlin' | 'swift' | 'javascript' | 'go';\n\nexport interface WalkEntry {\n /** Absolute filesystem path. */\n absolute: string;\n /** Project-relative path, '/'-separated. */\n relative: string;\n}\n\nexport interface WalkResult {\n files: WalkEntry[];\n skipped: number;\n}\n\n/** Uniform per-feature result shape returned by every multi-file transform module. */\nexport interface TransformOutcome {\n changed: number;\n changedPaths: string[];\n unsupported?: number;\n}\n\n/** Conservative generated/ignored directory names; the tool never writes outside the walked set. */\nconst SKIPPED_DIRECTORIES: Readonly<Record<string, true>> = {\n '.git': true, '.svn': true, '.hg': true, '.idea': true, '.vscode': true, '.factory-input': true,\n '.factory-cache': true, '.dart_tool': true, 'node_modules': true, 'Pods': true, '.gradle': true,\n '.build': true, 'build': true, 'dist': true, 'DerivedData': true, '.mygo': true, '.expo': true,\n 'unpackage': true, '.yarn': true, '.turbo': true, 'coverage': true, '.cargo': true,\n};\nconst SKIPPED_FILES: Readonly<Record<string, true>> = {\n 'Generated.xcconfig': true, 'flutter_export_environment.sh': true, '.DS_Store': true,\n};\n\nexport const IMAGE_EXTENSIONS: Readonly<Record<string, true>> = {\n png: true, jpg: true, jpeg: true, webp: true, gif: true, svg: true, ico: true, bmp: true, heic: true,\n};\nexport const RESOURCE_EXTENSIONS: Readonly<Record<string, true>> = {\n png: true, jpg: true, jpeg: true, webp: true, gif: true, svg: true, ico: true, bmp: true, heic: true,\n ttf: true, otf: true, woff: true, woff2: true, mp3: true, wav: true, m4a: true, aac: true, mp4: true,\n mov: true, m4v: true, json: true, plist: true, strings: true, xml: true, storyboard: true, xib: true,\n arb: true, po: true, lproj: true, car: true, dat: true, bin: true,\n};\nexport const TEXT_EXTENSIONS: Readonly<Record<string, true>> = {\n txt: true, md: true, json: true, yaml: true, yml: true, plist: true, strings: true, xml: true,\n html: true, htm: true, csv: true, tsv: true, po: true, arb: true, vue: true, ini: true, env: true,\n pbxproj: true, entitlements: true, css: true, scss: true, sass: true, less: true,\n};\nconst LANGUAGE_EXTENSIONS: Readonly<Record<string, Language>> = {\n dart: 'dart',\n java: 'java',\n kt: 'kotlin', kts: 'kotlin',\n swift: 'swift',\n js: 'javascript', jsx: 'javascript', ts: 'javascript', tsx: 'javascript', mjs: 'javascript', cjs: 'javascript',\n go: 'go',\n};\n\nexport function extension(relativePath: string): string {\n const base = relativePath.slice(relativePath.lastIndexOf('/') + 1);\n const dot = base.lastIndexOf('.');\n return dot < 1 ? '' : base.slice(dot + 1).toLowerCase();\n}\n\nexport function basenameWithoutExtension(relativePath: string): string {\n const base = relativePath.slice(relativePath.lastIndexOf('/') + 1);\n const dot = base.lastIndexOf('.');\n return dot < 1 ? base : base.slice(0, dot);\n}\n\nexport function isImageFile(relativePath: string): boolean {\n return extension(relativePath) in IMAGE_EXTENSIONS;\n}\n\nexport function isResourceFile(relativePath: string): boolean {\n return extension(relativePath) in RESOURCE_EXTENSIONS;\n}\n\nexport function isTextFile(relativePath: string): boolean {\n return extension(relativePath) in TEXT_EXTENSIONS;\n}\n\nexport function classifyLanguage(relativePath: string): Language | undefined {\n return LANGUAGE_EXTENSIONS[extension(relativePath)];\n}\n\n/** Text or code files that can carry path/name references worth rewriting during renames. */\nexport function isReferenceCarrier(relativePath: string): boolean {\n return isTextFile(relativePath) || classifyLanguage(relativePath) !== undefined;\n}\n\n/**\n * Code generated by build tooling (json_serializable, freezed, protobuf, …). Seed-generated\n * renames skip these files so generated serializers keep referencing the original names.\n */\nexport function isGeneratedCode(relativePath: string): boolean {\n return /(?:^|\\/)[^/]*\\.(?:g|freezed|pb|grpc)\\.dart$/.test(relativePath)\n || /(?:^|\\/)[^/]*\\.g\\.(?:java|kt|kts)$/.test(relativePath);\n}\n\n/** Walk owned project files deterministically, skipping generated/ignored paths and symlinks. */\nexport function walkProject(root: string): WalkResult {\n const files: WalkEntry[] = [];\n let skipped = 0;\n function visit(directory: string, prefix: string): void {\n for (const name of readdirSync(directory).sort()) {\n const absolute = join(directory, name);\n const relativePath = prefix ? `${prefix}/${name}` : name;\n const stat = lstatSync(absolute);\n if (stat.isSymbolicLink()) { skipped += 1; continue; }\n if (stat.isDirectory()) {\n if (name in SKIPPED_DIRECTORIES) { skipped += 1; continue; }\n visit(absolute, relativePath);\n } else if (stat.isFile()) {\n if (name in SKIPPED_FILES) { skipped += 1; continue; }\n files.push({ absolute, relative: relativePath });\n } else {\n skipped += 1;\n }\n }\n }\n const rootStat = lstatSync(root);\n if (!rootStat.isDirectory() || rootStat.isSymbolicLink()) throw new Error(`Project root is not a real directory: ${root}`);\n visit(root, '');\n return { files, skipped };\n}\n\n/** Deterministic hash of the joined parts. */\nexport function deriveHash(...parts: string[]): string {\n return createHash('sha256').update(parts.join('\\u0000')).digest('hex');\n}\n\n/** Deterministic short identifier token: 'obf' + 8 lowercase alphanumeric chars. */\nexport function deriveToken(seed: string, ...parts: string[]): string {\n const hash = deriveHash(seed, ...parts);\n let value = '';\n for (let index = 0; index < 8; index += 1) value += '0123456789abcdefghijklmnopqrstuvwxyz'[parseInt(hash.slice(index * 2, index * 2 + 2), 16) % 36]!;\n return `obf${value}`;\n}\n\n/** Deterministic 24-char uppercase hex token (Xcode UUID shape). */\nexport function deriveUuid(seed: string, original: string): string {\n const hash = deriveHash(seed, 'uuid', original);\n const value = hash.slice(0, 24).toUpperCase();\n return value === original ? hash.slice(24, 48).toUpperCase() : value;\n}\n\nexport function seededRandom(seed: string): () => number {\n const hash = deriveHash(seed);\n let state = (parseInt(hash.slice(0, 8), 16) ^ parseInt(hash.slice(8, 16), 16)) >>> 0 || 0x9e3779b9;\n return () => {\n state = (state + 0x6d2b79f5) >>> 0;\n let t = state;\n t = Math.imul(t ^ (t >>> 15), t | 1);\n t ^= t + Math.imul(t ^ (t >>> 7), t | 61);\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n };\n}\n\nexport function seededInt(seed: string, salt: string, min: number, max: number): number {\n const random = seededRandom(deriveHash(seed, salt));\n return min + Math.floor(random() * (max - min + 1));\n}\n\nexport function escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Whole-word replacement. Word characters are ASCII identifier chars; CJK and other scripts\n * match because their neighbors fall outside the class. Returns the replacement count.\n */\nexport function replaceWholeWords(text: string, words: Readonly<Record<string, string>>): { text: string; count: number } {\n let result = text;\n let count = 0;\n for (const key of Object.keys(words).sort((a, b) => b.length - a.length)) {\n if (!key) continue;\n const pattern = new RegExp(`(?<![A-Za-z0-9_$])${escapeRegExp(key)}(?![A-Za-z0-9_$])`, 'g');\n result = result.replace(pattern, () => { count += 1; return words[key]!; });\n }\n return { text: result, count };\n}\n\nexport function writeIfChanged(entry: WalkEntry, content: string): boolean {\n const previous = readFileSync(entry.absolute);\n if (previous.equals(Buffer.from(content, 'utf8'))) return false;\n writeFileSync(entry.absolute, content);\n return true;\n}\n"],"mappings":";;;;AAKA,MAAa,aAAmC;CAAC;CAAW;CAAgB;CAAS;CAAgB;CAAQ;CAAW;AAAM;;AAuB9H,MAAM,sBAAsD;CAC1D,QAAQ;CAAM,QAAQ;CAAM,OAAO;CAAM,SAAS;CAAM,WAAW;CAAM,kBAAkB;CAC3F,kBAAkB;CAAM,cAAc;CAAM,gBAAgB;CAAM,QAAQ;CAAM,WAAW;CAC3F,UAAU;CAAM,SAAS;CAAM,QAAQ;CAAM,eAAe;CAAM,SAAS;CAAM,SAAS;CAC1F,aAAa;CAAM,SAAS;CAAM,UAAU;CAAM,YAAY;CAAM,UAAU;AAChF;AACA,MAAM,gBAAgD;CACpD,sBAAsB;CAAM,iCAAiC;CAAM,aAAa;AAClF;AAEA,MAAa,mBAAmD;CAC9D,KAAK;CAAM,KAAK;CAAM,MAAM;CAAM,MAAM;CAAM,KAAK;CAAM,KAAK;CAAM,KAAK;CAAM,KAAK;CAAM,MAAM;AAClG;AACA,MAAa,sBAAsD;CACjE,KAAK;CAAM,KAAK;CAAM,MAAM;CAAM,MAAM;CAAM,KAAK;CAAM,KAAK;CAAM,KAAK;CAAM,KAAK;CAAM,MAAM;CAChG,KAAK;CAAM,KAAK;CAAM,MAAM;CAAM,OAAO;CAAM,KAAK;CAAM,KAAK;CAAM,KAAK;CAAM,KAAK;CAAM,KAAK;CAChG,KAAK;CAAM,KAAK;CAAM,MAAM;CAAM,OAAO;CAAM,SAAS;CAAM,KAAK;CAAM,YAAY;CAAM,KAAK;CAChG,KAAK;CAAM,IAAI;CAAM,OAAO;CAAM,KAAK;CAAM,KAAK;CAAM,KAAK;AAC/D;AACA,MAAa,kBAAkD;CAC7D,KAAK;CAAM,IAAI;CAAM,MAAM;CAAM,MAAM;CAAM,KAAK;CAAM,OAAO;CAAM,SAAS;CAAM,KAAK;CACzF,MAAM;CAAM,KAAK;CAAM,KAAK;CAAM,KAAK;CAAM,IAAI;CAAM,KAAK;CAAM,KAAK;CAAM,KAAK;CAAM,KAAK;CAC7F,SAAS;CAAM,cAAc;CAAM,KAAK;CAAM,MAAM;CAAM,MAAM;CAAM,MAAM;AAC9E;AACA,MAAM,sBAA0D;CAC9D,MAAM;CACN,MAAM;CACN,IAAI;CAAU,KAAK;CACnB,OAAO;CACP,IAAI;CAAc,KAAK;CAAc,IAAI;CAAc,KAAK;CAAc,KAAK;CAAc,KAAK;CAClG,IAAI;AACN;AAEA,SAAgB,UAAU,cAA8B;CACtD,MAAM,OAAO,aAAa,MAAM,aAAa,YAAY,GAAG,IAAI,CAAC;CACjE,MAAM,MAAM,KAAK,YAAY,GAAG;CAChC,OAAO,MAAM,IAAI,KAAK,KAAK,MAAM,MAAM,CAAC,CAAC,CAAC,YAAY;AACxD;AAEA,SAAgB,yBAAyB,cAA8B;CACrE,MAAM,OAAO,aAAa,MAAM,aAAa,YAAY,GAAG,IAAI,CAAC;CACjE,MAAM,MAAM,KAAK,YAAY,GAAG;CAChC,OAAO,MAAM,IAAI,OAAO,KAAK,MAAM,GAAG,GAAG;AAC3C;AAEA,SAAgB,YAAY,cAA+B;CACzD,OAAO,UAAU,YAAY,KAAK;AACpC;AAEA,SAAgB,eAAe,cAA+B;CAC5D,OAAO,UAAU,YAAY,KAAK;AACpC;AAEA,SAAgB,WAAW,cAA+B;CACxD,OAAO,UAAU,YAAY,KAAK;AACpC;AAEA,SAAgB,iBAAiB,cAA4C;CAC3E,OAAO,oBAAoB,UAAU,YAAY;AACnD;;AAGA,SAAgB,mBAAmB,cAA+B;CAChE,OAAO,WAAW,YAAY,KAAK,iBAAiB,YAAY,MAAM,KAAA;AACxE;;;;;AAMA,SAAgB,gBAAgB,cAA+B;CAC7D,OAAO,8CAA8C,KAAK,YAAY,KACjE,qCAAqC,KAAK,YAAY;AAC7D;;AAGA,SAAgB,YAAY,MAA0B;CACpD,MAAM,QAAqB,CAAC;CAC5B,IAAI,UAAU;CACd,SAAS,MAAM,WAAmB,QAAsB;EACtD,KAAK,MAAM,QAAQ,YAAY,SAAS,CAAC,CAAC,KAAK,GAAG;GAChD,MAAM,WAAW,KAAK,WAAW,IAAI;GACrC,MAAM,eAAe,SAAS,GAAG,OAAO,GAAG,SAAS;GACpD,MAAM,OAAO,UAAU,QAAQ;GAC/B,IAAI,KAAK,eAAe,GAAG;IAAE,WAAW;IAAG;GAAU;GACrD,IAAI,KAAK,YAAY,GAAG;IACtB,IAAI,QAAQ,qBAAqB;KAAE,WAAW;KAAG;IAAU;IAC3D,MAAM,UAAU,YAAY;GAC9B,OAAO,IAAI,KAAK,OAAO,GAAG;IACxB,IAAI,QAAQ,eAAe;KAAE,WAAW;KAAG;IAAU;IACrD,MAAM,KAAK;KAAE;KAAU,UAAU;IAAa,CAAC;GACjD,OACE,WAAW;EAEf;CACF;CACA,MAAM,WAAW,UAAU,IAAI;CAC/B,IAAI,CAAC,SAAS,YAAY,KAAK,SAAS,eAAe,GAAG,MAAM,IAAI,MAAM,yCAAyC,MAAM;CACzH,MAAM,MAAM,EAAE;CACd,OAAO;EAAE;EAAO;CAAQ;AAC1B;;AAGA,SAAgB,WAAW,GAAG,OAAyB;CACrD,OAAO,WAAW,QAAQ,CAAC,CAAC,OAAO,MAAM,KAAK,IAAQ,CAAC,CAAC,CAAC,OAAO,KAAK;AACvE;;AAGA,SAAgB,YAAY,MAAc,GAAG,OAAyB;CACpE,MAAM,OAAO,WAAW,MAAM,GAAG,KAAK;CACtC,IAAI,QAAQ;CACZ,KAAK,IAAI,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,uCAAuC,SAAS,KAAK,MAAM,QAAQ,GAAG,QAAQ,IAAI,CAAC,GAAG,EAAE,IAAI;CAChJ,OAAO,MAAM;AACf;;AAGA,SAAgB,WAAW,MAAc,UAA0B;CACjE,MAAM,OAAO,WAAW,MAAM,QAAQ,QAAQ;CAC9C,MAAM,QAAQ,KAAK,MAAM,GAAG,EAAE,CAAC,CAAC,YAAY;CAC5C,OAAO,UAAU,WAAW,KAAK,MAAM,IAAI,EAAE,CAAC,CAAC,YAAY,IAAI;AACjE;AAEA,SAAgB,aAAa,MAA4B;CACvD,MAAM,OAAO,WAAW,IAAI;CAC5B,IAAI,SAAS,SAAS,KAAK,MAAM,GAAG,CAAC,GAAG,EAAE,IAAI,SAAS,KAAK,MAAM,GAAG,EAAE,GAAG,EAAE,OAAO,KAAK;CACxF,aAAa;EACX,QAAS,QAAQ,eAAgB;EACjC,IAAI,IAAI;EACR,IAAI,KAAK,KAAK,IAAK,MAAM,IAAK,IAAI,CAAC;EACnC,KAAK,IAAI,KAAK,KAAK,IAAK,MAAM,GAAI,IAAI,EAAE;EACxC,SAAS,IAAK,MAAM,QAAS,KAAK;CACpC;AACF;AAEA,SAAgB,UAAU,MAAc,MAAc,KAAa,KAAqB;CACtF,MAAM,SAAS,aAAa,WAAW,MAAM,IAAI,CAAC;CAClD,OAAO,MAAM,KAAK,MAAM,OAAO,KAAK,MAAM,MAAM,EAAE;AACpD;AAEA,SAAgB,aAAa,OAAuB;CAClD,OAAO,MAAM,QAAQ,uBAAuB,MAAM;AACpD;;;;;AAMA,SAAgB,kBAAkB,MAAc,OAA0E;CACxH,IAAI,SAAS;CACb,IAAI,QAAQ;CACZ,KAAK,MAAM,OAAO,OAAO,KAAK,KAAK,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG;EACxE,IAAI,CAAC,KAAK;EACV,MAAM,UAAU,IAAI,OAAO,qBAAqB,aAAa,GAAG,EAAE,oBAAoB,GAAG;EACzF,SAAS,OAAO,QAAQ,eAAe;GAAE,SAAS;GAAG,OAAO,MAAM;EAAO,CAAC;CAC5E;CACA,OAAO;EAAE,MAAM;EAAQ;CAAM;AAC/B;AAEA,SAAgB,eAAe,OAAkB,SAA0B;CAEzE,IADiB,aAAa,MAAM,QACzB,CAAC,CAAC,OAAO,OAAO,KAAK,SAAS,MAAM,CAAC,GAAG,OAAO;CAC1D,cAAc,MAAM,UAAU,OAAO;CACrC,OAAO;AACT"}
@@ -0,0 +1,123 @@
1
+ import { z } from "zod";
2
+ // src/config.ts
3
+ const wordMap = z.record(z.string().min(1), z.string().min(1));
4
+ const hexColor = z.string().regex(/^#[0-9a-fA-F]{6}$/);
5
+ const CodeOptionsSchema = z.strictObject({
6
+ removeComments: z.boolean().optional(),
7
+ encryptStrings: z.strictObject({ excludes: z.array(z.string().min(1)).default([]) }).optional(),
8
+ renameIdentifiers: z.strictObject({
9
+ mappings: wordMap.default({}),
10
+ patterns: z.array(z.strictObject({
11
+ match: z.string().min(1),
12
+ replacement: z.string().min(1)
13
+ })).default([]),
14
+ /** Deterministically rename this many discovered declarations to seed-derived tokens. */
15
+ generated: z.number().int().min(0).max(64).optional()
16
+ }).optional(),
17
+ junkCode: z.strictObject({ lines: z.number().int().min(0).max(32).default(2) }).optional(),
18
+ keywords: wordMap.optional()
19
+ });
20
+ const ResourcesOptionsSchema = z.strictObject({
21
+ rename: z.strictObject({
22
+ mappings: wordMap.default({}),
23
+ /** Deterministically rename this many discovered resource stems to seed-derived tokens. */
24
+ generated: z.number().int().min(0).max(128).optional()
25
+ }).optional(),
26
+ hashPerturb: z.strictObject({ bytes: z.number().int().min(1).max(64) }).optional(),
27
+ images: z.strictObject({
28
+ recolor: z.array(z.strictObject({
29
+ from: hexColor,
30
+ to: hexColor,
31
+ tolerance: z.number().int().min(0).max(255).optional()
32
+ })).min(1),
33
+ recolorAll: z.union([
34
+ z.strictObject({
35
+ mode: z.literal("hue-shift"),
36
+ degrees: z.number().finite()
37
+ }),
38
+ z.strictObject({ mode: z.literal("invert") }),
39
+ z.strictObject({
40
+ mode: z.literal("channels"),
41
+ order: z.string().regex(/^[RGBA]{3,4}$/)
42
+ })
43
+ ]).optional()
44
+ }).optional()
45
+ });
46
+ const ProjectOptionsSchema = z.strictObject({
47
+ name: z.strictObject({
48
+ from: z.string().min(1),
49
+ to: z.string().min(1)
50
+ }).optional(),
51
+ directories: z.array(z.string().min(1)).default([]),
52
+ uuids: z.boolean().optional()
53
+ });
54
+ const ContentOptionsSchema = z.strictObject({
55
+ sensitiveWords: wordMap.optional(),
56
+ controlColors: z.record(hexColor, hexColor).optional(),
57
+ fontColors: z.record(hexColor, hexColor).optional()
58
+ });
59
+ const ObfuscatorConfigSchema = z.strictObject({
60
+ framework: z.enum([
61
+ "flutter",
62
+ "android-java",
63
+ "swift",
64
+ "react-native",
65
+ "expo",
66
+ "uni-app",
67
+ "mygo"
68
+ ]).optional(),
69
+ seed: z.string().min(1).optional(),
70
+ code: CodeOptionsSchema.optional(),
71
+ resources: ResourcesOptionsSchema.optional(),
72
+ project: ProjectOptionsSchema.optional(),
73
+ content: ContentOptionsSchema.optional()
74
+ }).superRefine((value, context) => {
75
+ if (!value.code && !value.resources && !value.project && !value.content) context.addIssue({
76
+ code: "custom",
77
+ path: [],
78
+ message: "At least one of code, resources, project or content must be configured"
79
+ });
80
+ const rename = value.code?.renameIdentifiers;
81
+ if (rename && !Object.keys(rename.mappings).length && !rename.patterns.length && !rename.generated) context.addIssue({
82
+ code: "custom",
83
+ path: ["code", "renameIdentifiers"],
84
+ message: "renameIdentifiers needs at least one mapping, pattern or generated count"
85
+ });
86
+ const resourceRename = value.resources?.rename;
87
+ if (resourceRename && !Object.keys(resourceRename.mappings).length && !resourceRename.generated) context.addIssue({
88
+ code: "custom",
89
+ path: ["resources", "rename"],
90
+ message: "rename needs at least one mapping or a generated count"
91
+ });
92
+ for (const [index, pattern] of (rename?.patterns ?? []).entries()) try {
93
+ new RegExp(pattern.match);
94
+ } catch {
95
+ context.addIssue({
96
+ code: "custom",
97
+ path: [
98
+ "code",
99
+ "renameIdentifiers",
100
+ "patterns",
101
+ index
102
+ ],
103
+ message: `Pattern must compile as a regular expression: ${pattern.match}`
104
+ });
105
+ }
106
+ for (const [index, exclude] of (value.code?.encryptStrings?.excludes ?? []).entries()) try {
107
+ new RegExp(exclude);
108
+ } catch {
109
+ context.addIssue({
110
+ code: "custom",
111
+ path: [
112
+ "code",
113
+ "encryptStrings",
114
+ "excludes",
115
+ index
116
+ ],
117
+ message: `Exclude pattern must compile as a regular expression: ${exclude}`
118
+ });
119
+ }
120
+ });
121
+ export { ObfuscatorConfigSchema };
122
+
123
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","names":[],"sources":["../src/config.ts"],"sourcesContent":["import { z } from 'zod';\nimport type { Framework } from './common.js';\n\nconst wordMap = z.record(z.string().min(1), z.string().min(1));\nconst hexColor = z.string().regex(/^#[0-9a-fA-F]{6}$/);\n\nconst CodeOptionsSchema = z.strictObject({\n removeComments: z.boolean().optional(),\n encryptStrings: z.strictObject({ excludes: z.array(z.string().min(1)).default([]) }).optional(),\n renameIdentifiers: z.strictObject({\n mappings: wordMap.default({}),\n patterns: z.array(z.strictObject({ match: z.string().min(1), replacement: z.string().min(1) })).default([]),\n /** Deterministically rename this many discovered declarations to seed-derived tokens. */\n generated: z.number().int().min(0).max(64).optional(),\n }).optional(),\n junkCode: z.strictObject({ lines: z.number().int().min(0).max(32).default(2) }).optional(),\n keywords: wordMap.optional(),\n});\n\nconst ResourcesOptionsSchema = z.strictObject({\n rename: z.strictObject({\n mappings: wordMap.default({}),\n /** Deterministically rename this many discovered resource stems to seed-derived tokens. */\n generated: z.number().int().min(0).max(128).optional(),\n }).optional(),\n hashPerturb: z.strictObject({ bytes: z.number().int().min(1).max(64) }).optional(),\n images: z.strictObject({\n recolor: z.array(z.strictObject({ from: hexColor, to: hexColor, tolerance: z.number().int().min(0).max(255).optional() })).min(1),\n recolorAll: z.union([\n z.strictObject({ mode: z.literal('hue-shift'), degrees: z.number().finite() }),\n z.strictObject({ mode: z.literal('invert') }),\n z.strictObject({ mode: z.literal('channels'), order: z.string().regex(/^[RGBA]{3,4}$/) }),\n ]).optional(),\n }).optional(),\n});\n\nconst ProjectOptionsSchema = z.strictObject({\n name: z.strictObject({ from: z.string().min(1), to: z.string().min(1) }).optional(),\n directories: z.array(z.string().min(1)).default([]),\n uuids: z.boolean().optional(),\n});\n\nconst ContentOptionsSchema = z.strictObject({\n sensitiveWords: wordMap.optional(),\n controlColors: z.record(hexColor, hexColor).optional(),\n fontColors: z.record(hexColor, hexColor).optional(),\n});\n\nexport const ObfuscatorConfigSchema = z.strictObject({\n framework: z.enum(['flutter', 'android-java', 'swift', 'react-native', 'expo', 'uni-app', 'mygo']).optional(),\n seed: z.string().min(1).optional(),\n code: CodeOptionsSchema.optional(),\n resources: ResourcesOptionsSchema.optional(),\n project: ProjectOptionsSchema.optional(),\n content: ContentOptionsSchema.optional(),\n}).superRefine((value, context) => {\n if (!value.code && !value.resources && !value.project && !value.content) {\n context.addIssue({ code: 'custom', path: [], message: 'At least one of code, resources, project or content must be configured' });\n }\n const rename = value.code?.renameIdentifiers;\n if (rename && !Object.keys(rename.mappings).length && !rename.patterns.length && !rename.generated) {\n context.addIssue({ code: 'custom', path: ['code', 'renameIdentifiers'], message: 'renameIdentifiers needs at least one mapping, pattern or generated count' });\n }\n const resourceRename = value.resources?.rename;\n if (resourceRename && !Object.keys(resourceRename.mappings).length && !resourceRename.generated) {\n context.addIssue({ code: 'custom', path: ['resources', 'rename'], message: 'rename needs at least one mapping or a generated count' });\n }\n for (const [index, pattern] of (rename?.patterns ?? []).entries()) {\n try { new RegExp(pattern.match); } catch { context.addIssue({ code: 'custom', path: ['code', 'renameIdentifiers', 'patterns', index], message: `Pattern must compile as a regular expression: ${pattern.match}` }); }\n }\n for (const [index, exclude] of (value.code?.encryptStrings?.excludes ?? []).entries()) {\n try { new RegExp(exclude); } catch { context.addIssue({ code: 'custom', path: ['code', 'encryptStrings', 'excludes', index], message: `Exclude pattern must compile as a regular expression: ${exclude}` }); }\n }\n});\nexport type ObfuscatorConfig = z.infer<typeof ObfuscatorConfigSchema>;\nexport type CodeOptions = NonNullable<ObfuscatorConfig['code']>;\nexport type ResourcesOptions = NonNullable<ObfuscatorConfig['resources']>;\nexport type ProjectOptions = NonNullable<ObfuscatorConfig['project']>;\nexport type ContentOptions = NonNullable<ObfuscatorConfig['content']>;\nexport type FrameworkOption = NonNullable<ObfuscatorConfig['framework']> | Framework;\n"],"mappings":"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"}
@@ -0,0 +1,89 @@
1
+ import { isReferenceCarrier, replaceWholeWords, writeIfChanged } from "./common.js";
2
+ import { readFileSync } from "node:fs";
3
+ // src/content.ts
4
+ /**
5
+ * Deterministic content-level scrubbing across text and code files: whole-word
6
+ * sensitive-word replacement and exact hex-color remapping. Binary resource payloads
7
+ * are never touched here; those belong to the resource transforms.
8
+ */
9
+ /**
10
+ * Replace every sensitive word (longest key first) with its configured stand-in.
11
+ * Applies inside text files, code and comments; binary payloads are untouched.
12
+ */
13
+ function filterSensitiveWords(files, words) {
14
+ const entries = Object.entries(words).filter(([key, value]) => key.length > 0 && value.length > 0).sort((a, b) => b[0].length - a[0].length);
15
+ if (!entries.length) return {
16
+ changed: 0,
17
+ changedPaths: []
18
+ };
19
+ const mapping = {};
20
+ for (const [key, value] of entries) mapping[key] = value;
21
+ const changedPaths = [];
22
+ let changed = 0;
23
+ for (const entry of files) {
24
+ if (!isReferenceCarrier(entry.relative)) continue;
25
+ const before = readFileSync(entry.absolute, "utf8");
26
+ const result = replaceWholeWords(before, mapping);
27
+ if (result.count === 0) continue;
28
+ if (writeIfChanged(entry, result.text)) {
29
+ changed += 1;
30
+ changedPaths.push(entry.relative);
31
+ }
32
+ }
33
+ return {
34
+ changed,
35
+ changedPaths
36
+ };
37
+ }
38
+ const HEX_COLOR = /#([0-9A-Fa-f]{6})/g;
39
+ /** Normalize map keys to uppercase so '#aabbcc' config keys match '#AABBCC' literals. */
40
+ function normalized(map) {
41
+ if (!map) return void 0;
42
+ const result = {};
43
+ for (const [key, value] of Object.entries(map)) result[key.toUpperCase()] = value;
44
+ return result;
45
+ }
46
+ function lookup(maps, hex) {
47
+ for (const map of maps) {
48
+ const value = map?.[`#${hex.toUpperCase()}`];
49
+ if (value !== void 0) return value;
50
+ }
51
+ }
52
+ /**
53
+ * Remap exact six-digit hex color literals in text/code files. Control colors are
54
+ * consulted before font colors; unmatched literals stay unchanged. Each literal is
55
+ * replaced at most once, so mapped values never cascade into further matches.
56
+ */
57
+ function recolorSourceColors(files, controlColors, fontColors) {
58
+ if (!controlColors && !fontColors) return {
59
+ changed: 0,
60
+ changedPaths: []
61
+ };
62
+ const control = normalized(controlColors);
63
+ const font = normalized(fontColors);
64
+ const changedPaths = [];
65
+ let changed = 0;
66
+ for (const entry of files) {
67
+ if (!isReferenceCarrier(entry.relative)) continue;
68
+ const before = readFileSync(entry.absolute, "utf8");
69
+ let matched = false;
70
+ const next = before.replace(HEX_COLOR, (literal, hex) => {
71
+ const mapped = lookup([control, font], hex);
72
+ if (mapped === void 0) return literal;
73
+ matched = true;
74
+ return `#${mapped.replace(/^#/, "").toLowerCase()}`;
75
+ });
76
+ if (!matched) continue;
77
+ if (writeIfChanged(entry, next)) {
78
+ changed += 1;
79
+ changedPaths.push(entry.relative);
80
+ }
81
+ }
82
+ return {
83
+ changed,
84
+ changedPaths
85
+ };
86
+ }
87
+ export { filterSensitiveWords, recolorSourceColors };
88
+
89
+ //# sourceMappingURL=content.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content.js","names":[],"sources":["../src/content.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport type { TransformOutcome, WalkEntry } from './common.js';\nimport { isReferenceCarrier, replaceWholeWords, writeIfChanged } from './common.js';\n\n/**\n * Deterministic content-level scrubbing across text and code files: whole-word\n * sensitive-word replacement and exact hex-color remapping. Binary resource payloads\n * are never touched here; those belong to the resource transforms.\n */\n\n/**\n * Replace every sensitive word (longest key first) with its configured stand-in.\n * Applies inside text files, code and comments; binary payloads are untouched.\n */\nexport function filterSensitiveWords(files: WalkEntry[], words: Readonly<Record<string, string>>): TransformOutcome {\n const entries = Object.entries(words).filter(([key, value]) => key.length > 0 && value.length > 0).sort((a, b) => b[0].length - a[0].length);\n if (!entries.length) return { changed: 0, changedPaths: [] };\n const mapping: Record<string, string> = {};\n for (const [key, value] of entries) mapping[key] = value;\n const changedPaths: string[] = [];\n let changed = 0;\n for (const entry of files) {\n if (!isReferenceCarrier(entry.relative)) continue;\n const before = readFileSync(entry.absolute, 'utf8');\n const result = replaceWholeWords(before, mapping);\n if (result.count === 0) continue;\n if (writeIfChanged(entry, result.text)) {\n changed += 1;\n changedPaths.push(entry.relative);\n }\n }\n return { changed, changedPaths };\n}\n\nconst HEX_COLOR = /#([0-9A-Fa-f]{6})/g;\n\n/** Normalize map keys to uppercase so '#aabbcc' config keys match '#AABBCC' literals. */\nfunction normalized(map: Readonly<Record<string, string>> | undefined): Record<string, string> | undefined {\n if (!map) return undefined;\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(map)) result[key.toUpperCase()] = value;\n return result;\n}\n\nfunction lookup(maps: ReadonlyArray<Readonly<Record<string, string>> | undefined>, hex: string): string | undefined {\n for (const map of maps) {\n const value = map?.[`#${hex.toUpperCase()}`];\n if (value !== undefined) return value;\n }\n return undefined;\n}\n\n/**\n * Remap exact six-digit hex color literals in text/code files. Control colors are\n * consulted before font colors; unmatched literals stay unchanged. Each literal is\n * replaced at most once, so mapped values never cascade into further matches.\n */\nexport function recolorSourceColors(\n files: WalkEntry[],\n controlColors: Readonly<Record<string, string>> | undefined,\n fontColors: Readonly<Record<string, string>> | undefined,\n): TransformOutcome {\n if (!controlColors && !fontColors) return { changed: 0, changedPaths: [] };\n const control = normalized(controlColors);\n const font = normalized(fontColors);\n const changedPaths: string[] = [];\n let changed = 0;\n for (const entry of files) {\n if (!isReferenceCarrier(entry.relative)) continue;\n const before = readFileSync(entry.absolute, 'utf8');\n let matched = false;\n const next = before.replace(HEX_COLOR, (literal, hex: string) => {\n const mapped = lookup([control, font], hex);\n if (mapped === undefined) return literal;\n matched = true;\n return `#${mapped.replace(/^#/, '').toLowerCase()}`;\n });\n if (!matched) continue;\n if (writeIfChanged(entry, next)) {\n changed += 1;\n changedPaths.push(entry.relative);\n }\n }\n return { changed, changedPaths };\n}\n"],"mappings":";;;;;;;;;;;;AAcA,SAAgB,qBAAqB,OAAoB,OAA2D;CAClH,MAAM,UAAU,OAAO,QAAQ,KAAK,CAAC,CAAC,QAAQ,CAAC,KAAK,WAAW,IAAI,SAAS,KAAK,MAAM,SAAS,CAAC,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,MAAM;CAC3I,IAAI,CAAC,QAAQ,QAAQ,OAAO;EAAE,SAAS;EAAG,cAAc,CAAC;CAAE;CAC3D,MAAM,UAAkC,CAAC;CACzC,KAAK,MAAM,CAAC,KAAK,UAAU,SAAS,QAAQ,OAAO;CACnD,MAAM,eAAyB,CAAC;CAChC,IAAI,UAAU;CACd,KAAK,MAAM,SAAS,OAAO;EACzB,IAAI,CAAC,mBAAmB,MAAM,QAAQ,GAAG;EACzC,MAAM,SAAS,aAAa,MAAM,UAAU,MAAM;EAClD,MAAM,SAAS,kBAAkB,QAAQ,OAAO;EAChD,IAAI,OAAO,UAAU,GAAG;EACxB,IAAI,eAAe,OAAO,OAAO,IAAI,GAAG;GACtC,WAAW;GACX,aAAa,KAAK,MAAM,QAAQ;EAClC;CACF;CACA,OAAO;EAAE;EAAS;CAAa;AACjC;AAEA,MAAM,YAAY;;AAGlB,SAAS,WAAW,KAAuF;CACzG,IAAI,CAAC,KAAK,OAAO,KAAA;CACjB,MAAM,SAAiC,CAAC;CACxC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,GAAG,GAAG,OAAO,IAAI,YAAY,KAAK;CAC5E,OAAO;AACT;AAEA,SAAS,OAAO,MAAmE,KAAiC;CAClH,KAAK,MAAM,OAAO,MAAM;EACtB,MAAM,QAAQ,MAAM,IAAI,IAAI,YAAY;EACxC,IAAI,UAAU,KAAA,GAAW,OAAO;CAClC;AAEF;;;;;;AAOA,SAAgB,oBACd,OACA,eACA,YACkB;CAClB,IAAI,CAAC,iBAAiB,CAAC,YAAY,OAAO;EAAE,SAAS;EAAG,cAAc,CAAC;CAAE;CACzE,MAAM,UAAU,WAAW,aAAa;CACxC,MAAM,OAAO,WAAW,UAAU;CAClC,MAAM,eAAyB,CAAC;CAChC,IAAI,UAAU;CACd,KAAK,MAAM,SAAS,OAAO;EACzB,IAAI,CAAC,mBAAmB,MAAM,QAAQ,GAAG;EACzC,MAAM,SAAS,aAAa,MAAM,UAAU,MAAM;EAClD,IAAI,UAAU;EACd,MAAM,OAAO,OAAO,QAAQ,YAAY,SAAS,QAAgB;GAC/D,MAAM,SAAS,OAAO,CAAC,SAAS,IAAI,GAAG,GAAG;GAC1C,IAAI,WAAW,KAAA,GAAW,OAAO;GACjC,UAAU;GACV,OAAO,IAAI,OAAO,QAAQ,MAAM,EAAE,CAAC,CAAC,YAAY;EAClD,CAAC;EACD,IAAI,CAAC,SAAS;EACd,IAAI,eAAe,OAAO,IAAI,GAAG;GAC/B,WAAW;GACX,aAAa,KAAK,MAAM,QAAQ;EAClC;CACF;CACA,OAAO;EAAE;EAAS;CAAa;AACjC"}