Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 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
71 changes: 69 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,11 @@ chel pin 2.0.0 dist/contracts/2.0.0/group.2.0.0.manifest.json
```json
{
"contracts": {
"chatroom": {
"gi.contracts/chatroom": {
"version": "2.0.6",
"path": "contracts/gi.contracts_chatroom/2.0.6/chatroom.2.0.6.manifest.json"
},
"group": {
"gi.contracts/group": {
"version": "2.0.0",
"path": "contracts/gi.contracts_group/2.0.0/group.2.0.0.manifest.json"
}
Expand Down Expand Up @@ -278,6 +278,73 @@ chel migrate --from sqlite --to redis --to-config redis.toml
chel migrate --from sqlite --from-config sqlite.toml --to redis --to-config redis.toml
```

## Configuration Files

The project uses two separate configuration files for different purposes:

### `chel.toml` — Runtime CLI Configuration

`chel.toml` configures the `chel` command itself at runtime. It is read by `nconf` with priority: CLI arguments > environment variables > `chel.toml` > defaults. It controls things like server host/port, database backend selection, and other operational settings.

```toml
# Example chel.toml
[server]
host = "0.0.0.0"
port = 8000
dashboardPort = 8888

[database]
backend = "sqlite"
```

### `chelonia.json` — App Properties

`chelonia.json` is an **app-level properties file** that describes which
contracts make up the application and their pinned versions. It is **not**
runtime configuration for `chel`. Instead, it is:

- **Created and updated** by `chel pin` when pinning contracts to specific
versions.
- **Read by the server** (`chel serve`) at startup to provide version
information and other app-specific settings to clients.

The file contains a `contracts` object keyed by the full contract name
(e.g. `gi.contracts/chatroom`), where each entry has:

| Field | Description |
|-------|-------------|
| `version` | The pinned version string for this contract |
| `path` | Relative path to the manifest file in the `contracts/` directory |

It may also contain an `appVersion` field for the overall application version.

**Example:**
```json
{
"appVersion": "2.0.0",
"contracts": {
"gi.contracts/chatroom": {
"version": "2.0.6",
"path": "contracts/gi.contracts_chatroom/2.0.6/chatroom.2.0.6.manifest.json"
},
"gi.contracts/group": {
"version": "2.0.0",
"path": "contracts/gi.contracts_group/2.0.0/group.2.0.0.manifest.json"
}
}
}
```

**Summary of differences:**

| | `chel.toml` | `chelonia.json` |
|---|---|---|
| **Purpose** | Runtime configuration of the `chel` CLI | App properties (e.g., contract versions) |
| **Managed by** | Manually by the developer / system administrator | Automatically by `chel pin` |
| **Read by** | `chel` commands (via `nconf`) | The server (at startup), values exposed to clients |
| **Format** | TOML | JSON |
| **Example content** | Database configuration | Contract versions |

## History

See [HISTORY.md](HISTORY.md)
Expand Down
60 changes: 38 additions & 22 deletions build/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -3094,6 +3094,7 @@ import path6 from "node:path";
import process9 from "node:process";
import { join as join72 } from "node:path";
import process10 from "node:process";
import { pathToFileURL } from "node:url";
import process11 from "node:process";
import process13 from "node:process";

Expand Down Expand Up @@ -87653,7 +87654,7 @@ var require_thread_stream = __commonJS({
var { EventEmitter } = __require2("events");
var { Worker: Worker2 } = __require2("worker_threads");
var { join: join92 } = __require2("path");
var { pathToFileURL } = __require2("url");
var { pathToFileURL: pathToFileURL2 } = __require2("url");
var { wait } = require_wait2();
var {
WRITE_INDEX,
Expand Down Expand Up @@ -87693,7 +87694,7 @@ var require_thread_stream = __commonJS({
...opts.workerOpts,
trackUnmanagedFds: false,
workerData: {
filename: filename.indexOf("file://") === 0 ? filename : pathToFileURL(filename).href,
filename: filename.indexOf("file://") === 0 ? filename : pathToFileURL2(filename).href,
dataBuf: stream[kImpl].dataBuf,
stateBuf: stream[kImpl].stateBuf,
workerData: {
Expand Down Expand Up @@ -108555,15 +108556,14 @@ var Hapi2;
var import_inert2;
var import_npm_chalk3;
var import_npm_nconf6;
var cheloniaAppManifest;
var ARCHIVE_MODE2;
var ownerSizeTotalWorker;
var creditsWorker;
var CONTRACTS_VERSION;
var GI_VERSION;
var hapi;
var appendToOrphanedNamesIndex;
var init_server = __esm({
"src/serve/server.ts"() {
async "src/serve/server.ts"() {
"use strict";
init_SPMessage();
init_chelonia();
Expand All @@ -108585,14 +108585,22 @@ var init_server = __esm({
init_pubsub2();
init_push();
import_npm_nconf6 = __toESM(require_nconf());
cheloniaAppManifest = await (async () => {
try {
return (await import(pathToFileURL(join72(process10.cwd(), "chelonia.json")).toString(), {
with: { type: "json" }
})).default;
} catch {
console.warn("`chelonia.json` unparsable or not found. Version information will be unavailable.");
}
})();
ARCHIVE_MODE2 = import_npm_nconf6.default.get("server:archiveMode");
if (CREDITS_WORKER_TASK_TIME_INTERVAL && OWNER_SIZE_TOTAL_WORKER_TASK_TIME_INTERVAL > CREDITS_WORKER_TASK_TIME_INTERVAL) {
process10.stderr.write("The size calculation worker must run more frequently than the credits worker for accurate billing");
process10.exit(1);
}
ownerSizeTotalWorker = ARCHIVE_MODE2 || !OWNER_SIZE_TOTAL_WORKER_TASK_TIME_INTERVAL ? void 0 : createWorker_default(join72(import.meta.dirname || ".", "serve", "ownerSizeTotalWorker.js"));
creditsWorker = ARCHIVE_MODE2 || !CREDITS_WORKER_TASK_TIME_INTERVAL ? void 0 : createWorker_default(join72(import.meta.dirname || ".", "serve", "creditsWorker.js"));
({ CONTRACTS_VERSION, GI_VERSION } = process10.env);
hapi = new Hapi2.Server({
// debug: false, // <- Hapi v16 was outputing too many unnecessary debug statements
// // v17 doesn't seem to do this anymore so I've re-enabled the logging
Expand Down Expand Up @@ -108874,8 +108882,10 @@ var init_server = __esm({
serverHandlers: {
connection(socket) {
const versionInfo = {
GI_VERSION: GI_VERSION || null,
CONTRACTS_VERSION: CONTRACTS_VERSION || null
appVersion: cheloniaAppManifest?.appVersion || null,
contractsVersion: cheloniaAppManifest?.contracts ? Object.fromEntries(
Object.entries(cheloniaAppManifest?.contracts).map(([k, v2]) => [k, v2.version])
) : null
};
socket.send(createNotification(NOTIFICATION_TYPE.VERSION_INFO, versionInfo));
}
Expand Down Expand Up @@ -109074,7 +109084,7 @@ var init_serve = __esm({
console.info(import_npm_chalk4.default.bold("backend startup sequence complete."));
resolve82();
});
Promise.resolve().then(() => (init_server(), server_exports)).catch(reject);
init_server().then(() => server_exports).catch(reject);
});
esm_default("okTurtles.events/once", SERVER_EXITING, () => {
esm_default("okTurtles.data/apply", PUBSUB_INSTANCE, function(pubsub) {
Expand Down Expand Up @@ -110607,10 +110617,12 @@ var module9 = {
return migrate(argv);
}
};
var RESERVED_FILE_CHARS = /[/\\:*?"<>|]/;
var RESERVED_FILE_CHARS_REPLACE = /[/\\:*?"<>|]/g;
var projectRoot;
var cheloniaConfig;
function sanitizeContractName(contractName) {
return contractName.replace(/[/\\:*?"<>|]/g, "_").replace(/\.\./g, "__");
return contractName.replace(RESERVED_FILE_CHARS_REPLACE, "_").replace(/\.\./g, "__");
}
async function pin(args) {
const version3 = args["manifest-version"];
Expand All @@ -110628,8 +110640,11 @@ async function pin(args) {
if (!existsSync(fullManifestPath)) {
exit(`Manifest file not found: ${manifestPath}`);
}
const { contractName, contractFiles, manifestVersion } = await parseManifest(fullManifestPath);
console.log(blue(`Contract name: ${contractName}`));
const { contractName, fullContractName, contractFiles, manifestVersion } = await parseManifest(fullManifestPath);
if (RESERVED_FILE_CHARS.test(manifestVersion)) {
exit(`Invalid manifest version: ${manifestVersion}`);
}
console.log(blue(`Contract name: ${fullContractName}`));
console.log(blue(`Manifest version: ${manifestVersion}`));
if (version3) {
if (version3 !== manifestVersion) {
Expand All @@ -110639,28 +110654,28 @@ async function pin(args) {
}
console.log(green(`\u2705 Version validation passed: ${version3}`));
}
const currentPinnedVersion = cheloniaConfig.contracts[contractName]?.version;
const currentPinnedVersion = cheloniaConfig.contracts[fullContractName]?.version;
if (currentPinnedVersion === manifestVersion) {
console.log(yellow(`\u2728 Contract ${contractName} is already pinned to version ${manifestVersion} - no action needed`));
console.log(yellow(`\u2728 Contract ${fullContractName} is already pinned to version ${manifestVersion} - no action needed`));
return;
}
if (currentPinnedVersion) {
console.log(cyan(`\u{1F4CC} Updating ${contractName} from version ${currentPinnedVersion} to ${manifestVersion}`));
console.log(cyan(`\u{1F4CC} Updating ${fullContractName} from version ${currentPinnedVersion} to ${manifestVersion}`));
} else {
console.log(cyan(`\u{1F4CC} Pinning ${contractName} to version ${manifestVersion} (first time)`));
console.log(cyan(`\u{1F4CC} Pinning ${fullContractName} to version ${manifestVersion} (first time)`));
}
const contractVersionDir = join62(projectRoot, "contracts", contractName, manifestVersion);
if (existsSync(contractVersionDir)) {
if (!args.overwrite) {
exit(`Version ${manifestVersion} already exists for contract ${contractName}. Use --overwrite to replace it.`);
exit(`Version ${manifestVersion} already exists for contract ${fullContractName}. Use --overwrite to replace it.`);
}
console.log(yellow(`Version ${manifestVersion} already exists for ${contractName} - checking files...`));
console.log(yellow(`Version ${manifestVersion} already exists for ${fullContractName} - checking files...`));
} else {
await createVersionDirectory(contractName, manifestVersion);
}
await copyContractFiles(contractFiles, manifestPath, contractName, manifestVersion, args);
await updateCheloniaConfig(contractName, manifestVersion, manifestPath);
console.log(green(`\u2705 Successfully pinned ${contractName} to version ${version3}`));
await updateCheloniaConfig(fullContractName, contractName, manifestVersion, manifestPath);
console.log(green(`\u2705 Successfully pinned ${contractName} to version ${manifestVersion}`));
console.log(gray(`Location: contracts/${contractName}/${manifestVersion}/`));
} catch (error2) {
exit(error2);
Expand All @@ -110682,6 +110697,7 @@ async function parseManifest(manifestPath) {
return {
contractName,
manifestVersion,
fullContractName,
contractFiles: {
main: mainFile,
slim: slimFile
Expand Down Expand Up @@ -110746,10 +110762,10 @@ async function loadCheloniaConfig() {
cheloniaConfig.contracts = {};
}
}
async function updateCheloniaConfig(contractName, version3, manifestPath) {
async function updateCheloniaConfig(fullContractName, contractName, version3, manifestPath) {
const manifestFileName = basename42(manifestPath);
const pinnedManifestPath = `contracts/${contractName}/${version3}/${manifestFileName}`;
cheloniaConfig.contracts[contractName] = {
cheloniaConfig.contracts[fullContractName] = {
version: version3,
path: pinnedManifestPath
};
Expand Down
35 changes: 22 additions & 13 deletions src/pin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@ import process from 'node:process'
import type { ArgumentsCamelCase, CommandModule } from './commands.ts'
import { exit } from './utils.ts'

const RESERVED_FILE_CHARS = /[/\\:*?"<>|]/
const RESERVED_FILE_CHARS_REPLACE = /[/\\:*?"<>|]/g

type Params = { overwrite: boolean, 'dir'?: string, 'manifest-version'?: string, manifest: string }

let projectRoot: string
let cheloniaConfig: { contracts: Record<string, { version: string, path: string }> }

function sanitizeContractName (contractName: string): string {
return contractName.replace(/[/\\:*?"<>|]/g, '_').replace(/\.\./g, '__')
return contractName.replace(RESERVED_FILE_CHARS_REPLACE, '_').replace(/\.\./g, '__')
}

export async function pin (args: ArgumentsCamelCase<Params>): Promise<void> {
Expand All @@ -36,8 +39,13 @@ export async function pin (args: ArgumentsCamelCase<Params>): Promise<void> {
exit(`Manifest file not found: ${manifestPath}`)
}

const { contractName, contractFiles, manifestVersion } = await parseManifest(fullManifestPath)
console.log(colors.blue(`Contract name: ${contractName}`))
const { contractName, fullContractName, contractFiles, manifestVersion } = await parseManifest(fullManifestPath)

if (RESERVED_FILE_CHARS.test(manifestVersion)) {
exit(`Invalid manifest version: ${manifestVersion}`)
}
Comment thread
corrideat marked this conversation as resolved.
Outdated

console.log(colors.blue(`Contract name: ${fullContractName}`))
console.log(colors.blue(`Manifest version: ${manifestVersion}`))

if (version) {
Expand All @@ -50,33 +58,33 @@ export async function pin (args: ArgumentsCamelCase<Params>): Promise<void> {
console.log(colors.green(`✅ Version validation passed: ${version}`))
}

const currentPinnedVersion = cheloniaConfig.contracts[contractName]?.version
const currentPinnedVersion = cheloniaConfig.contracts[fullContractName]?.version
if (currentPinnedVersion === manifestVersion) {
console.log(colors.yellow(`✨ Contract ${contractName} is already pinned to version ${manifestVersion} - no action needed`))
console.log(colors.yellow(`✨ Contract ${fullContractName} is already pinned to version ${manifestVersion} - no action needed`))
return
}

if (currentPinnedVersion) {
console.log(colors.cyan(`📌 Updating ${contractName} from version ${currentPinnedVersion} to ${manifestVersion}`))
console.log(colors.cyan(`📌 Updating ${fullContractName} from version ${currentPinnedVersion} to ${manifestVersion}`))
} else {
console.log(colors.cyan(`📌 Pinning ${contractName} to version ${manifestVersion} (first time)`))
console.log(colors.cyan(`📌 Pinning ${fullContractName} to version ${manifestVersion} (first time)`))
}

const contractVersionDir = join(projectRoot, 'contracts', contractName, manifestVersion)

if (existsSync(contractVersionDir)) {
if (!args.overwrite) {
exit(`Version ${manifestVersion} already exists for contract ${contractName}. Use --overwrite to replace it.`)
exit(`Version ${manifestVersion} already exists for contract ${fullContractName}. Use --overwrite to replace it.`)
}
console.log(colors.yellow(`Version ${manifestVersion} already exists for ${contractName} - checking files...`))
console.log(colors.yellow(`Version ${manifestVersion} already exists for ${fullContractName} - checking files...`))
} else {
await createVersionDirectory(contractName, manifestVersion)
}

await copyContractFiles(contractFiles, manifestPath, contractName, manifestVersion, args)
await updateCheloniaConfig(contractName, manifestVersion, manifestPath)
await updateCheloniaConfig(fullContractName, contractName, manifestVersion, manifestPath)

console.log(colors.green(`✅ Successfully pinned ${contractName} to version ${version}`))
console.log(colors.green(`✅ Successfully pinned ${fullContractName} to version ${manifestVersion}`))
console.log(colors.gray(`Location: contracts/${contractName}/${manifestVersion}/`))
} catch (error) {
exit(error)
Expand All @@ -103,6 +111,7 @@ async function parseManifest (manifestPath: string) {
return {
contractName,
manifestVersion,
fullContractName,
contractFiles: {
main: mainFile,
slim: slimFile
Expand Down Expand Up @@ -195,11 +204,11 @@ async function loadCheloniaConfig () {
}
}

async function updateCheloniaConfig (contractName: string, version: string, manifestPath: string) {
async function updateCheloniaConfig (fullContractName: string, contractName: string, version: string, manifestPath: string) {
const manifestFileName = basename(manifestPath)
const pinnedManifestPath = `contracts/${contractName}/${version}/${manifestFileName}`

cheloniaConfig.contracts[contractName] = {
cheloniaConfig.contracts[fullContractName] = {
version,
path: pinnedManifestPath
}
Expand Down
Loading
Loading