Skip to content

Commit 35ee4bb

Browse files
rmdespaulrobertlloyd
authored andcommitted
chore: make the development environment work from a clean checkout
Setting up to work on Indiekit needed steps that were either undocumented or documented incorrectly, and the test suite depended on ambient state. `npm test` now needs nothing at all. It already started its own in-memory MongoDB for most tests, but `util/test/unit/mongodb.js` connected to `mongodb://foo:bar@localhost` — port 27017 — and asserted the error was `Authentication failed.`. That only holds when something happens to be listening there. `docs/development.md` tells contributors to run MongoDB on 27018, so following the documented setup exactly produced a failure after a 30-second server-selection timeout. It now starts an in-memory server with authentication enabled, which is deterministic, needs no service, and takes 180ms rather than 30s. The test scripts also supply development defaults for SECRET and PASSWORD_SECRET, overridden by real environment variables when set. Without them the suite reports 115 failures that look like broken code rather than a missing environment. CI passes both from repository secrets, which are not available to pull requests from forks. `compose.yml` replaces the block of YAML that development.md asked contributors to copy by hand. That copy pinned mongo:7.0.11 — a release the README warns against, being affected by CVE-2025-14847 — and required `docker volume create` first, because it declared the volume external. Also adds `.env.example`, and corrects `npm run dev --production`, which passes the flag to npm rather than to the script.
1 parent 69910bf commit 35ee4bb

6 files changed

Lines changed: 114 additions & 36 deletions

File tree

.env.example

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Copy to `.env` and edit. Nothing here is needed to run `npm test`.
2+
3+
PUBLICATION_URL="http://localhost:3000"
4+
5+
# Used by @indiekit/endpoint-auth to sign and verify tokens, and to salt the
6+
# password. Any non-empty value works locally.
7+
SECRET="development"
8+
9+
# Hashed and salted password used when signing in. Generate by starting the
10+
# server and visiting /auth/new-password.
11+
PASSWORD_SECRET=""
12+
13+
# MongoDB. These match the defaults in compose.yml — `npm run db:up` reads
14+
# them, so changing the credentials here changes them in the container too.
15+
# MongoDB is optional; leave MONGO_URL unset to run without persistence.
16+
MONGO_PORT="27018"
17+
MONGO_INITDB_ROOT_USERNAME="indiekit"
18+
MONGO_INITDB_ROOT_PASSWORD="indiekit"
19+
MONGO_URL="mongodb://indiekit:indiekit@localhost:27018"
20+
21+
# Content store — see docs/development.md for the alternatives.
22+
# GITHUB_USER=""
23+
# GITHUB_REPO=""
24+
# GITHUB_BRANCH="main"
25+
# GITHUB_TOKEN=""
26+
27+
# Syndicators — see docs/development.md.
28+
# MASTODON_URL=""
29+
# MASTODON_USER=""
30+
# MASTODON_ACCESS_TOKEN=""

.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ docs/**/*.md
44
**/locales/*.json
55
*.html
66
CHANGELOG.md
7+
*.env.example

compose.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Development MongoDB for working on Indiekit itself.
2+
#
3+
# npm run db:up start it
4+
# npm run db:down stop it, keeping the data
5+
# npm run db:reset stop it and discard the data
6+
#
7+
# The test suite does not need this — `npm test` starts its own in-memory
8+
# MongoDB and requires no running services. This is here so that `npm start`
9+
# and `npm run dev` have a database to talk to.
10+
#
11+
# The port defaults to 27018 rather than 27017 so it does not collide with a
12+
# MongoDB already installed on the host. Override it with MONGO_PORT.
13+
14+
services:
15+
mongo:
16+
image: mongo:8
17+
container_name: indiekit-dev-mongo
18+
restart: unless-stopped
19+
ports:
20+
- "${MONGO_PORT:-27018}:27017"
21+
volumes:
22+
- mongo-data:/data/db
23+
environment:
24+
- MONGO_INITDB_ROOT_USERNAME=${MONGO_INITDB_ROOT_USERNAME:-indiekit}
25+
- MONGO_INITDB_ROOT_PASSWORD=${MONGO_INITDB_ROOT_PASSWORD:-indiekit}
26+
healthcheck:
27+
test: ["CMD", "mongosh", "--quiet", "--eval", "db.adminCommand('ping')"]
28+
interval: 10s
29+
timeout: 5s
30+
retries: 5
31+
start_period: 20s
32+
33+
volumes:
34+
mongo-data:

docs/development.md

Lines changed: 24 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# Setting up a local development environment
22

3-
To begin local development on the Indiekit project, clone this repository, configure a [content store](concepts#content-store), [publication preset](concepts#publication-preset) and [syndicator](concepts#syndication), and create a MongoDB database you can connect to.
3+
To begin local development on the Indiekit project, clone this repository, run `npm install`, then copy `.env.example` to `.env`. To run the server you will also want a [content store](concepts#content-store), [publication preset](concepts#publication-preset) and [syndicator](concepts#syndication), and a MongoDB database to connect to.
4+
5+
Running the tests needs none of that — see [Tests](#tests).
46

57
## Project structure
68

@@ -32,42 +34,26 @@ Express waits for a resolved configuration file before starting the server.
3234

3335
## MongoDB
3436

35-
Indiekit uses a MongoDB database for persistence. A convenient way to run MongoDB locally is to use [Docker Compose](https://docs.docker.com/compose/). Since the filesystem of a Docker container is ephemeral, you will need to create a [Docker volume](https://docs.docker.com/storage/volumes/). You can do this with the following docker command:
37+
Indiekit uses a MongoDB database for persistence. The repository ships a
38+
`compose.yml` that runs one, so you do not need to install MongoDB locally:
3639

3740
```sh
38-
docker volume create mongo-data
41+
npm run db:up
3942
```
4043

41-
Create a `docker-compose.yml` file that runs a service on an available port of your Docker host (e.g. 27018) and uses the Docker volume you have just created.
42-
43-
```yml
44-
version: '3.9'
45-
services:
46-
mongo:
47-
container_name: mongo
48-
environment:
49-
- MONGO_INITDB_ROOT_USERNAME
50-
- MONGO_INITDB_ROOT_PASSWORD
51-
image: mongo:7.0.11
52-
network_mode: bridge
53-
ports:
54-
- '27018:27017'
55-
restart: always
56-
volumes:
57-
- mongo-data:/data/db
58-
volumes:
59-
mongo-data:
60-
external: true
61-
```
44+
This starts MongoDB on port 27018 of your host, storing its data in a named
45+
Docker volume so it survives a restart. Use `npm run db:down` to stop it, or
46+
`npm run db:reset` to stop it and discard the data.
6247

63-
This configuration tells Docker to run the `mongo` service on port 27017 of the `mongo` container, and expose port 27018 on the Docker host (e.g. your computer).
48+
The port defaults to 27018 rather than 27017 so that it does not collide with
49+
a MongoDB already installed on your machine. Set `MONGO_PORT` to change it.
6450

65-
You can set the necessary environment variables in a `.env` file:
51+
Copy `.env.example` to `.env` to get a matching `MONGO_URL`:
6652

6753
```dotenv
68-
MONGO_INITDB_ROOT_USERNAME="username"
69-
MONGO_INITDB_ROOT_PASSWORD="password"
70-
MONGO_URL="mongodb://$MONGO_INITDB_ROOT_USERNAME:$MONGO_INITDB_ROOT_PASSWORD@localhost:27018"
54+
MONGO_INITDB_ROOT_USERNAME="indiekit"
55+
MONGO_INITDB_ROOT_PASSWORD="indiekit"
56+
MONGO_URL="mongodb://indiekit:indiekit@localhost:27018"
7157
```
7258

7359
> [!TIP]
@@ -76,6 +62,9 @@ MONGO_URL="mongodb://$MONGO_INITDB_ROOT_USERNAME:$MONGO_INITDB_ROOT_PASSWORD@loc
7662
> [!TIP]
7763
> To inspect data stored in a MongoDB database, use the [MongoDB shell](https://www.mongodb.com/products/tools/shell) or an application like [Compass](https://www.mongodb.com/products/tools/compass).
7864
65+
> [!NOTE]
66+
> MongoDB is optional. Leave `MONGO_URL` unset to run Indiekit without persistence; see the [README](https://github.com/getindiekit/indiekit#readme) for which features need a database.
67+
7968
## Configure a content store
8069

8170
Indiekit performs create, read, update and delete (CRUD) operations on files that are stored in a content store. Different content stores require different configurations and credentials.
@@ -171,6 +160,12 @@ The project uses both unit and integration tests. Run tests using the following
171160
npm test
172161
```
173162

163+
The test suite needs no setup at all: it starts its own in-memory MongoDB, so
164+
no database has to be running, and the `test` script supplies development
165+
defaults for `NODE_ENV`, `SECRET` and `PASSWORD_SECRET`. A clean checkout can
166+
run `npm install && npm test` straight away. Setting `SECRET` or
167+
`PASSWORD_SECRET` in the environment overrides the default.
168+
174169
To run a single test suite, use `node` followed by the path to the test. For example:
175170

176171
```sh

package.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@
2929
"docs:dev": "vitepress dev docs",
3030
"docs:build": "vitepress build docs",
3131
"dev": "node --watch-path=./ packages/indiekit/bin/cli.js serve",
32+
"db:up": "docker compose up --detach --wait",
33+
"db:down": "docker compose down",
34+
"db:reset": "docker compose down --volumes",
3235
"debug": "indiekit serve --debug",
3336
"start": "indiekit serve",
3437
"lint:prettier": "prettier . --check",
@@ -39,9 +42,9 @@
3942
"lint:css:fix": "stylelint '**/*.css' --fix",
4043
"lint": "npm run lint:prettier && npm run lint:js && npm run lint:css",
4144
"lint:fix": "npm run lint:prettier:fix && npm run lint:js:fix && npm run lint:css:fix",
42-
"test": "NODE_ENV=test node --test --test-reporter=spec",
43-
"test:coverage": "NODE_ENV=test node --test --experimental-test-coverage",
44-
"test:lcov": "NODE_ENV=test node --test --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=coverage/lcov.info"
45+
"test": "NODE_ENV=test SECRET=${SECRET:-test} PASSWORD_SECRET=${PASSWORD_SECRET:-test} node --test --test-reporter=spec",
46+
"test:coverage": "NODE_ENV=test SECRET=${SECRET:-test} PASSWORD_SECRET=${PASSWORD_SECRET:-test} node --test --experimental-test-coverage",
47+
"test:lcov": "NODE_ENV=test SECRET=${SECRET:-test} PASSWORD_SECRET=${PASSWORD_SECRET:-test} node --test --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=coverage/lcov.info"
4548
},
4649
"workspaces": [
4750
"helpers/*",

packages/util/test/unit/mongodb.js

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,27 @@ describe("util/lib/mongodb", async () => {
7474
);
7575
});
7676

77+
// Uses an in-memory server with authentication enabled rather than whatever
78+
// happens to be listening on port 27017. The suite must not depend on a
79+
// service it did not start: `docs/development.md` puts MongoDB on 27018, so
80+
// a correctly configured machine would otherwise fail here after a
81+
// 30-second server-selection timeout.
7782
it("Returns error if can’t connect to MongoDB client", async () => {
83+
const authServer = await MongoMemoryServer.create({
84+
auth: { enable: true },
85+
});
7886
mock.method(console, "error", () => {});
7987

80-
await getMongodbClient("mongodb://foo:bar@localhost");
81-
const result = console.error.mock.calls[0].arguments[0];
82-
83-
assert.equal(result, `Authentication failed.`);
88+
try {
89+
const uri = authServer
90+
.getUri()
91+
.replace("mongodb://", "mongodb://foo:bar@");
92+
await getMongodbClient(uri);
93+
const result = console.error.mock.calls[0].arguments[0];
94+
95+
assert.equal(result, `Authentication failed.`);
96+
} finally {
97+
await authServer.stop();
98+
}
8499
});
85100
});

0 commit comments

Comments
 (0)