rebrand-strings.ts 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. #!/usr/bin/env tsx
  2. /**
  3. * scripts/rebrand-strings.ts
  4. *
  5. * Phase 3 of BACKEND_FORK_BRIEF.md — mechanical case-sensitive rebrand sweep
  6. * across the vendored Studio fork. Run before the visual-restyle phases.
  7. *
  8. * Usage:
  9. * pnpm tsx scripts/rebrand-strings.ts # default target: apps/studio
  10. * pnpm tsx scripts/rebrand-strings.ts apps/studio # explicit target
  11. * pnpm tsx scripts/rebrand-strings.ts --dry-run # report counts, no writes
  12. *
  13. * Replacements (case-sensitive):
  14. * Supabase -> Briven
  15. * supabase -> briven
  16. * SUPABASE -> BRIVEN
  17. * SUPA_ -> BRVN_
  18. * supa_ -> brvn_
  19. *
  20. * Protected tokens (NOT rewritten — would break runtime):
  21. * - @supabase/... (npm scope; published packages we cannot rename)
  22. * - @supabase-labs/... (same)
  23. * - supabase.com / .io / .co URLs and subdomains (Phase 4 handles via CSP/link audit)
  24. * - .supabase. fragments inside hostnames (e.g. db.fqfdjxabc.supabase.co)
  25. *
  26. * Excluded paths:
  27. * - node_modules/, .next/, .turbo/, dist/, build/, coverage/
  28. * - public/ (Phase 4: brand-asset rebrand)
  29. * - lock files (pnpm-lock.yaml, package-lock.json, yarn.lock)
  30. * - binary files (by extension)
  31. */
  32. import { readFileSync, writeFileSync, readdirSync } from 'node:fs';
  33. import { join, extname, relative } from 'node:path';
  34. const args = process.argv.slice(2);
  35. const dryRun = args.includes('--dry-run');
  36. const targets = args.filter((a) => !a.startsWith('--'));
  37. const rootTargets = targets.length > 0 ? targets : ['apps/studio'];
  38. const EXCLUDED_DIRS = new Set([
  39. 'node_modules',
  40. '.next',
  41. '.turbo',
  42. '.tsup',
  43. 'dist',
  44. 'build',
  45. 'coverage',
  46. '.git',
  47. 'public',
  48. ]);
  49. const BINARY_EXTENSIONS = new Set([
  50. '.png',
  51. '.jpg',
  52. '.jpeg',
  53. '.gif',
  54. '.svg',
  55. '.ico',
  56. '.webp',
  57. '.avif',
  58. '.woff',
  59. '.woff2',
  60. '.ttf',
  61. '.otf',
  62. '.eot',
  63. '.mp4',
  64. '.webm',
  65. '.mp3',
  66. '.wav',
  67. '.ogg',
  68. '.pdf',
  69. '.zip',
  70. '.gz',
  71. '.tar',
  72. '.7z',
  73. '.wasm',
  74. '.node',
  75. '.so',
  76. '.dylib',
  77. '.dll',
  78. '.lock',
  79. '.snap',
  80. ]);
  81. const SKIP_FILENAMES = new Set(['pnpm-lock.yaml', 'package-lock.json', 'yarn.lock', 'bun.lockb']);
  82. const PROTECT_PATTERNS: RegExp[] = [
  83. /@supabase-labs\/[a-z0-9-]+/g,
  84. /@supabase\/[a-z0-9-]+/g,
  85. /\b[a-z0-9-]+\.supabase\.(?:co|com|io)\b[^\s'"\)<>]*/g,
  86. /\bsupabase\.com\b[^\s'"\)<>]*/g,
  87. /\bsupabase\.io\b[^\s'"\)<>]*/g,
  88. /\bsupabase\.co\b[^\s'"\)<>]*/g,
  89. /\bgithub\.com\/supabase[a-z0-9_-]*\/[^\s'"\)<>]+/g,
  90. /\bgithub\.com\/orgs\/supabase[^\s'"\)<>]*/g,
  91. /\bsupabase_(?:admin|auth_admin|storage_admin|functions_admin|realtime_admin|replication_admin|read_only_user)\b/g,
  92. /\bsupabase_(?:functions|migrations)\b/g,
  93. /\bclient_connections_supabase_[a-z_]+\b/g,
  94. ];
  95. const REPLACEMENTS: Array<[RegExp, string]> = [
  96. [/Supabase/g, 'Briven'],
  97. [/SUPABASE/g, 'BRIVEN'],
  98. [/SUPA_/g, 'BRVN_'],
  99. [/supa_/g, 'brvn_'],
  100. [/supabase/g, 'briven'],
  101. ];
  102. interface FileResult {
  103. path: string;
  104. matches: number;
  105. protected: number;
  106. }
  107. function walk(dir: string, out: string[]): void {
  108. let entries;
  109. try {
  110. entries = readdirSync(dir, { withFileTypes: true });
  111. } catch {
  112. return;
  113. }
  114. for (const e of entries) {
  115. if (EXCLUDED_DIRS.has(e.name)) continue;
  116. const p = join(dir, e.name);
  117. if (e.isDirectory()) {
  118. walk(p, out);
  119. } else if (e.isFile()) {
  120. if (SKIP_FILENAMES.has(e.name)) continue;
  121. if (BINARY_EXTENSIONS.has(extname(e.name).toLowerCase())) continue;
  122. out.push(p);
  123. }
  124. }
  125. }
  126. function rebrand(content: string): { out: string; matches: number; protectedCount: number } {
  127. const placeholders: string[] = [];
  128. let working = content;
  129. let protectedCount = 0;
  130. for (const pat of PROTECT_PATTERNS) {
  131. working = working.replace(pat, (m) => {
  132. const idx = placeholders.length;
  133. placeholders.push(m);
  134. protectedCount++;
  135. return `PROTECT${idx}`;
  136. });
  137. }
  138. let matches = 0;
  139. for (const [pat, repl] of REPLACEMENTS) {
  140. working = working.replace(pat, () => {
  141. matches++;
  142. return repl;
  143. });
  144. }
  145. working = working.replace(/PROTECT(\d+)/g, (_m, idx) => placeholders[Number(idx)]);
  146. return { out: working, matches, protectedCount };
  147. }
  148. function main(): void {
  149. const repoRoot = process.cwd();
  150. const files: string[] = [];
  151. for (const t of rootTargets) {
  152. walk(t, files);
  153. }
  154. const results: FileResult[] = [];
  155. let totalMatches = 0;
  156. let totalProtected = 0;
  157. let filesChanged = 0;
  158. for (const f of files) {
  159. let raw: string;
  160. try {
  161. raw = readFileSync(f, 'utf8');
  162. } catch {
  163. continue;
  164. }
  165. const { out, matches, protectedCount } = rebrand(raw);
  166. totalProtected += protectedCount;
  167. if (matches === 0) continue;
  168. results.push({ path: relative(repoRoot, f), matches, protected: protectedCount });
  169. totalMatches += matches;
  170. filesChanged++;
  171. if (!dryRun) {
  172. writeFileSync(f, out, 'utf8');
  173. }
  174. }
  175. results.sort((a, b) => b.matches - a.matches);
  176. const topN = 25;
  177. for (const r of results.slice(0, topN)) {
  178. process.stdout.write(`${String(r.matches).padStart(5)} ${r.path}\n`);
  179. }
  180. if (results.length > topN) {
  181. process.stdout.write(` ... ${results.length - topN} more files\n`);
  182. }
  183. process.stdout.write(
  184. `\n${dryRun ? '[dry-run] ' : ''}` +
  185. `targets=${rootTargets.join(',')} ` +
  186. `scanned=${files.length} ` +
  187. `changed=${filesChanged} ` +
  188. `replacements=${totalMatches} ` +
  189. `protected=${totalProtected}\n`,
  190. );
  191. }
  192. main();