Status: This is the contract for the compiler package. Agents implementing the compiler MUST follow this spec exactly. Any deviation needs to come back to the Tech Lead first.
| Field | Value |
|---|---|
| Name | @keyyard/bedrock-build |
| Initial version | 0.1.0 (will hit 1.0.0 when paired with create-mc-bedrock@2.0) |
| License | MIT |
| Type | module (ESM) |
| Engines | "node": ">=18" |
| Binary | bedrock-build |
| Entry | dist/cli.js |
| Main | dist/index.js (for programmatic use) |
| Files published | dist/, README.md, LICENSE |
Usage:
bedrock-build <command> [options]
Commands:
build Compile sources and copy packs to dist/
watch Build, then rebuild on file changes (no deploy)
deploy Build, then copy dist/packs to com.mojang/development_*_packs/
pack Build --release, then zip into .mcaddon
folders Interactive picker that scaffolds canonical pack folders
Global options:
-c, --config <path> Path to bedrock.config.json (default: ./bedrock.config.json)
-v, --verbose Verbose logging
-h, --help Show help
--version Show version
Global flags apply to every subcommand. --config resolves the config path for build, watch, deploy, and pack uniformly.
bedrock-build build [options]
--release Minified, no sourcemaps, NODE_ENV=production
(default: dev mode — sourcemaps on, no minify)
--clean Remove dist/ before building (default: false)
bedrock-build watch [options]
(no watch-specific flags for v1)
Watch mode never minifies. Watch mode does NOT deploy — use deploy --watch for that.
bedrock-build deploy [options]
--watch Rebuild and re-deploy on file changes
--release Build in release mode before deploying
(default: dev mode)
bedrock-build pack [options]
--output <path> Override output .mcaddon path
(default: dist/<name>-<version>.mcaddon)
pack always implies --release. There is no --dev-pack.
bedrock-build folders
(no flags — interactive)
folders is interactive only. There is no non-interactive form for v1.
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Generic error (config missing, build failure, etc.) |
| 2 | Invalid config file (schema validation failure) |
| 3 | Deploy target not found (com.mojang dir doesn't exist) |
| 4 | Pack failure (zip step failed) |
interface BedrockConfig {
/** Project name, used for .mcaddon filename and manifest header.name */
name: string;
/** Project version, used for .mcaddon filename */
version: string;
/** Pack source directories (relative to config file) */
packs: {
bp: string; // default: "packs/BP"
rp: string; // default: "packs/RP"
};
/** TS entry point for the script module (relative to config file) */
entry: string; // default: "src/main.ts"
/** Build output directory (relative to config file) */
out: string; // default: "dist"
/** Deploy configuration */
deploy: {
target: "retail" | "custom"; // default: "retail"
customPath: string | null; // required when target === "custom"
};
/** Minecraft scripting API target version */
minecraft?: {
serverVersion?: string; // e.g., "1.19.0" — used for hints, not enforced
};
}nameandversionare required.versionmust be valid semver.packs.bpandpacks.rpmust exist as directories on disk at build time.- Each pack directory must contain a
manifest.json. entrymust exist as a file at build time.- If
deploy.target === "custom",deploy.customPathmust be a non-empty string. Ifdeploy.target === "retail",customPathis ignored.
If a field is omitted, apply defaults silently. Only name and version are required to be present.
<project>/
bedrock.config.json
package.json
tsconfig.json
src/
main.ts ← entry
packs/
BP/
manifest.json
blocks/ entities/ items/ ...
RP/
manifest.json
textures/ models/ ...
dist/ ← gitignored, owned by bedrock-build
The compiler does NOT regenerate manifest UUIDs at build time. UUIDs are frozen by the scaffolder and committed to git. The compiler reads them as-is.
- Load and validate config.
- If
--clean, remove<out>/recursively. - Bundle
<entry>via esbuild:- Target:
es2020 - Format:
esm - Platform:
neutral - Bundle:
true - External:
["@minecraft/server", "@minecraft/server-ui", "@minecraft/server-net", "@minecraft/server-admin", "@minecraft/server-gametest"] - Sourcemap:
inline(dev) /false(release) - Minify:
false(dev) /true(release) - Output:
<out>/packs/BP/scripts/main.js
- Target:
- Copy
<packs.bp>/*into<out>/packs/BP/. Note: source workspaces SHOULD NOT have a<packs.bp>/scripts/directory — the script module is built from<entry>and emitted directly to<out>/packs/BP/scripts/main.js. If ascripts/folder exists under<packs.bp>/, skip it during copy to avoid clobbering the bundled output (defensive only). - Copy
<packs.rp>/*into<out>/packs/RP/. - Log success with elapsed time.
Side effects: Only writes under <out>/. Never touches source files.
Same as build (dev mode), then watch the following with chokidar:
<entry>and everything it imports (via esbuild watch context)<packs.bp>/**/*(excluding<packs.bp>/scripts/)<packs.rp>/**/*
On change:
- Source file → trigger esbuild rebuild (incremental).
- Pack file → copy the changed file to its dist mirror.
Debounce: 50ms. Log each rebuild with timestamp + what changed.
Ctrl+C exits cleanly with code 0.
- Run
build(respecting--release). - Resolve deploy target:
retail(Windows): iterate the candidate path list below in order and return the first that exists as a directory. If none match, throw with exit code 3 and a message listing every path checked.custom: usedeploy.customPathdirectly. Throw with exit code 3 if path doesn't exist.
- Compute target subdirs:
- BP
=><comMojang>/development_behavior_packs/<name>/ - RP
=><comMojang>/development_resource_packs/<name>/
- BP
- Remove the target subdirs (clean slate), then copy
<out>/packs/BP/and<out>/packs/RP/into them. - If
--watch, enter watch mode and re-deploy on each rebuild.
Mirrors the detection logic in Microsoft's @minecraft/core-build-tasks so any Bedrock install layout it supports also works here.
| # | Layout | Path |
|---|---|---|
| 1 | Minecraft Bedrock launcher (modern) | %APPDATA%\Minecraft Bedrock\Users\Shared\games\com.mojang |
| 2 | Minecraft Bedrock Preview launcher | %APPDATA%\Minecraft Bedrock Preview\Users\Shared\games\com.mojang |
| 3 | Microsoft Store UWP (legacy) | %LOCALAPPDATA%\Packages\Microsoft.MinecraftUWP_8wekyb3d8bbwe\LocalState\games\com.mojang |
| 4 | Microsoft Store UWP Beta | %LOCALAPPDATA%\Packages\Microsoft.MinecraftWindowsBeta_8wekyb3d8bbwe\LocalState\games\com.mojang |
| 5 | Minecraft Education (UWP) | %LOCALAPPDATA%\Packages\Microsoft.MinecraftEducationEdition_8wekyb3d8bbwe\LocalState\games\com.mojang |
| 6 | Minecraft Education (desktop) | %APPDATA%\Minecraft Education Edition\games\com.mojang |
Fallback after the priority list misses: glob %LOCALAPPDATA%\Packages\Microsoft.MinecraftUWP_* case-insensitively (handles unusual publisher hashes or case variants). Return the first LocalState/games/com.mojang that exists.
Cross-platform note: Phase 1 supports Windows retail only. Custom path works on all platforms. Mac/Linux retail detection is a P2 (not 2.0).
On non-Windows platforms with deploy.target === "retail", throw with exit code 3 and the message: "Retail deploy is Windows-only for v1.0. Set deploy.target='custom' and deploy.customPath to your Minecraft data directory."
- Run
build --release. - Create zip with contents:
(Two subfolders inside the zip, named after the project + pack type. Minecraft accepts this layout.)
<name>-<version>.mcaddon <name>_BP/ ← contents of dist/packs/BP/ <name>_RP/ ← contents of dist/packs/RP/ - Output to
<out>/<name>-<version>.mcaddon(or--outputif given). - Log path + file size.
Use archiver for zipping. No native zip binary dependency.
- Load and validate config (same loader as the other commands).
- Show two
@clack/promptsmultiselectprompts in order: Behavior pack folders, then Resource pack folders. - Each option is a canonical Bedrock subfolder (see lists below). Folders that already exist on disk are still listed but suffixed with
(exists). - After both prompts resolve,
mkdir -pevery picked path. Already-existing picks are silently skipped. - Print a one-line summary:
✓ <N> created, <M> already existed.
Side effects: Only creates directories under <packs.bp>/ and <packs.rp>/. Never deletes, never writes files. Cancel (Ctrl+C / Esc) aborts before any mkdir.
No .gitkeep is created. Empty directories will not survive git add. This is intentional: users who pick a folder will populate it before committing.
animation_controllers, animations, biomes, blocks, cameras/presets, dialogue, entities (plural), feature_rules, features, functions, items, loot_tables, recipes, spawn_rules, structures, texts, trading.
scripts/ is intentionally excluded — owned by the bundler.
animation_controllers, animations, attachables, entity (singular), fogs, font, materials, models/entity (singular), models/blocks, particles, render_controllers, sounds, texts, textures/blocks, textures/entity (singular), textures/items, ui.
The plural/singular split (BP/entities vs. RP/entity, textures/entity, models/entity) is the primary footgun this command exists to solve.
Production:
esbuild^0.24.0chokidar^4.0.0archiver^7.0.0picocolors^1.1.0 (for terminal coloring — small, no deps)@clack/prompts^0.7.0 (interactive picker for thefolderscommand)
Dev:
typescript^5.6.0@types/node^22.0.0tsup^8.0.0 (to build the package itself — outputsdist/cli.js+dist/index.js)
No other runtime deps. Do not add commander, yargs, cac, etc. — write the arg parser by hand. The flag surface is small enough that ~50 lines of code beats taking on a dep.
- Use
picocolorsfor color. - Format:
[bedrock-build] <emoji> <message> - Levels:
info(cyan):📦 Building...,🚀 Deploying to retail...success(green):✅ Built in 142mswarn(yellow):⚠️ Pack <name> missing pack_icon.pngerror(red):❌ Config validation failed: packs.bp does not exist
--verboseadds debug lines prefixed with[debug]in gray.
Do NOT implement:
- JSON minification
- Asset hashing
- Multi-environment manifests (dev/release with different UUIDs)
- Mac/Linux retail detection
- Bedrock Preview / Education Edition deploy
- Source maps in release builds
- A
bedrock-build initcommand (that'screate-mc-bedrock's job) - Manifest UUID rewriting (that's the scaffolder's job)
- TypeScript type checking (esbuild only transpiles; if users want tsc checking, they run
tsc --noEmitseparately)
These may come in 1.1+. Resist scope creep.
packages/bedrock-build/ ← or its own repo, TBD
src/
cli.ts ← entry: parse argv, dispatch
commands/
build.ts
watch.ts
deploy.ts
pack.ts
config.ts ← schema + loader + validator
logger.ts ← picocolors wrappers
paths.ts ← com.mojang resolution
bundler.ts ← esbuild config builder
copier.ts ← pack file copying
package.json
tsconfig.json
tsup.config.ts
README.md
LICENSE
import { build, watch, deploy, pack, loadConfig } from "@keyyard/bedrock-build";
const config = await loadConfig("./bedrock.config.json");
await build(config, { release: false, clean: true });Each command function takes (config, options) and returns Promise<void> (throws on error). Used by create-mc-bedrock's scaffold step to run an initial build.
Each command needs at minimum:
- One smoke test against a fixture workspace under
test/fixtures/basic-addon/ - Config loader: unit tests for valid + invalid configs (one per validation rule)
Use vitest. Run via npm test.