Pre-AlphaMetaScript is in early design phase. The compiler is not yet available.Join Discord for updates
MetaScript

MetaScript Build

Configure your MetaScript project using build.ms - a fully typed MetaScript file.

Philosophy

"You already have a perfectly good programming language. Why invent another one for configuration?"

MetaScript follows Zig's approach: no separate config language. Your build.ms IS MetaScript code with full IDE support and compile-time validation.

File Structure

Minimal Project

my-project/
├── build.ms        # Package config & deps
├── ms.lock         # Lockfile (generated)
└── src/
    └── main.ms

With Complex Build Logic

my-project/
├── build.ms        # Simple, flat, CLI-editable
├── build/
│   ├── utils.ms    # Complex logic
│   ├── tasks.ms    # Task definitions
│   └── targets.ms  # Build targets
├── ms.lock
└── src/
    └── main.ms

Convention: Keep build.ms flat. Offload complexity to build/*.ms. This keeps msc add/remove working.

Minimal Example

// build.ms - Simplest possible package

export const package = {
  name: "hello",
  version: "1.0.0",
};

export const deps = {};

No imports needed. Just object literals. Fully typed.

Standard Example

// build.ms - Typical project

export const package = {
  name: "my-app",
  version: "1.0.0",
  edition: "2025",
  license: "MIT",
  backends: ["c", "js"],
};

// Cross-platform dependencies (minimum versions, MVS)
export const deps = {
  json: "1.5.0",
  http: "2.0.0",
};

// C-backend specific
export const deps_c = {
  libuv: { version: "1.48.0", native: true },
};

// JS-backend specific (npm packages)
export const deps_js = {
  "npm:express": "4.18.0",
  "npm:lodash": "4.17.0",
};

// Erlang-backend specific (hex packages)
export const deps_erlang = {
  "hex:cowboy": "2.10.0",
};

// Development dependencies
export const deps_dev = {
  "test-runner": "1.0.0",
};

// Compile-time macros
export const deps_macro = {
  derive: "1.0.0",
};

Package Metadata

export const package = {
  // Required
  name: "my-app",
  version: "1.0.0",

  // Recommended
  edition: "2025",
  license: "MIT",
  backends: ["c", "js", "erlang"],

  // Optional
  description: "What the package does",
  author: "Name <email@example.com>",
  repository: "https://github.com/user/repo",
  homepage: "https://docs.example.com",
  keywords: ["utility", "helper"],
};

Supported Backends

BackendDescription
"c"Native binary via C
"js"JavaScript (ES2020+)
"erlang"BEAM VM
"raiser"Raiser VM (scripting, hot-reload)

Dependency Variables

VariablePurposeCLI Managed
depsCross-platform depsYes
deps_cC-backend depsYes
deps_jsJS-backend depsYes
deps_erlangErlang-backend depsYes
deps_devDev dependenciesYes
deps_macroMacro dependenciesYes

Rule: deps* = simple literals (CLI-editable). Everything else = user-controlled.

Version Selection (MVS)

MetaScript uses Minimal Version Selection from Go. No semver ranges (^, ~).

export const deps = {
  json: "1.5.0",   // means "I need at least 1.5.0"
};

How MVS Works

Your build.ms:     json: "1.5.0"   (at least 1.5.0)
Dependency A:      json: "1.3.0"   (at least 1.3.0)
Dependency B:      json: "1.6.0"   (at least 1.6.0)

MVS selects:       json@1.6.0     (minimum satisfying ALL)
npm would select:  json@1.9.0     (latest in range)

Why MVS:

  • Simple: No range syntax to learn
  • Fast: O(n) graph traversal
  • Reproducible: Same inputs = same outputs
  • Predictable: You know what "1.5.0" means

Dependency Sources

export const deps = {
  // Registry - minimum version
  json: "1.5.0",
  http: "2.0.0",

  // Git - pinned by content hash
  router: {
    url: "git+https://github.com/user/router#v1.0.0",
    hash: "sha256:abc123...",
  },

  // Git branch (resolved to commit at fetch time)
  experimental: {
    url: "git+https://github.com/user/lib#main",
    hash: "sha256:def456...",
  },

  // Tarball - pinned by hash
  legacy: {
    url: "https://example.com/legacy-1.0.tar.gz",
    hash: "sha256:ghi789...",
  },

  // Local path (monorepo/development)
  shared: {
    path: "../libs/shared",
  },
};

Source Prefixes

PrefixSourceExample
(none)MetaScript registryjson: "1.5.0"
npm:npmjs.org"npm:lodash": "4.17.0"
hex:hex.pm"hex:cowboy": "2.10.0"
git+Git repository{ url: "git+https://...", hash: "..." }
https://Tarball URL{ url: "https://...", hash: "..." }
path:Local path{ path: "../libs/shared" }

CLI Operations

Add Dependencies

msc add json                  # Add to deps (latest version)
msc add json@2.0.0            # Specific minimum version
msc add --c libuv             # Add to deps_c
msc add --js npm:express      # Add to deps_js
msc add --erlang hex:cowboy   # Add to deps_erlang
msc add -D test-runner        # Add to deps_dev
msc add --macro derive        # Add to deps_macro

Add from Git/URL

msc fetch --save git+https://github.com/user/router#v1.0.0
msc fetch --save git+https://github.com/user/lib#main
msc fetch --save https://example.com/pkg.tar.gz

Remove Dependencies

msc remove json               # Remove from deps
msc remove --c libuv          # Remove from deps_c

Update Dependencies

msc update                    # Update all deps
msc update json               # Update specific dep

Complex Build Logic

Keep build.ms flat, import complex logic:

// build.ms
import { createTasks } from "./build/tasks.ms";
import { computePlatformDeps } from "./build/utils.ms";

export const package = {
  name: "my-app",
  version: "1.0.0",
  backends: ["c", "js", "erlang"],
};

// CLI manages these (simple literals)
export const deps = {
  json: "1.5.0",
  http: "2.0.0",
};

// Complex logic imported, not inline
export const dependencies = computePlatformDeps(deps);
export const tasks = createTasks();
// build/utils.ms
import { env, target } from "@ms/build";

export function computePlatformDeps(baseDeps: Record<string, string>) {
  return {
    ...baseDeps,
    ...(target.os === "linux" && { epoll: "1.0.0" }),
    ...(target.os === "macos" && { kqueue: "1.0.0" }),
    ...(env("CI") && { "ci-reporter": "1.0.0" }),
  };
}

Lockfile (ms.lock)

Generated file that pins exact versions:

{
  "version": 1,
  "generated": "2025-12-10T10:00:00Z",
  "metascript": "0.1.0",

  "packages": {
    "json@1.5.0": {
      "version": "1.5.0",
      "source": "registry+https://pkg.metascriptlang.org",
      "integrity": "sha256:abc123...",
      "backends": ["c", "js", "erlang"]
    }
  }
}

Properties:

  • Content-addressed integrity hashes
  • Source provenance per package
  • Backend tags for each dependency
  • Generated, not hand-edited

Example Configurations

Library Package

export const package = {
  name: "@yourname/utils",
  version: "1.0.0",
  license: "MIT",
  backends: ["c", "js"],
};

export const deps = {
  json: "1.5.0",
};

export const deps_dev = {
  "test-runner": "1.0.0",
};

CLI Application

export const package = {
  name: "my-cli",
  version: "1.0.0",
  backends: ["c"],
};

export const deps = {
  args: "1.0.0",
  fs: "1.0.0",
};

export const deps_c = {
  libuv: { version: "1.48.0", native: true },
};

Web Application

export const package = {
  name: "my-web-app",
  version: "1.0.0",
  backends: ["js"],
};

export const deps = {
  http: "2.0.0",
  json: "1.5.0",
};

export const deps_js = {
  "npm:express": "4.18.0",
  "npm:cors": "2.8.0",
};

Erlang/OTP Application

export const package = {
  name: "my-otp-app",
  version: "1.0.0",
  backends: ["erlang"],
};

export const deps = {
  json: "1.5.0",
};

export const deps_erlang = {
  "hex:cowboy": "2.10.0",
  "hex:jason": "1.4.0",
};

Game Scripts (Raiser)

export const package = {
  name: "my-game-scripts",
  version: "1.0.0",
  backends: ["raiser"],
};

export const deps = {
  math: "1.0.0",
};

export const raiser = {
  hotReload: true,
  debug: true,
};

Build Commands

msc build                     # Build all backends
msc build -t c                # Build C backend
msc build -t js               # Build JS backend
msc build -t erlang           # Build Erlang backend
msc build -t raiser           # Build Raiser bytecode

Running with Raiser

msc run --target=raiser src/main.ms     # Run directly
msc run --watch --target=raiser src/    # Watch mode with hot-reload

Best Practices

  1. Keep build.ms flat - Simple object literals for deps
  2. Use build/*.ms for complex logic - Keeps CLI editing working
  3. Pin Git deps by hash - Reproducible builds
  4. Use MVS mindset - Specify minimum versions you need
  5. Commit ms.lock - Reproducible builds for your team

Next Steps