Eternaltwin

Home | Applications

Eternaltwin Integration

This section describes how to integrate Eternaltwin into your project's repository. Integrating Eternaltwin to your repository allows you to run and test your project using a locally installed version of the Eternaltwin website.

Eternaltwin is installed as a project-local package (a Node package, or a Composer package for PHP projects):

  • it ensures all the contributors use the same version
  • the project is fully self-contained and does not require an internet connection to run
  • if you have multiple projects on your computer, there are no conflicts: each one has its own Eternaltwin version.

The packaged version is not the full website (for example, it does not include translations). It's a lightweight version specifically intended to be installed inside other projects.

The current published version is 0.16.4.

System requirements

You need the following tools on your system:

  • Node.js: version 20.11.0 or higher (this is the version Eternaltwin itself is developed against; some of the published packages still declare a lower engines.node floor, but they are not tested below it)
  • Yarn

The package ships a precompiled Eternaltwin executable for the following platforms only:

  • x86_64 Linux
  • x86_64 Windows
  • x86_64 macOS

On any other platform (in particular aarch64/Apple Silicon and ARM Linux), no precompiled executable is available: the install succeeds but the first call fails with native eternaltwin executable not found. You then need Rust and a local build of the executable, forced with the ETERNALTWIN_FORCE_BUILD=true environment variable.

ℹ Using npm as an alternative to yarn is not officially supported but should work.

Configure your repository for Node packages

Your repository must contain a package.json file at its root. It is a manifest file containing metadata for Node.js.

If your project does not have a package.json, you may create one by running the following command at the repo root and replying to the prompts:

yarn init

(add -p if your project is private and should never be published).

You may read the Yarn manifest reference if you wish to learn more about package.json files. The conventions used inside the Eternaltwin repository itself are described in package.json.

Below is an example minimal package.json file.

{
  "name": "myproject",
  "version": "0.0.1",
  "licenses": [
    {
      "type": "AGPL-3.0-or-later",
      "url": "https://spdx.org/licenses/AGPL-3.0-or-later.html"
    }
  ],
  "private": true,
  "scripts": {},
  "dependencies": {},
  "devDependencies": {}
}

Make sure to commit the package.json file.

Install Eternaltwin inside your project

Run the following command in the directory containing package.json:

yarn add --dev @eternaltwin/cli

This will perform the following 3 actions:

  1. Update your package.json file to document the new dependency on the package @eternaltwin/cli.
  2. Download the package (and its own dependencies) into the node_modules directory.
  3. Create (or update) a yarn.lock file to remember the exact version of the dependencies that were installed and prevent accidental regressions.

@eternaltwin/cli is a thin launcher: it depends on @eternaltwin/exe, which provides the native Eternaltwin executable for your platform, and forwards every argument to it.

Commit the package.json and yarn.lock files.

Do not commit the node_modules directory: add the node_modules/ rule to your .gitignore file.

The resulting package.json should be similar to:

{
  "name": "myproject",
  "version": "0.0.1",
  "licenses": [
    {
      "type": "AGPL-3.0-or-later",
      "url": "https://spdx.org/licenses/AGPL-3.0-or-later.html"
    }
  ],
  "private": true,
  "scripts": {},
  "dependencies": {},
  "devDependencies": {
    "@eternaltwin/cli": "^0.16.4"
  }
}

Yarn exposes the binaries of your dependencies directly, so you do not need to declare a script to call Eternaltwin: yarn eternaltwin already works. You may still add an entry such as "eternaltwin": "eternaltwin" to the scripts section if you prefer an explicit alias, or if you use npm.

⚠ The package was previously named @eternal-twin/website or @eternal-twin/cli, it was renamed to @eternaltwin/cli. Make sure you use the right package.

Start Eternaltwin

Once Eternaltwin is installed, you can run it from anywhere inside your repo using the following command:

yarn eternaltwin start

This command starts the local Eternaltwin server on your computer. You can use this server to test your project.

No configuration file is required: without one, Eternaltwin uses the built-in dev profile, which keeps all the data in memory and is lost on restart.

When starting, the server displays where each configuration source came from and the resolved configuration (with backend.secret blanked out), then one line per seeded user and per registered OAuth client. You can use this information to troubleshoot your configuration.

The built-in dev profile already seeds a handful of test users (alice / aaaaaaaaaa, bob / bbbbbbbbbb, and so on), a few forum sections and the OAuth clients of the historical Eternaltwin games, so you have something to log in with before configuring anything.

By default, the server uses the port 50320 and is available at the address http://localhost:50320/. start serves the website and the REST API on that single port, so http://localhost:50320/ is also the origin your application uses for the OAuth endpoints. (Inside the Eternaltwin repository itself, the website is served separately by the Angular dev server on port 50321; that split does not apply to the packaged version.)

Configure Eternaltwin

You can configure Eternaltwin to your personal preferences (the most notable is being able to run it with a Postgres database so you can have a persistent environment).

Eternaltwin looks for its configuration in the current directory and then in its parents. For the default dev profile, the file names are tried in the following order, and the first match wins:

  1. eternaltwin.dev.local.toml
  2. eternaltwin.dev.local.json
  3. eternaltwin.dev.toml
  4. eternaltwin.dev.json
  5. eternaltwin.local.toml
  6. eternaltwin.local.json
  7. eternaltwin.toml
  8. eternaltwin.json

(with another profile, dev in the first four names is replaced by the profile name; the sdk profile is special and reads no configuration file at all).

The *.local.* files are meant to hold values specific to your machine and should not be stored in Git. The recommended strategy is therefore:

  1. Create a file named eternaltwin.toml at the root of your repository, with the settings shared by all the contributors, and commit it. You may start from Eternaltwin's own commented configuration (raw), which documents every available key.
  2. Add the eternaltwin.local.toml rule to your .gitignore.
  3. Update your project setup documentation: contributors who need machine-specific settings (a Postgres connection, real mail credentials, ...) copy eternaltwin.toml to eternaltwin.local.toml and edit that copy.

The available keys and the full resolution algorithm are described in Eternaltwin server configuration. The section you will need first is [seed.app]: it is where you register your own game as an OAuth client, as described in Eternaltwin for OAuth.

Run yarn eternaltwin config to load the configuration, print the resolved result and exit, without starting a server. This is the quickest way to check which files were picked up and which values won.

Other commands

yarn eternaltwin forwards its arguments to the Eternaltwin executable, which provides the following subcommands:

  • yarn eternaltwin start: start the full website (REST API plus the web frontend)
  • yarn eternaltwin backend: start the backend only (REST API and action handlers, no frontend). Accepts --format json|jsonl|text to emit machine-readable server events
  • yarn eternaltwin config: resolve and print the configuration, then exit
  • yarn eternaltwin db check: check the state of the Postgres database used by the dev website if configured to use the Postgres store
  • yarn eternaltwin db reset: initialize an empty database
  • yarn eternaltwin db sync: upgrade an existing database to the latest schema version
  • yarn eternaltwin dump: dump the database state into a directory
  • yarn eternaltwin job: control the background jobs
  • yarn eternaltwin version: print the version of the executable

All the commands that need the configuration accept the same options, most notably --profile <name> (default: dev) and --config <uri>.

PHP projects

If your project is a PHP project, the equivalent of @eternaltwin/cli is the Composer package eternaltwin/cli. It requires PHP >=8.1.

composer require --dev eternaltwin/cli

The executable itself is provided by the eternaltwin/exe package, which downloads the precompiled binary at install time (or builds it with cargo if your platform has no precompiled build). It does so through a Composer compile plugin, so your own composer.json must allow that plugin and whitelist the package:

{
  "config": {
    "allow-plugins": {
      "civicrm/composer-compile-plugin": true
    }
  },
  "extra": {
    "compile-whitelist": [
      "eternaltwin/exe"
    ]
  }
}

Eternaltwin is then available as vendor/bin/eternaltwin, with the same subcommands as above:

./vendor/bin/eternaltwin start

The PHP client for the Eternaltwin API is a separate package, eternaltwin/etwin; see Eternaltwin API.

Running Eternaltwin from your tests

Beyond the command line, a local Eternaltwin server can be started and stopped programmatically, so your test suite can run against a real server instead of a mock.

For Node, this is what @eternaltwin/sdk does. Install it together with the executable and the Node API client:

yarn add --dev @eternaltwin/sdk @eternaltwin/exe @eternaltwin/client-node
import {EternaltwinNodeClient} from "@eternaltwin/client-node";
import {getExecutableUri} from "@eternaltwin/exe";
import {Sdk} from "@eternaltwin/sdk";

const exe = await getExecutableUri();
const sdk = await Sdk.fromExe(exe);
const server = await sdk.startServer({profile: "sdk"});
try {
  const uri = server.getUri();
  if (uri === null) {
    throw new Error("failed to retrieve the server URI");
  }
  // `uri` is the root of the server: the client appends `api/v1` itself.
  const client = new EternaltwinNodeClient(uri);
  const auth = await client.getAuthSelf({auth: "invalidtoken"});
  console.log(auth);
} finally {
  await server.stop();
}

startServer spawns eternaltwin backend --format jsonl and resolves once the server reports that it is listening, so there is no need to poll the port. EternaltwinServer also implements Symbol.asyncDispose, so if your sources go through TypeScript (5.2 or later) you may write await using server = await sdk.startServer(...) and drop the try/finally, as Eternaltwin's own tests do.

The profile option selects the configuration profile:

  • sdk (the default) reads no configuration file at all and always uses the same built-in settings: everything in memory, a virtual clock and mocked outbound clients. This is the profile to use for reproducible tests.
  • test uses the built-in test profile, which expects Postgres, and does read eternaltwin.test.toml / eternaltwin.test.local.toml from the working directory, so you can override the store with store = "Memory" as Eternaltwin's own test configuration does.

Because the sdk profile reads no file, it cannot be given your own OAuth client: it only seeds the same built-in test users and historical game clients as dev. Tests that go through the OAuth flow therefore use the test profile together with an eternaltwin.test.toml declaring the application under [seed.app] — this is exactly what Eternaltwin's own SDK tests do.

ℹ The PHP equivalent, eternaltwin/test-server, is still a work in progress and should not be relied on yet. For PHP tests, start vendor/bin/eternaltwin backend --profile sdk yourself and stop it when the suite ends.

Next steps

Now that your repo is configured to run Eternaltwin, you may start to actually integrate your project with Eternaltwin. The first step would be to use Eternaltwin to manage user accounts through OAuth.