# saltcorn — dev environment for the dev-deploy plugin This project root holds the `dev-deploy` Saltcorn plugin and the launcher scripts + per-instance state needed to develop and test it against two local Saltcorn instances. Upstream Saltcorn lives in `saltcorn/` (a sibling git checkout managed independently from this project). ## Layout ``` saltcorn/ project root (this git repo) ├── dev-deploy/ the plugin (Saltcorn plugin, sc_plugin_api_version 1) ├── startServer.sh launch MAIN instance on :3000 ├── startServerTest.sh launch TEST instance on :3001 ├── devServer.sh launch MAIN under `saltcorn dev:serve` (tsc watch) ├── installSaltcorn.sh reproduce this dev environment from scratch ├── .dev-state/ MAIN instance: env.sh, saltcorn.sqlite, files/, sessions ├── .dev-state-test/ TEST instance: same shape, separate port + secret └── saltcorn/ upstream Saltcorn checkout (its own .git, gitignored here) ``` The upstream subfolder, both `.dev-state*` directories, and `node_modules/` are all gitignored — only the plugin + scripts + this README are tracked. ## Prerequisites - `git`, `curl` - `nvm` (auto-installed at `~/.nvm` if missing) → Node 20 - `git-lfs` for the `.gitattributes` LFS filters (`git lfs install` once per clone) `installSaltcorn.sh` handles nvm + Node 20 install for you. ## Fresh install ```bash ./installSaltcorn.sh [destination] # default: ./saltcorn ``` Clones upstream Saltcorn into `/saltcorn/`, runs `npm install` + `npm run tsc`, generates `/.dev-state/env.sh` with a fresh session secret, initializes the SQLite schema, creates the admin user, and writes `/startServer.sh`. The script sets up the **MAIN** instance only. To add the **TEST** instance, duplicate `.dev-state/` as `.dev-state-test/`, edit its `env.sh` to use a new `SALTCORN_SESSION_SECRET` and add `export SALTCORN_PORT="3001"`, then: ```bash source .dev-state-test/env.sh saltcorn reset-schema -f saltcorn create-user -a -e admin@local -p AdminP@ss1 ``` The reference `.dev-state-test/env.sh` already in this repo shows the exact pattern. ## Running ```bash ./startServer.sh # MAIN → http://localhost:3000/ ./startServerTest.sh # TEST → http://localhost:3001/ ./devServer.sh # MAIN under `saltcorn dev:serve` (nodemon + tsc watch) ``` Login on either instance: `admin@local` / `AdminP@ss1`. Each `startServer*.sh` runs `saltcorn install-plugin -d ./dev-deploy` on every boot, so edits to `dev-deploy/` go live on the next restart. ## The dev-deploy plugin `dev-deploy/` migrates Saltcorn metadata (tables, fields, views, pages, triggers, roles, library, tags, constraints, files, page groups, workflow steps, plus selected config keys and plugin configuration) across Dev/Test/Prod environments via an append-only ops journal with stable cross-environment UUIDs and HMAC-authenticated peer endpoints. Concurrent edits surface as conflicts in the admin UI with theirs/mine/per-field-merge resolution. User row data is left alone unless an admin explicitly marks a table as `managed` or `starter`. Admin UI: `/admin/dev-deploy/` (logged in as an admin). Machine API: `/dev-deploy/api/{journal,ingest,file/:uuid,health}` (HMAC-signed peer requests). ## Tests ```bash # Both servers must be running first. ./startServer.sh & ./startServerTest.sh & cd dev-deploy && npm test ``` Runs `dev-deploy/test/e2e.js` — ~50+ end-to-end tests covering pairing, promote, pull, conflict resolution, constraints, files, page groups, workflow steps, config propagation, managed/starter row data, revert, and machine-endpoint security. Tests run in order and share state; don't reorder. `test/sc-exec.js` is a shim used by the tests to run JS against Saltcorn's models with full `require()` access (saltcorn's built-in `run-js` uses a vm sandbox that hides Field/TableConstraint/File/etc.). ## Notes - **Per-instance `sessions.sqlite`.** Saltcorn's SQLite session store writes `sessions.sqlite` at process cwd (`packages/server/routes/utils.js`). `startServer.sh` and `startServerTest.sh` `cd` into their respective state directory before `exec saltcorn serve`, so each instance gets its own sessions DB. `devServer.sh` cannot do this (its `dev:serve` runs `npm run tsc` which needs cwd=upstream root), so its sessions land in `saltcorn/sessions.sqlite` — don't run `devServer.sh` and `startServer.sh` against the MAIN instance simultaneously. - **`SALTCORN_SESSION_SECRET` is the pairing identity.** dev-deploy derives its at-rest encryption key (KEK) for peer secrets via HKDF from this value. Rotating it invalidates every peer pairing on the instance. The secret is generated once per instance and persisted in that instance's `env.sh`. - **Upstream Saltcorn updates.** Pull from upstream via `git -C saltcorn pull` — the project root is a separate repo and doesn't see those commits. After a pull, you may need `npm install && npm run tsc` inside `saltcorn/`.