Skip to content

Development

Stack

Node.js 22 (ES modules), Express 5, React 19 + Vite + Mantine for the GUI, Sequelize 6 on Postgres, Redis (node-redis), node-cron, yargs + table for the CLI, pino logging, Sentry, Jest + supertest.

config/            node-config defaults, env mapping, Sequelize migrations
scripts/           client scripts (tis-pull/push/cron), SQLite→Postgres migration, .env generator
src/api/           Express app: routes/, middleware/ (auth, acl, cache, error), public/ (landing page, /get scripts)
src/cli/           interactive CLI: index.js (shell, sign-in), commands/, ui.js (tables, messages), oidc.js
src/models/        Sequelize models: Source, Indicator, Tag, Token, Group (+ join tables)
src/services/      fetcher (fetch + parse + ingest), exporter, scheduler, groups, indicators, resync
src/helpers/       pure helpers (value detection, whitelist matching, netmask, tags, formatting)
src/worker.js      daemon entry: scheduler + API; src/server.js API only
tests/             Jest tests
web/               web GUI: React + Vite + Mantine, own package.json; builds into src/api/public/app

Local setup

Needs Node.js 22 and Docker. Postgres and Redis run in containers (docker-compose.dev.yml, localhost only); the worker, CLI and GUI run from the checkout.

npm ci && npm --prefix web ci
npm run dev:setup      # Postgres + Redis, migrations, demo data (_since v1.33.0_)

dev:setup seeds the admin token admin / dev-secret (or CONFIG_SEED_ADMIN_TOKEN if set) and npm run dev:seed loads demo data: a manual, a private import-only (demo-honeypot, own retention) and a feed source (demo-urlhaus, the public URLhaus host file, fetched on demand), tagged indicators spread over the last days, the group demo-customer (tag and age filters, whitelist source) and the guest token demo-guest scoped to it (secret printed once). It skips what already exists and refuses to run with NODE_ENV=production.

Then, in two terminals:

npm run dev:worker     # scheduler + API on :3000, using the local Redis
npm run dev:web        # GUI with hot reload: http://localhost:5173/app/ (proxies /api to :3000, or TIS_API_URL)
Command What it does
npm run dev:services Starts Postgres (:5432) and Redis (:6379); TIS_DEV_PG_PORT / TIS_DEV_REDIS_PORT move them.
npm run dev:services:down Stops them; the database stays in the tis-dev_tis-dev-db volume (docker volume rm to reset).
npm run dev:seed Demo data, see above.
npm run dev:worker The worker with CONFIG_REDIS_URL=redis://127.0.0.1:6379 (unless set).
npm run dev API only, restarted on changes (pm2 watch) – no scheduler; without Redis sessions stay in memory.
npm run cli The interactive CLI; NODE_ENV defaults to development, so no sign-in.
npm run build:web Production build of the GUI into src/api/public/app (also done by the Dockerfile and CI).

The API browser is at http://localhost:3000/api/. The defaults (postgres://tis:tis@127.0.0.1:5432/tis) match the dev services; with other ports set CONFIG_DB_URL / CONFIG_REDIS_URL. The production docker-compose.yml runs the registry image and needs the external proxy_public network – not meant for development.

Tests

npm test        # prettier --check, then Jest
npx jest tests/groups.test.js
  • API tests mock the models (jest.mock('../src/models/index.js')) and run the Express app via supertest.
  • Model and service tests use real models on in-memory SQLite: setupTestDb() from tests/helpers.js.
  • Integration tests in tests/integration/ run the real models, SQL and API against Postgres – whitelisting, exports, ingest, pruning, search. Run them with scripts/test-integration.sh (starts and removes a throwaway Postgres container; extra arguments go to Jest), or npm run test:integration with CONFIG_DB_URL pointing to a database whose name contains test – they truncate every table and refuse other databases. CI runs them with a Postgres service.

Git hooks (husky): pre-commit runs npm run lint (prettier), pre-push runs npm test.

Migrations

Migrations are ES modules in config/migrations/ named YYYYMMDDHHMMSS-description.js, exporting up(queryInterface, Sequelize) and down(...). They run automatically when the container starts. Keep model definitions in src/models/ in sync, and say in the commit message when a migration is irreversible.

Commits, branches and releases

  • Work on a branch (fix/…, feat/…), open a merge request into dev; dev is merged into master.
  • Commit messages follow Conventional Commits – semantic-release derives the version from them on master:
Type Release
fix(scope): … patch
feat(scope): … minor
BREAKING CHANGE: footer or feat!: major
refactor, test, docs, chore, … none

Reference issues in the footer (Closes #12). Issues, commits and code comments are in English.

CI/CD

.gitlab-ci.yml:

  1. test – every MR and push to master: npm ci, npm test, npm run build:web; integration-tests runs npm run test:integration against a Postgres service.
  2. release – on master: semantic-release bumps package.json, writes the chore(release): x.y.z commit, tags vx.y.z and creates the GitLab release with notes.
  3. build-image – on tags (and manual web pipelines): builds the Docker image and pushes :<tag> and :latest (:dev-<sha>/:dev for manual runs) to the GitLab registry.

GitLab CI is the only pipeline (the former GitHub Actions image build was removed). The Dockerfile builds production dependencies and the GUI in separate stages and copies only runtime files (package.json, config/, scripts/, src/) with --chown, so nothing is duplicated into an extra layer.