mirror of https://github.com/docusealco/docuseal
Adds a deterministic rebrand sweep so each upstream sync is a scripted
transformation rather than a manual sweep:
- bin/rebrand-sync — idempotent Ruby script that rewrites DocuSeal →
WaboSign tokens across the tree (Ruby module, AATL cert name, DB
names, Docker user, registry/repo URLs, DOM-ID/localStorage prefix,
daisyUI theme name, hardcoded UI strings). Sentinel-protects AGPL
§7(b) attribution phrases, SDK custom-element identifiers, the
@docuseal/* npm packages, and the docusealco/{fields-detection,
pdfium-binaries,turbo} binary URLs. Deny-lists NOTICE, LICENSE_*,
README, the AGPL attribution partials, calculator.js's DocuSeal LLC
copyright, lib/wabosign.rb's UPSTREAM constants, and the
docuseal_aatl migration that searches by the legacy name.
- bin/rebrand-check — fails CI when an unintended DocuSeal reference
survives in the tree. Wired in as the new `Rebrand check` job in
.github/workflows/ci.yml.
- REBRANDING.md gains a "Sync workflow" section documenting the
per-sync steps, rerere setup, and how to keep the two scripts'
allow/preserve lists in sync.
- .gitattributes marks Gemfile.lock and yarn.lock as -merge — they get
regenerated post-merge rather than diffed.
- Latent rebrand leftovers fixed: public/service-worker.js no longer
logs "DocuSeal App installed/activated"; .dockerignore and
.gitignore now ignore /wabosign (the actual runtime data dir mount)
rather than the stale /docuseal path.
Strategy detail lives at .claude/plans/come-up-with-a-foamy-flask.md.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
pull/687/head
parent
ee5a295e57
commit
6b652f8aa5
@ -1 +1,6 @@
|
||||
*.html linguist-detectable=false
|
||||
|
||||
# Upstream-merge ergonomics. Lockfiles are regenerated post-merge rather
|
||||
# than merged line-by-line — see REBRANDING.md "Sync workflow".
|
||||
Gemfile.lock -merge
|
||||
yarn.lock -merge
|
||||
|
||||
@ -0,0 +1,142 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
#
|
||||
# bin/rebrand-check — fails (exit 1) if any unintended "docuseal" /
|
||||
# "Docuseal" / "DocuSeal" references survive in the tree.
|
||||
#
|
||||
# Intended survivors are documented in REBRANDING.md:
|
||||
# - AGPL §7(b) attribution (NOTICE, README, LICENSE_ADDITIONAL_TERMS,
|
||||
# _powered_by, _email_attribution, completed.vue's credit, the
|
||||
# based_on i18n keys, Wabosign::UPSTREAM_NAME / UPSTREAM_URL)
|
||||
# - SDK custom-element names <docuseal-form>, <docuseal-builder>
|
||||
# - npm packages @docuseal/{react,vue,angular,embed}
|
||||
# - upstream binary URLs github.com/docusealco/{fields-detection,
|
||||
# pdfium-binaries,turbo}
|
||||
# - DocuSeal LLC copyright header in calculator.js
|
||||
#
|
||||
# Any other occurrence is treated as an accidental survivor and printed.
|
||||
#
|
||||
# Wired into CI via .github/workflows/ci.yml.
|
||||
|
||||
require 'find'
|
||||
require 'set'
|
||||
|
||||
ROOT = File.expand_path('..', __dir__)
|
||||
|
||||
DENY_PREFIXES = [
|
||||
'.git/', '.github/', 'node_modules/', 'vendor/bundle/', 'vendor/cache/',
|
||||
'tmp/', 'log/', 'pg_data/', 'public/packs/', 'public/packs-test/',
|
||||
'wabosign/', '.claude/', 'coverage/'
|
||||
].freeze
|
||||
|
||||
BINARY_EXT = Set.new(%w[
|
||||
.png .jpg .jpeg .gif .ico .svg .pdf .zip .tgz .gz .bz2 .xz
|
||||
.woff .woff2 .ttf .eot .otf
|
||||
.pkcs12 .p12 .pem .crt .cer .key
|
||||
.db .sqlite .sqlite3 .so .dylib .dll .exe .onnx
|
||||
]).freeze
|
||||
|
||||
# Whole files we ignore — every "docuseal" hit in them is intentional.
|
||||
ALLOW_FILES = Set.new([
|
||||
'NOTICE',
|
||||
'LICENSE',
|
||||
'LICENSE_ADDITIONAL_TERMS',
|
||||
'REBRANDING.md',
|
||||
'CHANGELOG.md',
|
||||
'README.md',
|
||||
'GOOGLE_SSO.md',
|
||||
'SMS.md',
|
||||
'app/javascript/submission_form/calculator.js',
|
||||
'app/javascript/submission_form/completed.vue',
|
||||
'app/views/shared/_powered_by.html.erb',
|
||||
'app/views/shared/_email_attribution.html.erb',
|
||||
'bin/rebrand-sync',
|
||||
'bin/rebrand-check',
|
||||
'lib/wabosign.rb',
|
||||
# Migration that finds rows by the legacy docuseal_aatl name.
|
||||
'db/migrate/20260515183000_rename_docuseal_aatl_cert.rb'
|
||||
]).freeze
|
||||
|
||||
ALLOW_FILE_PREFIXES = ['docs/embedding/'].freeze
|
||||
|
||||
# Per-line allowed patterns. If every "docuseal" hit on a line is
|
||||
# inside one of these, the line is tolerated.
|
||||
ALLOW_PATTERNS = [
|
||||
# SDK custom element tags — bare, attributed, HTML-encoded, quoted
|
||||
%r{<docuseal-(form|builder)\b},
|
||||
%r{</docuseal-(form|builder)>},
|
||||
%r{</?docuseal-(form|builder)},
|
||||
%r{['"]docuseal-(form|builder)['"]},
|
||||
# SDK class names exported by the npm packages
|
||||
%r{\bDocuseal(Form|Builder)(Component)?\b},
|
||||
# npm package paths
|
||||
%r{@docuseal/(react|vue|angular|embed)},
|
||||
# Upstream binary release URLs
|
||||
%r{docusealco/fields-detection},
|
||||
%r{docusealco/pdfium-binaries},
|
||||
%r{docusealco/turbo},
|
||||
# Programmatic custom-element registration
|
||||
%r{customElements\.define\(['"]docuseal-},
|
||||
# Constants holding the AGPL §7(b) attribution
|
||||
%r{Wabosign::UPSTREAM_(NAME|URL)},
|
||||
# AGPL attribution idioms — preserve the literal "DocuSeal" credit
|
||||
%r{\b(?:fork(?:ed)? of|forked from|based on|derived from)\s+DocuSeal\b},
|
||||
# JS calculator copyright header
|
||||
%r{DocuSeal,?\s+LLC},
|
||||
# i18n key carrying the AGPL credit value
|
||||
%r{based_on:}
|
||||
].freeze
|
||||
|
||||
def deny_listed?(rel)
|
||||
return true if ALLOW_FILES.include?(rel)
|
||||
return true if ALLOW_FILE_PREFIXES.any? { |p| rel.start_with?(p) }
|
||||
DENY_PREFIXES.any? { |p| rel.start_with?(p) }
|
||||
end
|
||||
|
||||
def likely_binary?(path)
|
||||
return true if BINARY_EXT.include?(File.extname(path).downcase)
|
||||
head = File.binread(path, 1024)
|
||||
head.include?("\x00")
|
||||
rescue StandardError
|
||||
false
|
||||
end
|
||||
|
||||
HIT_RE = /docuseal/i
|
||||
|
||||
def line_is_tolerated?(line)
|
||||
scrubbed = line.dup
|
||||
ALLOW_PATTERNS.each { |re| scrubbed = scrubbed.gsub(re, '') }
|
||||
!scrubbed.match?(HIT_RE)
|
||||
end
|
||||
|
||||
violations = []
|
||||
|
||||
Find.find(ROOT) do |path|
|
||||
rel = path.sub(%r{\A#{Regexp.escape(ROOT)}/}, '')
|
||||
if File.directory?(path)
|
||||
Find.prune if rel != '' && DENY_PREFIXES.any? { |p| "#{rel}/".start_with?(p) }
|
||||
next
|
||||
end
|
||||
next if deny_listed?(rel)
|
||||
next if likely_binary?(path)
|
||||
|
||||
begin
|
||||
File.foreach(path, encoding: 'UTF-8').with_index(1) do |line, lineno|
|
||||
next unless line.match?(HIT_RE)
|
||||
next if line_is_tolerated?(line)
|
||||
|
||||
violations << "#{rel}:#{lineno}: #{line.chomp}"
|
||||
end
|
||||
rescue Encoding::InvalidByteSequenceError, ArgumentError
|
||||
next
|
||||
end
|
||||
end
|
||||
|
||||
if violations.empty?
|
||||
puts 'rebrand-check: ok'
|
||||
exit 0
|
||||
else
|
||||
warn "rebrand-check: #{violations.size} unintended DocuSeal reference(s):"
|
||||
violations.each { |v| warn " #{v}" }
|
||||
exit 1
|
||||
end
|
||||
@ -0,0 +1,204 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
#
|
||||
# bin/rebrand-sync — applies the DocuSeal → WaboSign rename sweep across
|
||||
# the working tree. Idempotent: re-running produces no further changes.
|
||||
#
|
||||
# Intended use: on a sync branch created from an upstream tag, before
|
||||
# merging into master. Re-run after the merge to catch any new files
|
||||
# upstream introduced. See REBRANDING.md "Sync workflow".
|
||||
#
|
||||
# Rule spec lives at .claude/plans/come-up-with-a-foamy-flask.md.
|
||||
# Preserved tokens (SDK custom elements, @docuseal/* npm packages,
|
||||
# upstream binary URLs, AGPL §7(b) attribution) are protected via
|
||||
# sentinel-swap before substitutions run.
|
||||
|
||||
require 'find'
|
||||
require 'set'
|
||||
require 'fileutils'
|
||||
|
||||
ROOT = File.expand_path('..', __dir__)
|
||||
|
||||
# Pull canonical target names from lib/wabosign.rb when present, so a
|
||||
# future brand change only needs editing that file. On a fresh upstream
|
||||
# tag the file does not exist yet — fall back to the literal defaults.
|
||||
wabosign_rb_path = File.join(ROOT, 'lib/wabosign.rb')
|
||||
WABOSIGN_RB = File.exist?(wabosign_rb_path) ? File.read(wabosign_rb_path) : ''
|
||||
|
||||
def const_str(name)
|
||||
m = WABOSIGN_RB.match(/^\s*#{Regexp.escape(name)}\s*=\s*['"]([^'"]+)['"]/)
|
||||
m && m[1]
|
||||
end
|
||||
|
||||
PRODUCT_NAME = const_str('PRODUCT_NAME') || 'WaboSign'
|
||||
AATL_CERT = const_str('AATL_CERT_NAME') || 'wabosign_aatl'
|
||||
LC = PRODUCT_NAME.downcase
|
||||
|
||||
DENY_PATHS = Set.new([
|
||||
'NOTICE',
|
||||
'LICENSE',
|
||||
'LICENSE_ADDITIONAL_TERMS',
|
||||
'REBRANDING.md',
|
||||
'CHANGELOG.md',
|
||||
'README.md',
|
||||
'GOOGLE_SSO.md',
|
||||
'SMS.md',
|
||||
'app/javascript/submission_form/calculator.js',
|
||||
'app/javascript/submission_form/completed.vue',
|
||||
'app/views/shared/_powered_by.html.erb',
|
||||
'app/views/shared/_email_attribution.html.erb',
|
||||
'bin/rebrand-sync',
|
||||
'bin/rebrand-check',
|
||||
# Holds UPSTREAM_NAME / UPSTREAM_URL constants — must not be swept.
|
||||
'lib/wabosign.rb',
|
||||
# Encrypted-config migration matches the literal upstream string to find
|
||||
# rows to rewrite; sweeping would silently neuter it.
|
||||
'db/migrate/20260515183000_rename_docuseal_aatl_cert.rb'
|
||||
]).freeze
|
||||
|
||||
DENY_PREFIXES = [
|
||||
'docs/embedding/',
|
||||
'.git/',
|
||||
'.github/',
|
||||
'node_modules/',
|
||||
'vendor/bundle/',
|
||||
'vendor/cache/',
|
||||
'tmp/',
|
||||
'log/',
|
||||
'pg_data/',
|
||||
'public/packs/',
|
||||
'public/packs-test/',
|
||||
'wabosign/',
|
||||
'.claude/',
|
||||
'coverage/'
|
||||
].freeze
|
||||
|
||||
BINARY_EXT = Set.new(%w[
|
||||
.png .jpg .jpeg .gif .ico .svg .pdf .zip .tgz .gz .bz2 .xz
|
||||
.woff .woff2 .ttf .eot .otf
|
||||
.pkcs12 .p12 .pem .crt .cer .key
|
||||
.db .sqlite .sqlite3 .so .dylib .dll .exe .onnx
|
||||
]).freeze
|
||||
|
||||
# File renames the upstream rebrand commit applied. The two logo partials
|
||||
# were given a *semantic* name (_brand_logo, not _wabosign_logo) so we do
|
||||
# NOT rename them in the sweep — git's rename detection during merge will
|
||||
# carry upstream content edits into the fork's _brand_logo.html.erb.
|
||||
FILE_RENAMES = {
|
||||
'lib/docuseal.rb' => 'lib/wabosign.rb'
|
||||
}.freeze
|
||||
|
||||
# Tokens that must survive the sweep. Sentinel-protected so the
|
||||
# substitutions below cannot touch them. Sentinels use plain ASCII; the
|
||||
# surrounding spaces and digits make a collision with real content
|
||||
# vanishingly unlikely.
|
||||
PRESERVE = {
|
||||
# SDK custom-element identifiers (hyphenated — match anywhere they
|
||||
# appear: bare, inside <tags> with attributes, HTML-encoded as
|
||||
# <docuseal-form, or quoted as 'docuseal-form')
|
||||
'docuseal-form' => ' SENTINEL00 ',
|
||||
'docuseal-builder' => ' SENTINEL01 ',
|
||||
# npm packages
|
||||
'@docuseal/react' => ' SENTINEL02 ',
|
||||
'@docuseal/vue' => ' SENTINEL03 ',
|
||||
'@docuseal/angular' => ' SENTINEL04 ',
|
||||
'@docuseal/embed' => ' SENTINEL05 ',
|
||||
# Upstream binary release URLs (org + repo path)
|
||||
'docusealco/fields-detection' => ' SENTINEL06 ',
|
||||
'docusealco/pdfium-binaries' => ' SENTINEL07 ',
|
||||
'docusealco/turbo' => ' SENTINEL08 ',
|
||||
# Ruby constants holding the AGPL §7(b) attribution
|
||||
'Wabosign::UPSTREAM_NAME' => ' SENTINEL09 ',
|
||||
'Wabosign::UPSTREAM_URL' => ' SENTINEL0A ',
|
||||
# AGPL attribution idioms — preserve the literal "DocuSeal" credit
|
||||
# wherever the fork describes itself as derived from upstream.
|
||||
'fork of DocuSeal' => ' SENTINEL0B ',
|
||||
'forked from DocuSeal' => ' SENTINEL0C ',
|
||||
'based on DocuSeal' => ' SENTINEL0D ',
|
||||
'derived from DocuSeal' => ' SENTINEL0E ',
|
||||
# JS calculator copyright header
|
||||
'DocuSeal LLC' => ' SENTINEL0F ',
|
||||
'DocuSeal, LLC' => ' SENTINEL10 ',
|
||||
# i18n key (not value) for AGPL credit
|
||||
'based_on:' => ' SENTINEL11 '
|
||||
}.freeze
|
||||
|
||||
RULES = [
|
||||
[/\bDocuseal\b/, 'Wabosign'],
|
||||
[/\bDocuSeal\b/, PRODUCT_NAME],
|
||||
['docuseal_aatl', AATL_CERT],
|
||||
['docuseal_dev', "#{LC}_dev"],
|
||||
['docuseal_test', "#{LC}_test"],
|
||||
['docuseal_production', "#{LC}_production"],
|
||||
['docuseal.env', "#{LC}.env"],
|
||||
['ghcr.io/docusealco/docuseal', "ghcr.io/wabolabs/#{LC}"],
|
||||
['docusealco/docuseal', "wabolabs/#{LC}"],
|
||||
['/home/docuseal', "/home/#{LC}"],
|
||||
['docuseal:docuseal', "#{LC}:#{LC}"],
|
||||
[/\bdocuseal_/, "#{LC}_"],
|
||||
[/\bdocuseal\b/, LC]
|
||||
].freeze
|
||||
|
||||
def deny_listed?(rel)
|
||||
return true if DENY_PATHS.include?(rel)
|
||||
DENY_PREFIXES.any? { |p| rel.start_with?(p) }
|
||||
end
|
||||
|
||||
def likely_binary?(path)
|
||||
return true if BINARY_EXT.include?(File.extname(path).downcase)
|
||||
head = File.binread(path, 1024)
|
||||
head.include?("\x00")
|
||||
rescue StandardError
|
||||
false
|
||||
end
|
||||
|
||||
def rewrite(text)
|
||||
PRESERVE.each { |t, s| text = text.gsub(t, s) }
|
||||
RULES.each { |pat, repl| text = text.gsub(pat, repl) }
|
||||
PRESERVE.each { |t, s| text = text.gsub(s, t) }
|
||||
text
|
||||
end
|
||||
|
||||
renamed = []
|
||||
FILE_RENAMES.each do |from, to|
|
||||
from_path = File.join(ROOT, from)
|
||||
to_path = File.join(ROOT, to)
|
||||
next unless File.exist?(from_path)
|
||||
next if File.exist?(to_path)
|
||||
|
||||
FileUtils.mkdir_p(File.dirname(to_path))
|
||||
FileUtils.mv(from_path, to_path)
|
||||
renamed << "#{from} -> #{to}"
|
||||
end
|
||||
|
||||
changed = []
|
||||
skipped_encoding = []
|
||||
|
||||
Find.find(ROOT) do |path|
|
||||
rel = path.sub(%r{\A#{Regexp.escape(ROOT)}/}, '')
|
||||
if File.directory?(path)
|
||||
Find.prune if rel != '' && deny_listed?("#{rel}/")
|
||||
next
|
||||
end
|
||||
|
||||
next if deny_listed?(rel)
|
||||
next if likely_binary?(path)
|
||||
|
||||
begin
|
||||
original = File.read(path, encoding: 'UTF-8')
|
||||
rescue Encoding::InvalidByteSequenceError, ArgumentError
|
||||
skipped_encoding << rel
|
||||
next
|
||||
end
|
||||
|
||||
updated = rewrite(original)
|
||||
next if updated == original
|
||||
|
||||
File.write(path, updated)
|
||||
changed << rel
|
||||
end
|
||||
|
||||
puts "rebrand-sync: renamed #{renamed.size} file(s), rewrote #{changed.size} file(s)"
|
||||
renamed.each { |r| puts " renamed: #{r}" }
|
||||
changed.each { |f| puts " rewrote: #{f}" }
|
||||
skipped_encoding.each { |f| warn " skipped (encoding): #{f}" }
|
||||
Loading…
Reference in new issue