Overview
Setup creates an outer pnpm workspace with detached git worktrees for mf-dev, mf_admin, mf_store, mf_ui_theme, and mf_api_server.
Portless gives each service a stable local HTTPS URL while the apps continue to run on internal ports managed by portless.
| Service | Default URL | Namespaced example |
|---|---|---|
| API | https://api.mf.localhost | https://api-mfd-1234.mf.localhost |
| Admin | https://admin.mf.localhost | https://admin-mfd-1234.mf.localhost |
| Store | https://store.mf.localhost | https://store-mfd-1234.mf.localhost |
| Theme docs | https://theme.mf.localhost | https://theme-mfd-1234.mf.localhost |
| mf-dev docs | https://docs.mf.localhost | https://docs-mfd-1234.mf.localhost |
Passing a namespace like MFD-1234 changes both the workspace directory and the hostnames. Without a namespace, setup creates ../marketfuel-local and uses the default URLs. With a namespace, setup creates ../MFD-1234 and prefixes each service hostname with a DNS-safe version of that value.
Prerequisites
Node.js 20+
Node runs the docs app, React apps, pnpm, Turbo, and portless. Use Node 20 or newer so every workspace package uses the same runtime family as CI.
node --version
# Fresh macOS install with nvm
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
nvm install 20
nvm use 20
node --version pnpm 9
pnpm installs the outer workspace and links local @branditdev/* packages into the apps without publishing them first.
pnpm --version
# Fresh install through Corepack
corepack enable
corepack prepare pnpm@9.0.0 --activate
pnpm --version Ruby and Bundler
Ruby and Bundler run mf_api_server and install its Rails dependencies.
ruby --version
bundle --version
# Fresh macOS install with rbenv
brew install rbenv ruby-build
cd ~/code/mf_api_server
rbenv install "$(cat .ruby-version)"
rbenv local "$(cat .ruby-version)"
gem install bundler
ruby --version
bundle --version Foreman
Foreman reads generated Procfile.portless.* profiles and starts only the process group you choose, such as core, frontend, packages, docs, or full.
foreman --version
# Fresh install
gem install foreman
foreman --version portless
portless replaces hardcoded local ports with stable HTTPS URLs like https://admin.mf.localhost. Setup installs portless into the generated outer workspace and the Procfile calls it with pnpm exec portless. A global install is still useful for one-off commands like portless list.
portless --version
# Optional fresh global install for one-off commands
npm install -g portless
portless --version
# First-time HTTPS trust setup, if prompted later
portless trust Package registry tokens
The React apps depend on private packages from GitHub Packages and Font Awesome packages from the Font Awesome registry. The generated outer .npmrc reads tokens from your shell environment.
# Verify tokens are available to pnpm
test -n "$GITHUB_TOKEN" && echo "GITHUB_TOKEN is set"
test -n "$FONT_AWESOME_TOKEN" && echo "FONT_AWESOME_TOKEN is set"
# Add these to your shell profile if needed
export GITHUB_TOKEN="<github-token-with-packages-read-access>"
export FONT_AWESOME_TOKEN="<font-awesome-token>" Create the workspace
Run the setup command from your mf-dev checkout.
pnpm setup:local -- MFD-1234 By default, setup reads source checkouts from the sibling directory of mf-dev, uses the positional argument as the full workspace directory name and URL namespace, and uses each source checkout's current HEAD. Omitting the argument creates ../marketfuel-local and uses unprefixed URLs like https://admin.mf.localhost.
Customize refs or paths
ROOT=../custom-workspace SOURCE_ROOT=.. MF_DEV_REF=my-mf-dev-branch MF_ADMIN_REF=main MF_STORE_REF=main MF_UI_THEME_REF=main MF_API_REF=main pnpm setup:local Pass --force to overwrite generated workspace files without recreating existing worktrees.
pnpm setup:local -- MFD-1234 --force The generated root package.json forces @branditdev/mf_ui_theme to the local mf_ui_theme workspace path and @branditdev/mf-types to mf_api_server/packages/mf-types. This keeps admin and store pointed at local package changes without publishing new package versions.
URL namespaces
The setup argument controls both the generated directory name and URL namespace. pnpm setup:local creates ../marketfuel-local and uses unprefixed URLs. pnpm setup:local -- MFD-1234 creates ../MFD-1234 and uses URLs like https://admin-mfd-1234.mf.localhost.
Namespaces are normalized for hostnames. For example, MFD_1234 / Checkout becomes mfd-1234-checkout, so the admin URL becomes https://admin-mfd-1234-checkout.mf.localhost. Only open URLs for services you have started: the default pnpm dev starts API, admin, and store; docs and theme require pnpm dev:docs, pnpm dev:theme, or pnpm dev:full.
Install and run
cd ../MFD-1234
pnpm install
pnpm dev The generated pnpm dev command aliases pnpm dev:core. It builds linked packages, starts the portless proxy, then runs foreman start -f Procfile.portless.core for the API, admin, and store only.
pnpm dev:api # Rails API only
pnpm dev:admin # Admin only
pnpm dev:store # Store only
pnpm dev:frontend # Admin + store
pnpm dev:packages # Optional hooks/utils/ui/theme package watchers
pnpm dev:worker # Sidekiq only
pnpm dev:docs # mf-dev docs only
pnpm dev:theme # mf_ui_theme app + theme build watcher
pnpm dev:full # Everything The initial linked package build is required because the linked packages export files from dist, and fresh worktrees do not have those gitignored build outputs yet. Watch processes for @branditdev/hooks, @branditdev/utils, @branditdev/ui, and @branditdev/mf_ui_theme are opt-in through pnpm dev:packagesor pnpm dev:full.
Starting the proxy on HTTPS port 443 may ask for your macOS password once. Starting it before Foreman avoids every app process asking for sudo at the same time.
The generated Procfile uses pnpm exec portless so Foreman does not need your shell's global npm bin on its PATH. For Rails and Sidekiq, it also sets RBENV_VERSION from mf_api_server/.ruby-version and runs commands through rbenv exec. Rails reads the portless-assigned PORT directly, so the generated API command does not pass -p. The API and worker also get a local REDIS_URL=redis://localhost:6379/0 default.
Because credentials and env files are gitignored, setup links local-only files from your source checkouts into the generated worktrees when they exist locally: mf_api_server/config/master.key, mf_api_server/.env, mf_admin/.env, and mf_store/.env. If any are missing, ask the team for the local development values.
If you see warnings that package-level pnpm fields will not take effect, that is expected for this outer workspace. pnpm only applies overrides from the workspace root, so setup writes the shared app and theme overrides into the generated root package.json.
A 404 for a private package usually means GITHUB_TOKEN is missing or does not have package read access, not that the package is public npm-missing.
Cleanup
If setup fails because a worktree is missing but already registered, or if you want to remove the generated workspace, run the cleanup command from your mf-dev checkout.
pnpm cleanup:local Use the same workspace name, WORKSPACE_PREFIX, ROOT, or SOURCE_ROOT values you used when creating the workspace.
pnpm cleanup:local -- MFD-1234 Frontend API URLs
Setup links real frontend env files from your source checkouts when available. If they are missing, it writes minimal fallback files. Keep API URLs pointed at the portless API URL for one-off app starts. In namespaced workspaces, replace the default host with the namespaced host, such as api-mfd-1234.mf.localhost.
mf_admin/.env
PORT=3500
HOST=localhost
WDS_SOCKET_PORT=0
ALLOWED_HOSTS=admin-mfd-1234.mf.localhost, localhost, 127.0.0.1
REACT_APP_BACKEND_API_URL=https://api-mfd-1234.mf.localhost/api/v1
REACT_APP_ENV=development mf_store/.env
PORT=3501
WDS_SOCKET_PORT=0
REACT_APP_BACKEND_API_URL=https://api-mfd-1234.mf.localhost/api/v1/portal_core Troubleshooting
- Check active routes with
portless list. - Remove dead routes with
portless prune. - If Safari cannot resolve
*.localhost, runportless hosts sync. - Reset portless state with
portless clean.