feat: Tier B operator scaffolding — bundle, copy SoT, posts, emails
Pick up and finish yesterday's cut-off Tier B pass. - build/: PyInstaller scaffold (datatools.spec + launcher.py + hook-streamlit.py + README) — folder-mode bundle, locked 127.0.0.1, per-OS recipe - marketing/COPY.md: single source of truth for every customer-facing string — landing H1/sub/CTAs, demo CTAs, email subjects, Gumroad listing, banned phrases - marketing/community-posts/: 9 drafts (3 posts × 3 niches: bookkeeper, revops, shopify-pet) — story / tip / soft-offer - marketing/emails/: 18 drafts (Gumroad delivery + 5-touch onboarding × 3 niches), per-niche segmentation guidance - docs/NEXT-STEPS.md: flip 2.2 / 2.4 / 3.1 / 3.4 to done with pointers to the new assets; add Phase 0 inventory rows - .gitignore: narrow `build/` ignore so PyInstaller spec + launcher + hooks get tracked, only generated artifacts (build/build/, build/__pycache__/, build/dist/) stay ignored Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
206
build/README.md
Normal file
206
build/README.md
Normal file
@@ -0,0 +1,206 @@
|
||||
# Build — DataTools desktop installer
|
||||
|
||||
> Cross-platform PyInstaller bundle for Mac / Windows / Linux. The
|
||||
> single deliverable the buyer downloads from Gumroad.
|
||||
> **Owner**: Michael · **Updated**: 2026-05-01
|
||||
|
||||
This directory is the build pipeline. Source of truth for the bundle
|
||||
shape, hidden-import lists, per-platform recipes, and the launcher
|
||||
that boots Streamlit inside the bundle.
|
||||
|
||||
## Files
|
||||
|
||||
```
|
||||
build/
|
||||
├── launcher.py Entry point PyInstaller wraps. Boots a local
|
||||
│ Streamlit server, opens browser, locks server
|
||||
│ to 127.0.0.1 so the privacy claim holds.
|
||||
├── datatools.spec PyInstaller spec — hidden imports, data files,
|
||||
│ Mac .app bundle config.
|
||||
├── hooks/ PyInstaller hooks for libs the static analyser
|
||||
│ └── hook-streamlit.py misses (Streamlit's dynamic imports).
|
||||
├── icon.icns macOS app icon (TODO: produce from a 1024×1024
|
||||
│ PNG. Optional — bundle still builds without).
|
||||
├── icon.ico Windows app icon (TODO).
|
||||
└── README.md this file
|
||||
```
|
||||
|
||||
## Per-platform recipe
|
||||
|
||||
Each platform builds on its own machine — PyInstaller does **not**
|
||||
cross-compile. Pick the platform that matches the bundle you need.
|
||||
GitHub Actions matrix runners are the simplest way to produce all
|
||||
three from one push (see "CI build" below).
|
||||
|
||||
### Mac (Intel + Apple Silicon, universal2)
|
||||
|
||||
```bash
|
||||
# One-time:
|
||||
pyenv install 3.12
|
||||
pyenv local 3.12
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
pip install pyinstaller
|
||||
|
||||
# Build:
|
||||
pyinstaller build/datatools.spec --clean
|
||||
|
||||
# Output:
|
||||
# dist/DataTools/ — folder mode (faster cold start)
|
||||
# dist/DataTools.app/ — macOS .app bundle (drag-drop into /Applications)
|
||||
|
||||
# Sign + notarize (after Apple Developer Program enrollment per BUSINESS.md §10):
|
||||
codesign --deep --force --options runtime \
|
||||
--sign "Developer ID Application: <YOUR-NAME> (<TEAMID>)" \
|
||||
dist/DataTools.app
|
||||
|
||||
# Notarize:
|
||||
xcrun notarytool submit dist/DataTools.app \
|
||||
--apple-id "<YOUR-APPLE-ID>" \
|
||||
--team-id "<TEAMID>" \
|
||||
--password "<APP-SPECIFIC-PASSWORD>" \
|
||||
--wait
|
||||
|
||||
# Staple the notarization ticket so Gatekeeper sees it offline:
|
||||
xcrun stapler staple dist/DataTools.app
|
||||
|
||||
# Wrap for distribution:
|
||||
hdiutil create -volname "DataTools" -srcfolder dist/DataTools.app \
|
||||
-ov -format UDZO dist/DataTools-1.0.0-mac.dmg
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
||||
```powershell
|
||||
# One-time:
|
||||
py -3.12 -m venv .venv
|
||||
.venv\Scripts\activate
|
||||
pip install -r requirements.txt
|
||||
pip install pyinstaller
|
||||
|
||||
# Build:
|
||||
pyinstaller build\datatools.spec --clean
|
||||
|
||||
# Output:
|
||||
# dist\DataTools\ — folder mode
|
||||
# dist\DataTools\DataTools.exe
|
||||
|
||||
# Wrap with Inno Setup (free):
|
||||
# 1. Install Inno Setup (https://jrsoftware.org/isdl.php)
|
||||
# 2. Create installer.iss next to this README:
|
||||
# [Setup]
|
||||
# AppName=DataTools
|
||||
# AppVersion=1.0.0
|
||||
# DefaultDirName={autopf}\DataTools
|
||||
# OutputDir=..\..\dist
|
||||
# OutputBaseFilename=DataTools-1.0.0-win-setup
|
||||
# Compression=lzma
|
||||
# SolidCompression=yes
|
||||
# [Files]
|
||||
# Source: "..\..\dist\DataTools\*"; DestDir: "{app}"; Flags: recursesubdirs
|
||||
# [Icons]
|
||||
# Name: "{autoprograms}\DataTools"; Filename: "{app}\DataTools.exe"
|
||||
# 3. Compile: ISCC.exe build\installer.iss
|
||||
|
||||
# Code-sign (optional but reduces SmartScreen warnings):
|
||||
# Use signtool with a code-signing cert (Sectigo / DigiCert).
|
||||
# Without signing, buyer sees "Windows protected your PC" once;
|
||||
# they click "More info → Run anyway." Acceptable for v1.
|
||||
```
|
||||
|
||||
### Linux (AppImage)
|
||||
|
||||
```bash
|
||||
python3.12 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
pip install pyinstaller
|
||||
|
||||
pyinstaller build/datatools.spec --clean
|
||||
# dist/DataTools/ — folder mode
|
||||
|
||||
# Wrap as AppImage (single-file portable app):
|
||||
# 1. Download appimagetool from https://appimage.org/
|
||||
# 2. Set up the AppDir layout:
|
||||
# DataTools.AppDir/
|
||||
# ├── AppRun -> ./DataTools/DataTools
|
||||
# ├── DataTools.desktop (icon + entry config)
|
||||
# ├── icon.png
|
||||
# └── usr/bin/ -> dist/DataTools/*
|
||||
# 3. ./appimagetool DataTools.AppDir dist/DataTools-1.0.0-linux-x86_64.AppImage
|
||||
```
|
||||
|
||||
## CI build (recommended once the spec is stable)
|
||||
|
||||
`.github/workflows/build.yml` (template):
|
||||
|
||||
```yaml
|
||||
name: Build installers
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags: [ 'v*' ]
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, windows-latest, ubuntu-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with: { python-version: '3.12' }
|
||||
- run: pip install -r requirements.txt pyinstaller
|
||||
- run: pyinstaller build/datatools.spec --clean
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: DataTools-${{ matrix.os }}
|
||||
path: dist/
|
||||
```
|
||||
|
||||
Mac code-signing in CI requires the cert + private key as a GitHub
|
||||
secret (encoded with `base64`). Detailed walkthrough belongs in a
|
||||
later doc — for v1, sign locally and upload to GitHub Releases.
|
||||
|
||||
## Common pitfalls
|
||||
|
||||
| Symptom | Fix |
|
||||
|---|---|
|
||||
| Bundle is 800+ MB | Check the ``excludes`` list in ``datatools.spec``. ``matplotlib`` / ``scipy`` / ``tkinter`` are the usual suspects. |
|
||||
| App launches, browser opens, page is blank | Streamlit's static assets aren't bundled. Re-run with `--log-level=DEBUG` and confirm the static dir was collected by `collect_data_files('streamlit')`. |
|
||||
| App launches but logs ``ImportError: streamlit.runtime.X`` | Add ``X`` to ``hidden_imports`` in the spec or to ``hook-streamlit.py``. |
|
||||
| Mac Gatekeeper says "DataTools is damaged and can't be opened" | The bundle wasn't signed + notarized. Don't ship to buyers without these — see the Mac recipe above. |
|
||||
| Windows SmartScreen blocks first launch | Buyer clicks "More info → Run anyway". Code-signing reduces but doesn't eliminate this; for v1 it's an accepted friction. |
|
||||
| Bundle works on dev machine but crashes on a clean machine | Likely a missing C runtime. On Windows, install [VC++ redistributable](https://aka.ms/vs/17/release/vc_redist.x64.exe) into the installer alongside the bundle. |
|
||||
|
||||
## Testing the bundle
|
||||
|
||||
Smoke-test on a **clean** machine (or VM) — your dev machine has too
|
||||
much state to trust:
|
||||
|
||||
```
|
||||
1. Boot a clean Mac / Win / Linux VM.
|
||||
2. Copy the .dmg / .exe / .AppImage onto it.
|
||||
3. Install / drag-drop into Applications / chmod +x.
|
||||
4. Double-click the app icon.
|
||||
5. Browser should open to http://127.0.0.1:850x within 5 seconds.
|
||||
6. Drop samples/demo/shopify_pet_customers.csv into the
|
||||
Pipeline Runner page; click Run; AFTER preview should appear.
|
||||
7. Confirm in the network tab: zero outbound calls except to
|
||||
127.0.0.1 and the Streamlit static asset paths (also local).
|
||||
```
|
||||
|
||||
Step 7 is the privacy-claim integrity check from
|
||||
`docs/POST-LAUNCH.md` §6 — do this once per release, then trust it.
|
||||
|
||||
## Versioning
|
||||
|
||||
Bump the version string in three places per release:
|
||||
|
||||
- `datatools.spec` (CFBundleVersion + CFBundleShortVersionString)
|
||||
- the Inno Setup `AppVersion` line
|
||||
- the AppImage filename
|
||||
|
||||
A single source of truth (e.g. `src/__init__.py`) is a future
|
||||
refactor — for v1 the three-spot update is fine.
|
||||
Reference in New Issue
Block a user