Add rebrand-sync/-check tooling for upstream merges

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
Wabo 1 month ago
parent ee5a295e57
commit 6b652f8aa5

@ -26,6 +26,6 @@
/public/packs-test /public/packs-test
/public/packs /public/packs
/attachments /attachments
/docuseal /wabosign
.DS_Store .DS_Store
.env .env

5
.gitattributes vendored

@ -1 +1,6 @@
*.html linguist-detectable=false *.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

@ -3,6 +3,19 @@ name: CI
on: [push] on: [push]
jobs: jobs:
rebrand_check:
name: Rebrand check
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
- name: Install Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 4.0.1
- name: Run rebrand-check
run: ruby bin/rebrand-check
rubocop: rubocop:
name: Rubocop name: Rubocop
runs-on: ubuntu-latest runs-on: ubuntu-latest

2
.gitignore vendored

@ -34,7 +34,7 @@ yarn-debug.log*
/coverage /coverage
/attachments /attachments
/docuseal /wabosign
/ee /ee
dump.rdb dump.rdb
*.onnx *.onnx

@ -65,3 +65,40 @@ Per AGPL §7(b) and downstream SDK compatibility, the following references remai
- Webhook spec assertions updated to match new `WaboSign Webhook` user-agent - Webhook spec assertions updated to match new `WaboSign Webhook` user-agent
Full Rails boot requires Ruby 4.0.1, which was not available on the dev machine that performed the rebrand. Recommend running `docker compose up --build` to verify boot end-to-end before publishing. Full Rails boot requires Ruby 4.0.1, which was not available on the dev machine that performed the rebrand. Recommend running `docker compose up --build` to verify boot end-to-end before publishing.
## Sync workflow
Upstream lives at `docusealco/docuseal`. Each upstream release is brought in by re-running a deterministic rebrand sweep on the upstream tree, then merging into `master`. The strategy details are in [.claude/plans/come-up-with-a-foamy-flask.md](.claude/plans/come-up-with-a-foamy-flask.md); the short version follows.
### Tooling
- [bin/rebrand-sync](bin/rebrand-sync) — Ruby script that performs the DocuSeal → WaboSign rename sweep across the working tree. Idempotent. Honors a deny-list (see §"Intentionally preserved upstream references" above) and sentinel-protects AGPL §7(b) attribution phrases, SDK custom-element names (`docuseal-form`, `docuseal-builder`), `@docuseal/*` npm packages, and the `github.com/docusealco/{fields-detection,pdfium-binaries,turbo}` binary URLs.
- [bin/rebrand-check](bin/rebrand-check) — fails (exit 1) if any unintended DocuSeal reference survives. Wired into [.github/workflows/ci.yml](.github/workflows/ci.yml) as the `Rebrand check` job.
- `git config rerere.enabled true && git config rerere.autoupdate true` — once-per-checkout setup; remembers semantic conflict resolutions so the same call is not re-made each release.
- [.gitattributes](.gitattributes) marks `Gemfile.lock` and `yarn.lock` as `-merge` (regenerate after merge rather than diffing).
### Per-sync steps
```sh
git fetch upstream --tags
git checkout -b sync/upstream-<tag> <tag> # e.g. 3.0.0
bin/rebrand-sync
git add -A && git commit -m "Apply WaboSign rebrand sweep to upstream <tag>"
git checkout master
git merge --no-ff sync/upstream-<tag>
# Resolve conflicts. Rerere caches recurring resolutions.
bin/rebrand-sync # catch upstream-only new files
bin/rebrand-check # CI gate
bundle install
yarn install
# Verify (see "Verification" in the plan), then:
git tag wabosign-synced-with-<tag>
```
### Adding new preserved tokens
When upstream introduces a new SDK identifier, binary URL, or attribution surface that must survive the sweep, edit `PRESERVE` in [bin/rebrand-sync](bin/rebrand-sync) and `ALLOW_PATTERNS` in [bin/rebrand-check](bin/rebrand-check) together. The two must stay in sync — `rebrand-sync` decides what the sweep ignores, `rebrand-check` decides what CI tolerates.

@ -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{&lt;/?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
# &lt;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}" }

@ -1,9 +1,9 @@
self.addEventListener('install', () => { self.addEventListener('install', () => {
console.log('DocuSeal App installed') console.log('WaboSign App installed')
}) })
self.addEventListener('activate', () => { self.addEventListener('activate', () => {
console.log('DocuSeal App activated') console.log('WaboSign App activated')
}) })
self.addEventListener('fetch', (event) => { self.addEventListener('fetch', (event) => {

Loading…
Cancel
Save