| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211 |
- #!/usr/bin/env tsx
- /**
- * scripts/rebrand-strings.ts
- *
- * Phase 3 of BACKEND_FORK_BRIEF.md — mechanical case-sensitive rebrand sweep
- * across the vendored Studio fork. Run before the visual-restyle phases.
- *
- * Usage:
- * pnpm tsx scripts/rebrand-strings.ts # default target: apps/studio
- * pnpm tsx scripts/rebrand-strings.ts apps/studio # explicit target
- * pnpm tsx scripts/rebrand-strings.ts --dry-run # report counts, no writes
- *
- * Replacements (case-sensitive):
- * Supabase -> Briven
- * supabase -> briven
- * SUPABASE -> BRIVEN
- * SUPA_ -> BRVN_
- * supa_ -> brvn_
- *
- * Protected tokens (NOT rewritten — would break runtime):
- * - @supabase/... (npm scope; published packages we cannot rename)
- * - @supabase-labs/... (same)
- * - supabase.com / .io / .co URLs and subdomains (Phase 4 handles via CSP/link audit)
- * - .supabase. fragments inside hostnames (e.g. db.fqfdjxabc.supabase.co)
- *
- * Excluded paths:
- * - node_modules/, .next/, .turbo/, dist/, build/, coverage/
- * - public/ (Phase 4: brand-asset rebrand)
- * - lock files (pnpm-lock.yaml, package-lock.json, yarn.lock)
- * - binary files (by extension)
- */
- import { readFileSync, writeFileSync, readdirSync } from 'node:fs';
- import { join, extname, relative } from 'node:path';
- const args = process.argv.slice(2);
- const dryRun = args.includes('--dry-run');
- const targets = args.filter((a) => !a.startsWith('--'));
- const rootTargets = targets.length > 0 ? targets : ['apps/studio'];
- const EXCLUDED_DIRS = new Set([
- 'node_modules',
- '.next',
- '.turbo',
- '.tsup',
- 'dist',
- 'build',
- 'coverage',
- '.git',
- 'public',
- ]);
- const BINARY_EXTENSIONS = new Set([
- '.png',
- '.jpg',
- '.jpeg',
- '.gif',
- '.svg',
- '.ico',
- '.webp',
- '.avif',
- '.woff',
- '.woff2',
- '.ttf',
- '.otf',
- '.eot',
- '.mp4',
- '.webm',
- '.mp3',
- '.wav',
- '.ogg',
- '.pdf',
- '.zip',
- '.gz',
- '.tar',
- '.7z',
- '.wasm',
- '.node',
- '.so',
- '.dylib',
- '.dll',
- '.lock',
- '.snap',
- ]);
- const SKIP_FILENAMES = new Set(['pnpm-lock.yaml', 'package-lock.json', 'yarn.lock', 'bun.lockb']);
- const PROTECT_PATTERNS: RegExp[] = [
- /@supabase-labs\/[a-z0-9-]+/g,
- /@supabase\/[a-z0-9-]+/g,
- /\b[a-z0-9-]+\.supabase\.(?:co|com|io)\b[^\s'"\)<>]*/g,
- /\bsupabase\.com\b[^\s'"\)<>]*/g,
- /\bsupabase\.io\b[^\s'"\)<>]*/g,
- /\bsupabase\.co\b[^\s'"\)<>]*/g,
- /\bgithub\.com\/supabase[a-z0-9_-]*\/[^\s'"\)<>]+/g,
- /\bgithub\.com\/orgs\/supabase[^\s'"\)<>]*/g,
- /\bsupabase_(?:admin|auth_admin|storage_admin|functions_admin|realtime_admin|replication_admin|read_only_user)\b/g,
- /\bsupabase_(?:functions|migrations)\b/g,
- /\bclient_connections_supabase_[a-z_]+\b/g,
- ];
- const REPLACEMENTS: Array<[RegExp, string]> = [
- [/Supabase/g, 'Briven'],
- [/SUPABASE/g, 'BRIVEN'],
- [/SUPA_/g, 'BRVN_'],
- [/supa_/g, 'brvn_'],
- [/supabase/g, 'briven'],
- ];
- interface FileResult {
- path: string;
- matches: number;
- protected: number;
- }
- function walk(dir: string, out: string[]): void {
- let entries;
- try {
- entries = readdirSync(dir, { withFileTypes: true });
- } catch {
- return;
- }
- for (const e of entries) {
- if (EXCLUDED_DIRS.has(e.name)) continue;
- const p = join(dir, e.name);
- if (e.isDirectory()) {
- walk(p, out);
- } else if (e.isFile()) {
- if (SKIP_FILENAMES.has(e.name)) continue;
- if (BINARY_EXTENSIONS.has(extname(e.name).toLowerCase())) continue;
- out.push(p);
- }
- }
- }
- function rebrand(content: string): { out: string; matches: number; protectedCount: number } {
- const placeholders: string[] = [];
- let working = content;
- let protectedCount = 0;
- for (const pat of PROTECT_PATTERNS) {
- working = working.replace(pat, (m) => {
- const idx = placeholders.length;
- placeholders.push(m);
- protectedCount++;
- return `PROTECT${idx}`;
- });
- }
- let matches = 0;
- for (const [pat, repl] of REPLACEMENTS) {
- working = working.replace(pat, () => {
- matches++;
- return repl;
- });
- }
- working = working.replace(/PROTECT(\d+)/g, (_m, idx) => placeholders[Number(idx)]);
- return { out: working, matches, protectedCount };
- }
- function main(): void {
- const repoRoot = process.cwd();
- const files: string[] = [];
- for (const t of rootTargets) {
- walk(t, files);
- }
- const results: FileResult[] = [];
- let totalMatches = 0;
- let totalProtected = 0;
- let filesChanged = 0;
- for (const f of files) {
- let raw: string;
- try {
- raw = readFileSync(f, 'utf8');
- } catch {
- continue;
- }
- const { out, matches, protectedCount } = rebrand(raw);
- totalProtected += protectedCount;
- if (matches === 0) continue;
- results.push({ path: relative(repoRoot, f), matches, protected: protectedCount });
- totalMatches += matches;
- filesChanged++;
- if (!dryRun) {
- writeFileSync(f, out, 'utf8');
- }
- }
- results.sort((a, b) => b.matches - a.matches);
- const topN = 25;
- for (const r of results.slice(0, topN)) {
- process.stdout.write(`${String(r.matches).padStart(5)} ${r.path}\n`);
- }
- if (results.length > topN) {
- process.stdout.write(` ... ${results.length - topN} more files\n`);
- }
- process.stdout.write(
- `\n${dryRun ? '[dry-run] ' : ''}` +
- `targets=${rootTargets.join(',')} ` +
- `scanned=${files.length} ` +
- `changed=${filesChanged} ` +
- `replacements=${totalMatches} ` +
- `protected=${totalProtected}\n`,
- );
- }
- main();
|