miga 0.1.3

Bedrock Addon Utility Package Manager
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
# 🐜 miga β€” Technical Reference

This document describes the internal architecture of `miga` for contributors
and maintainers.

---

## πŸ“‘ Table of Contents

- [High-level architecture]#-high-level-architecture
- [Binary entry point]#-binary-entry-point
- [CLI layer]#-cli-layer
- [Commands]#-commands
- [Compiler pipeline]#-compiler-pipeline
- [Registry protocol]#-registry-protocol
- [Version conflict resolution]#-version-conflict-resolution
- [Shared utilities]#-shared-utilities
- [File layout]#-file-layout
- [Key data structures]#-key-data-structures

---

## πŸ—οΈ High-level architecture

```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                  miga binary                    β”‚
β”‚                                                 β”‚
β”‚  main.rs  ──►  cli.rs (clap)                    β”‚
β”‚                   β”‚                             β”‚
β”‚          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚          β”‚        commands/                 β”‚   β”‚
β”‚          β”‚  init Β· add Β· fetch Β· run Β·      β”‚   β”‚
β”‚          β”‚  build Β· remove                  β”‚   β”‚
β”‚          β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚               β”‚               β”‚                 β”‚
β”‚         compiler/        registry/              β”‚
β”‚         (oxc pipeline)   (manifest + HTTP)      β”‚
β”‚               β”‚               β”‚                 β”‚
β”‚          utils/ ─────────────────────────────── β”‚
β”‚          fs Β· json Β· npm Β· env Β· builder Β·      β”‚
β”‚          output Β· tsconfig Β· net Β· project      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

All commands return `anyhow::Result<()>`. Errors bubble up to `main.rs` where a
single `output::error()` call formats and exits.

---

## πŸš€ Binary entry point

**`src/main.rs`**

1. Parses the CLI with `Cli::parse()` (clap derive).
2. Matches on `Commands` and delegates to the appropriate `commands::*::run()`.
3. On error: prints a formatted message via `utils::output::error()` and exits
   with code 1.

---

## ⌨️ CLI layer

**`src/cli.rs`**

Defines `Cli` and `Commands` using clap's derive macros.

| Subcommand | Struct variant                                          |
| ---------- | ------------------------------------------------------- |
| `init`     | `Commands::Init { namespace, name, project_type, yes }` |
| `add`      | `Commands::Add { packages }`                            |
| `fetch`    | `Commands::Fetch { module, version, update }`           |
| `run`      | `Commands::Run { no_watch }`                            |
| `build`    | `Commands::Build`                                       |
| `remove`   | `Commands::Remove { packages, all }`                    |

### `ProjectType` enum

```rust
#[derive(ValueEnum)]
pub enum ProjectType {
    Full,              // BP + scripts + RP (default)
    Behavior,          // BP + scripts only
    BehaviorScriptless,// BP data-driven only
    AddonScriptless,   // BP + RP, both without scripts
    Resource,          // RP only
}
```

Helper methods: `has_behavior()`, `has_resource()`, `has_scripts()`.

---

## πŸ”§ Commands

### πŸ†• `init` (`src/commands/init/`)

Refactored into a module directory with three files:

| File           | Responsibility                                                        |
| -------------- | --------------------------------------------------------------------- |
| `mod.rs`       | Main `run()` orchestration with project-type-aware conditional logic. |
| `prompts.rs`   | Interactive prompts, validation, `InitConfig` struct, defaults.       |
| `templates.rs` | All template content: manifests, licenses, README, static strings.    |

**Flow:**

1. `prompts::collect_config()` gathers metadata β€” either interactively
   (`dialoguer::Input` / `dialoguer::Select`) or from CLI args + defaults
   when `--yes` is set.
2. Validates namespace (lowercase, no spaces/colons) and name.
3. Creates the project directory and sets it as the working directory.
4. Based on `ProjectType`:
    - **`has_behavior()`** β†’ creates BP directory tree + manifest (scripted or
      scriptless depending on `has_scripts()`).
    - **`has_scripts()`** β†’ creates `scripts/` tree, `tsconfig.json`,
      entry-point files, and calls `commands::add::run()` to install
      `@minecraft/server`.
    - **`has_resource()`** β†’ creates RP directory tree + manifest + texture JSONs.
5. Writes `.miga/miga.json` (project manifest) and `.miga/modules.lock`.
6. Generates `.env`, `.env.template`, `.gitignore`, `LICENSE`, `README.md`.

**Templates** are split into:

- `const` strings for static content (`TSCONFIG`, `GITIGNORE`, `PACK_ICON_PNG`, etc.)
- `fn` helpers for dynamic content (`behavior_manifest_scripted()`,
  `behavior_manifest_scriptless()`, `resource_manifest()`, `readme()`, etc.)

---

### πŸ“˜ `add` (`src/commands/add.rs`)

Downloads TypeScript type packages from the npm registry.

1. `utils::net::is_online()` β€” connectivity check.
2. `utils::project::require_initialized()` β€” ensures `.miga/` exists.
3. For each package spec: calls `utils::npm::fetch_types()`.
4. Records `name β†’ version` in `.miga/miga.json` under `externals`.
5. Updates `behavior/manifest.json` dependencies block via
   `sync_behavior_manifest()`.

---

### πŸ“¦ `fetch` (`src/commands/fetch.rs`)

Installs modules from the miga registry with **version conflict resolution**.

1. Connectivity check.
2. `utils::project::require_initialized()`.
3. Supports multiple modes:
    - **No arguments** β†’ installs all modules listed in `miga.json`.
    - **Module name** β†’ installs that specific module (latest or `--version`).
    - **`--update`** β†’ updates one or all modules to their latest version.
4. For each module: resolves the full dependency tree via
   `registry::resolve_dependencies()`, which returns `Vec<ResolvedModule>` with
   populated `resolved_deps` maps.
5. For each resolved module, checks for **version conflicts** against the
   existing lock file (see [Version conflict resolution]#-version-conflict-resolution).
6. Downloads the archive and extracts to `.miga_modules/<name>/v<version>/`.
7. Records the module in `.miga/modules.lock` with its `resolved_deps`.

---

### πŸ”„ `run` (`src/commands/run.rs`)

Hot-reload watch loop.

1. Loads the project manifest and lock file via `builder::load_project()`
   and `project::load_lock()`.
2. Reads `SOURCE_MAPS` from `.env` via `dotenvy` (`true`/`1` = enabled).
3. Builds `dep_versions` from `builder::user_dep_versions()`.
4. Compiles and deploys:
    - `builder::process_behavior()` β€” compiles user TypeScript scripts
      (writes `.js.map` alongside `.js` when source maps are enabled).
    - `builder::process_resource()` β€” copies resource pack files.
    - `builder::process_dependencies()` β€” compiles versioned module code.
    - Copies outputs to Minecraft dev pack folders.
5. If `--no-watch` is set, exits. Otherwise, starts
   `notify::RecommendedWatcher` on `behavior/scripts/` and re-deploys
   on every change event.

---

### πŸ“¦ `build` (`src/commands/build.rs`)

Full compile and package for release.

1. Loads project manifest and lock file.
2. Builds with minification enabled and source maps disabled:
    - `builder::process_behavior()` β€” TypeScript β†’ minified JS.
    - `builder::process_resource()` β€” copies + minifies JSON.
    - `builder::process_dependencies()` β€” compiles module deps.
3. Syncs version from `.miga/miga.json` into both manifests.
4. `dist/` is cleaned, then `zip` archives the BP and RP into `.mcpack` files
   and combines both into a `.mcaddon`.

---

### πŸ—‘οΈ `remove` (`src/commands/remove.rs`)

Removes modules and/or external packages.

1. `utils::project::require_initialized()`.
2. Supports removing multiple packages at once, or `--all` to remove everything.
3. Detects whether a package is a registry module or an npm external.
4. Removes the module's versioned directories from `.miga_modules/`.
5. Removes the dependency edge from `behavior/manifest.json`.
6. Updates `.miga/modules.lock`, `.miga/miga.json` and `tsconfig.json`.

---

## ⚑ Compiler pipeline

**`src/compiler/mod.rs`**

Uses the [oxc](https://oxc.rs/) family of crates for zero-Node.js TypeScript
compilation.

```
.ts source
    β”‚
    β–Ό
oxc_parser::Parser::parse()          β€” produces AST
    β”‚
    β–Ό
oxc_semantic::SemanticBuilder        β€” scope & binding analysis, produces
                                       the Scoping passed to the Transformer
    β”‚
    β–Ό
oxc_transformer::Transformer         β€” TypeScript β†’ ES2020 JS
    β”‚
    β–Ό
(optional) oxc_minifier::Minifier    β€” dead-code elimination + mangling
    β”‚
    β–Ό
oxc_codegen::Codegen                 β€” emit final JS string
    β”‚                                    (+ optional SourceMap when enabled)
    β–Ό
CompileResult { code, source_map }   β€” returned to caller
```

### πŸ—ΊοΈ Source maps

When `CompileOptions::source_maps` is `true`, the codegen sets
`CodegenOptions::source_map_path` which activates oxc's internal
`SourcemapBuilder`. The resulting `SourceMap` is serialized via
`to_json_string()` and a `//# sourceMappingURL=<file>.map` comment
is appended to the output code.

- **`run`** β€” reads `SOURCE_MAPS` from `.env` (`true`/`1` = enabled).
- **`build`** β€” always disables source maps (release mode).

### πŸ”— Import rewriting

The compiler rewrites import specifiers based on the `dep_versions` map
in `CompileOptions`:

| Import type                     | Rewrite rule                                                            |
| ------------------------------- | ----------------------------------------------------------------------- |
| Relative (`./`, `../`)          | `.ts` extension β†’ `.js`                                                 |
| `minecraft:*`                   | Left as-is (engine-provided)                                            |
| Bare specifier (e.g. `"bimap"`) | Rewritten to `./libs/<name>/v<version>/<entry>.js` using `dep_versions` |

This ensures each module always resolves to the exact version it was installed
with, even when multiple versions coexist.

---

## 🌐 Registry protocol

**`src/registry/mod.rs`**

The registry base URL is read from `MIGA_REGISTRY_URL` (falls back to a
hard-coded default). Two HTTP endpoints are used:

| Endpoint                            | Purpose                                       |
| ----------------------------------- | --------------------------------------------- |
| `GET /registry.json`                | Global registry manifest listing all modules. |
| `GET /modules/<name>/<name>.tar.gz` | Module archive download.                      |

**`src/registry/manifest.rs`** defines `ProjectManifest` (`.miga/miga.json`),
`ModuleManifest` (per-module descriptor inside archives), `LockFile`,
`LockedModule`, `ResolvedModule`, and helper functions for dependency spec
parsing and semver comparison.

---

## πŸ”€ Version conflict resolution

When `fetch` encounters a module that is already installed at a different
version, it applies the following logic:

| Scenario                              | Default action                                 | User prompt        |
| ------------------------------------- | ---------------------------------------------- | ------------------ |
| **Same version** already installed    | ⏭️ Skip silently                               | β€”                  |
| **Same major**, different minor/patch | ⬆️ Prompt: upgrade / keep both / keep existing | Yes                |
| **Different major** (breaking change) | πŸ“¦ Keep both versions                          | Prompt to override |

This is implemented via the `ConflictAction` enum and `check_version_conflict()`
in `src/commands/fetch.rs`. The lock file supports multiple versions per module
name natively (see [Key data structures](#-key-data-structures)).

---

## 🧰 Shared utilities

| Module              | Key responsibilities                                                                                                                             |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `utils/project.rs`  | `require_initialized`, `load_manifest`, `save_manifest`, `load_lock`, `save_lock` β€” single source of truth for project I/O.                      |
| `utils/net.rs`      | `is_online()` β€” TCP probe to `1.1.1.1:53` with a 1500 ms timeout.                                                                                |
| `utils/builder.rs`  | `load_project()`, `process_behavior()`, `process_resource()`, `process_dependencies()`, `user_dep_versions()` β€” orchestrates compile and deploy. |
| `compiler/mod.rs`   | oxc-based TypeScript β†’ JS pipeline with versioned import rewriting.                                                                              |
| `utils/env.rs`      | Resolves `DeployPaths` from `.env` or platform defaults.                                                                                         |
| `utils/fs.rs`       | `ensure_dir`, `clean_dir`, `copy_dir`, `exists`, `write_if_not_exists`, `write_force`, `copy_force`.                                             |
| `utils/json.rs`     | `read_json`, `write_json`, `minify`, `to_unicode_escapes`.                                                                                       |
| `utils/npm.rs`      | Downloads `.d.ts` files from the npm registry.                                                                                                   |
| `utils/output.rs`   | Consistent terminal output helpers (`section`, `step`, `success`, `error`, `warn`).                                                              |
| `utils/tsconfig.rs` | Writes the TypeScript path-mapping `tsconfig.json` for module resolution (versioned paths).                                                      |

---

## πŸ“ File layout

```
src/
β”œβ”€β”€ main.rs                 πŸš€ Entry point
β”œβ”€β”€ cli.rs                  ⌨️  clap command definitions
β”œβ”€β”€ commands/
β”‚   β”œβ”€β”€ mod.rs              Re-exports
β”‚   β”œβ”€β”€ init/               πŸ†• Project scaffolding
β”‚   β”‚   β”œβ”€β”€ mod.rs          Orchestration + project-type logic
β”‚   β”‚   β”œβ”€β”€ prompts.rs      Interactive prompts & validation
β”‚   β”‚   └── templates.rs    Template content & static strings
β”‚   β”œβ”€β”€ add.rs              πŸ“˜ npm type packages
β”‚   β”œβ”€β”€ fetch.rs            πŸ“¦ Registry module installer
β”‚   β”œβ”€β”€ run.rs              πŸ”„ Hot-reload dev loop
β”‚   β”œβ”€β”€ build.rs            πŸ“¦ Release packaging
β”‚   └── remove.rs           πŸ—‘οΈ  Package removal
β”œβ”€β”€ compiler/
β”‚   └── mod.rs              ⚑ oxc TypeScript β†’ JS pipeline
β”œβ”€β”€ registry/
β”‚   β”œβ”€β”€ mod.rs              🌐 HTTP registry client
β”‚   └── manifest.rs         πŸ“‹ Data types & semver helpers
└── utils/
    β”œβ”€β”€ mod.rs              Module re-exports
    β”œβ”€β”€ builder.rs          πŸ—οΈ Orchestrates compile + deploy
    β”œβ”€β”€ env.rs              πŸ”§ Deploy path resolution
    β”œβ”€β”€ fs.rs               πŸ“‚ File-system helpers
    β”œβ”€β”€ json.rs             πŸ“‹ JSON helpers
    β”œβ”€β”€ net.rs              🌐 Connectivity probe
    β”œβ”€β”€ npm.rs              πŸ“˜ npm type downloader
    β”œβ”€β”€ output.rs           πŸ–₯️  Terminal output
    β”œβ”€β”€ project.rs          πŸ“‹ Project manifest I/O
    └── tsconfig.rs         πŸ“˜ tsconfig generator
```

---

## πŸ“Š Key data structures

### `ProjectManifest` (`.miga/miga.json`)

```json
{
    "name": "my-addon",
    "namespace": "woc",
    "version": "0.1.0",
    "modules": { "<module-name>": "<version>" },
    "externals": { "@minecraft/server": "2.4.0" }
}
```

### `LockFile` (`.miga/modules.lock`)

The lock file uses a **nested versioned structure**: each module name maps to
a map of versions, allowing multiple versions to coexist.

```json
{
    "modules": {
        "bimap": {
            "1.0.0": {
                "entry": "index.ts",
                "files": ["index.ts", "utils.ts"],
                "resolved_deps": {}
            }
        },
        "utils": {
            "1.0.0": {
                "entry": "index.ts",
                "files": ["index.ts"],
                "resolved_deps": { "bimap": "1.0.0" }
            },
            "2.0.0": {
                "entry": "index.ts",
                "files": ["index.ts"],
                "resolved_deps": { "bimap": "1.0.0" }
            }
        }
    }
}
```

### `LockedModule`

```rust
pub struct LockedModule {
    pub entry: String,
    pub files: Vec<String>,
    /// dep_name β†’ dep_version used by the compiler for import rewriting.
    pub resolved_deps: HashMap<String, String>,
}
```

### `ResolvedModule`

Returned by `registry::resolve_dependencies()` after resolving the full
dependency tree:

```rust
pub struct ResolvedModule {
    pub manifest: ModuleManifest,
    /// dep_name β†’ resolved_version
    pub resolved_deps: HashMap<String, String>,
}
```

### `ModuleManifest` (inside each module archive)

```json
{
    "name": "module-name",
    "version": "1.0.0",
    "description": "...",
    "license": "MIT",
    "entry": "index.ts",
    "files": ["index.ts"],
    "dependencies": ["other-module@1.0.0"]
}
```

### `CompileOptions`

```rust
pub struct CompileOptions {
    pub minify: bool,
    pub source_maps: bool,
    pub script_root: PathBuf, // Defaults to "scripts"
    /// Maps bare module names to dependency info for import path rewriting.
    pub dep_versions: HashMap<String, DependencyInfo>,
}

#[derive(Clone, Debug)]
pub struct DependencyInfo {
    pub version: String,
    pub entry: String,
}
```

### `CompileResult`

```rust
pub struct CompileResult {
    pub code: String,
    pub source_map: Option<String>,
}
```

### Helper functions (`src/registry/manifest.rs`)

| Function                   | Purpose                                                 |
| -------------------------- | ------------------------------------------------------- |
| `parse_dep_spec(spec)`     | Splits `"bimap@1.0.0"` into `("bimap", Some("1.0.0"))`. |
| `is_breaking_change(a, b)` | Returns `true` if the major versions differ.            |
| `semver_cmp(a, b)`         | Component-by-component semver `Ordering`.               |