Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 67 additions & 51 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,39 +16,75 @@ thirty has shipped a new version, and what changed in it.

Foundry VTT v14 or later. GM only.

## Install

In Foundry, open **Add-on Modules → Install Module**, paste this into the
**Manifest URL** box, and press Install:

```
https://github.com/vttforge/settings-vault/releases/latest/download/module.json
```

Then turn it on in a world: **Game Settings → Manage Modules**.

## What it does

Open **Settings → Configure Settings → Settings Vault**.

![The Settings Vault window. A list of packages with a tick box each, an export
section with a label field, and an import section with a file
picker.](docs/vault-window.png)

Tick the packages you want. Write a profile, and your browser saves a JSON file.
In another world, choose the file and read it back. The window then lists every
value it applied and every one it skipped, with the reason.

A profile is plain JSON. Open it, read it, delete a line, send it to someone.

## The three scopes
## What a profile carries

Foundry stores a setting in one of three places, and each travels differently.
Foundry keeps a setting in one of three places, and each one travels
differently.

**World.** Same value for everyone in the world, held in the database. It
travels, and only a GM can write it.
**World.** One value for everybody in the world. It travels, and only a GM can
write it.

**Client.** Held in the browser, one value per device. It travels, and lands on
whichever device does the import.

**User.** Held in the database against one user. Foundry writes a user-scope
setting against whoever is logged in and gives no way to write another user's.
So a profile carries the values of the person who exported it, and the person
importing receives them as their own. You cannot move a player's settings for
them.
**User.** Held against one person. Foundry writes this kind against whoever is
logged in and gives no way to write someone else's. So a profile carries the
values of the person who exported it, and the person importing gets them as
their own. You cannot move a player's settings for them.

The window counts each kind per package, so you know what a profile will carry
before you write it.

## Passwords and keys

Some modules keep an API token or a licence key in a setting. Values whose name
looks like one stay out of the file unless you tick the box. The test is the
name of the setting, so it catches the ordinary cases and nothing clever.

Read a profile before you share it.

## What happens when you import

Each value goes in on its own. A value that no longer fits its setting is
skipped, and so is a setting whose module is not installed in this world. Both
are normal when a profile is older than the world it lands in. The rest of the
import carries on, and the report names what was left out.

The window shows the count per scope for each package, so you know what a
profile will carry before you write it.
Export this world first if you want a way back.

## Module updates

Open **Settings → Configure Settings → Check for updates**.

![The Module Updates window. A Check now button, the time of the last check, and
one module listed with its installed version, the newer version, the release
notes and a link to the release page.](docs/updates-window.png)

The list shows every installed module, the version this world runs, and the
newest release found. A module that is behind shows the new version, the release
notes, and a link to the release page. Nothing here installs anything: use
Expand All @@ -60,47 +96,30 @@ from, one sends no permission header, one answers 404, and `api.github.com`
answers with the tag and the notes together. A module hosted anywhere else is
listed as unchecked, with the reason.

### Why there is a button
### Why nothing checks on its own

GitHub allows a browser sixty requests an hour without a token, and a
conditional request that comes back "not modified" still spends one. Thirty
modules would burn half of that on every check.

So nothing checks on its own. A result is kept for a day, opening the window
costs nothing, and the button asks for a fresh one. If a check runs out of
budget it stops there and says how many modules it left out. It does not keep
asking and it does not record a refusal as though those modules had been
So the check waits for you to ask. A result is kept for a day, opening the
window costs nothing, and the button asks for a fresh one. If a check runs out
of budget it stops there and says how many modules it left out. It does not keep
asking, and it does not record a refusal as though those modules had been
checked.

A personal access token would raise the ceiling. It would also mean this module
storing a credential, which is the thing the section below exists to keep out of
storing a credential, which is the thing the section above exists to keep out of
a file. So there is no token setting.

### Tags that are not versions
### Releases it cannot read

The newest release of a monorepo can be tagged `@scope/name@0.6.0`. That is a
tag, not a version, and comparing it to an installed version produces an answer
with no meaning. A tag that does not start with a version is reported as unreadable
rather than shown as the version to upgrade to.

## Credentials

Some modules keep an API token or a licence key in a setting. Values whose key
names one stay out of the file unless you tick the box. The check reads the key
name, so it catches the common cases and nothing clever.

Read a profile before you share it.

## What happens on import

Each value is applied on its own. A value that no longer fits its setting is
skipped, and so is a setting whose package is not installed in the target world.
Both are normal when a profile is older than the world it lands in. The rest of
the import continues, and the report names what was left out.

Export the target world first if you want a way back.
with no meaning. A tag that does not start with a version is reported as
unreadable rather than shown as the version to upgrade to.

## From a macro
## The API, for macros and modules

```js
const vault = game.modules.get('settings-vault').api;
Expand All @@ -109,15 +128,15 @@ const vault = game.modules.get('settings-vault').api;
const profile = vault.buildProfile({ namespaces: ['some-module'] });

// Write it back, and see what did not land.
const report = await vault.applyProfile(profile);
console.log(report.applied.length, report.skipped);
const imported = await vault.applyProfile(profile);
console.log(imported.applied.length, imported.skipped);

// Open the profile window.
vault.open();

// What the last update check found. Asks GitHub nothing.
const report = vault.lastReport();
console.log(report.outdatedCount, report.checkedAt);
const updates = vault.lastReport();
console.log(updates.outdatedCount, updates.checkedAt);

// Ask GitHub. One request per module with a release page. GM only, because
// the result is stored in the world.
Expand All @@ -127,14 +146,6 @@ await vault.checkUpdates({ force: true });
vault.openUpdates();
```

## Install

Paste this manifest URL into Foundry's module installer:

```
https://github.com/vttforge/settings-vault/releases/latest/download/module.json
```

## Build from source

```bash
Expand All @@ -161,7 +172,12 @@ value and applies a profile over it, runs one real check against GitHub, seeds a
higher version to draw the outdated row, and confirms both refusals: a player
cannot check, and a spent request budget stops the loop instead of caching a
refusal against every remaining module. It opens both windows and asserts the
console stayed clean. 26 checks.
console stayed clean.

Then it does the part one world cannot show. It exports a profile, creates a
second world on the same system, launches it, and imports there. The new world
starts at its defaults, and after the import it holds the value from the first.
32 checks.

Local only. Booting Foundry needs a licence and an account, so this cannot run
in CI on a fresh clone. It needs `docker` on the PATH, plus
Expand Down
Binary file added docs/updates-window.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/vault-window.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 6 additions & 4 deletions vite.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ export default defineConfig({
id: 'settings-vault',
kind: 'module',
entry: 'scripts/main.ts',
// `readme`, `changelog` e `license` no manifesto sao caminhos dentro da
// pasta do modulo, entao os tres arquivos tem que entrar no zip. A lista
// padrao do plugin nao os inclui, e nomear a lista substitui o padrao.
staticAssets: ['lang', 'templates', 'packs', 'README.md', 'CHANGELOG.md', 'LICENSE'],
// `readme`, `changelog` and `license` in the manifest are paths inside the
// module folder, so all three files have to be in the zip. The plugin's
// default list leaves them out, and naming the list replaces the default.
// `docs` goes in with them: the README points at the images there, and
// Foundry reads that README from inside the module folder.
staticAssets: ['lang', 'templates', 'packs', 'docs', 'README.md', 'CHANGELOG.md', 'LICENSE'],
}),
],
});
Loading