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
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ Run `marmotta --help` or `npx marmotta --help` for the built-in usage summary.
| `-o, --output-dir <path>` | `build`, `rebuild`, `clean` | Override `outputDir`. Relative paths are resolved from the project directory. |
| `--target <triple>` | `build`, `rebuild` | Zig target triple used for cross-compilation. |
| `--debug` | `build`, `rebuild` | Compile without optimization and include debug information (`-O0 -g` instead of `-O2`). |
| `--json` | Any command | Emit machine-readable JSON Lines instead of plain text. Recognized before or after the command. |
| `-h, --help` | Any command | Show help. |
| `-v, --version` | Top-level command | Show the Marmotta version. |

Expand All @@ -183,8 +184,37 @@ For example, request a build for ARM64 macOS:
npx marmotta build --target aarch64-macos
```

Use `--json` in CI when another process needs to consume Marmotta's output; see [Logging](#logging) for the record format and the `MARMOTTA_LOG` variable.

Use a target triple supported by Zig for the intended target. Cross-compilation does not run the resulting addon on the host; load it on a compatible target system.

### Logging

By default Marmotta writes plain text: command results on stdout and errors on stderr, with no level prefixes, so scripts that read `marmotta --version` or `marmotta list` keep working unchanged.

Pass `--json` when another process needs to consume the output, as in CI. The flag is recognized before or after the command, and each record is one JSON object on its own line (JSON Lines):

```sh
npx marmotta build --json
npx marmotta --json list
```

```json
{"level":"info","time":1730000000000,"message":"Addon built: /project/build/hello.node"}
{"level":"info","time":1730000000001,"data":{"versions":["0.13.0","0.14.0"]},"message":"Installed Zig versions"}
{"level":"error","time":1730000000002,"data":{"code":"CLI_ARGUMENT_ERROR","exitCode":2},"message":"Unknown option: --bogus"}
```

- `level` is a string such as `info`, `warn`, or `error`. Informational records go to stdout; warnings and errors go to stderr.
- `message` is the human-readable text, and `data` carries the record's structured context when there is any, such as the error `code` and `exitCode`, the installed `versions`, or a child process's `command` and `stream`.
- Command results, such as help, the version, `list`, confirmations and error messages, are always printed. Output from the Zig and `tar` child processes is captured and re-emitted as records tagged with `command` and `stream`, so every line printed in `--json` mode is valid JSON and compiler errors are never hidden. Lines from the child's stderr are `warn` records on stderr; lines from its stdout are `info` records on stdout. Lines keep their order within each stream, but the order between a child's stdout and stderr lines is not preserved.
- Set `MARMOTTA_LOG` to the minimum level for diagnostic records: `trace`, `debug`, `info`, `warn`, `error`, `fatal`, or `silent`. The default is `info`, which hides `debug` records: which Zig is used, where Zig is downloaded from, and each Zig or `tar` command line. An unrecognized value is ignored with a warning.

`MARMOTTA_LOG` filters diagnostics only; it never hides a command's result or its final error. It applies to both formats and does not change the shape of the output.

`--version` is a top-level request and cannot be combined with a command, so `marmotta build --version` fails with an argument error instead of printing the version and skipping the build.


## Zig toolchain

Without a `zigVersion` pin, Marmotta uses `zig` from `PATH` when available, then a locally managed version, and otherwise downloads the latest stable Zig release for the supported host platform into the Marmotta home directory (see [Marmotta home directory](#marmotta-home-directory)). If `zigVersion` is set, Marmotta ignores `PATH` and selects that exact managed version, downloading it if necessary. If the pinned version is not available for the host platform, the command fails instead of falling back to another version. If the download index includes a checksum, Marmotta verifies the downloaded archive. If `MARMOTTA_ZIG_DIR` is set, Marmotta uses only that Zig and never downloads one (see [Existing Zig installation](#existing-zig-installation)).
Expand Down
131 changes: 130 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@
"node": ">=20"
},
"dependencies": {
"node-api-headers": "^1.3.0"
"node-api-headers": "^1.3.0",
"pino": "^10.4.0"
},
"devDependencies": {
"@types/node": "^22.15.0",
Expand Down
7 changes: 4 additions & 3 deletions src/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { dirname, extname, join, resolve } from 'node:path';
import { loadProject, type ProjectConfig } from './project.js';
import { createBuildDirectory, ensureZig, runZig } from './toolchain.js';
import { hostImportsSource, readDefSymbols } from './windows-host.js';
import { logger } from './logger.js';

const require = createRequire(import.meta.url);
type NodeApiHeaders = { include_dir: string; def_paths: { node_api_def: string } };
Expand Down Expand Up @@ -33,7 +34,7 @@ async function compile(config: ProjectConfig, options: BuildOptions, cleanOnly:
const output = join(outputDir, `${config.name}.node`);
if (cleanOnly) {
await rm(output, { force: true });
console.log(`Removed ${output}`);
logger.output(`Removed ${output}`);
return;
}

Expand Down Expand Up @@ -82,7 +83,7 @@ async function compile(config: ProjectConfig, options: BuildOptions, cleanOnly:

await mkdir(dirname(output), { recursive: true });
await runZig(zig, args, options.directory);
console.log(`Addon built: ${output}`);
logger.output(`Addon built: ${output}`);
} finally {
await rm(temporaryDir, { recursive: true, force: true });
}
Expand All @@ -91,7 +92,7 @@ async function compile(config: ProjectConfig, options: BuildOptions, cleanOnly:
export async function configure(options: BuildOptions): Promise<ProjectConfig> {
const config = await loadProject(options.directory, { target: options.target });
await ensureZig(config.zigVersion);
console.log(`Configuration ready: ${config.name} (${config.sources.length} sources)`);
logger.output(`Configuration ready: ${config.name} (${config.sources.length} sources)`);
return config;
}

Expand Down
Loading
Loading