@makinzm/mille 0.0.6 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,394 @@
1
+ # mille
2
+
3
+ > Architecture Checker — static analysis CLI for layered architecture rules
4
+
5
+ `mille` is a CLI tool that enforces **dependency rules for layered architectures** (Clean Architecture, Onion Architecture, Hexagonal Architecture, etc.).
6
+
7
+ It is implemented in Rust, supports multiple languages from a single TOML config, and is designed to run in CI/CD pipelines.
8
+
9
+ ## Features
10
+
11
+ | Feature | Status |
12
+ |---|---|
13
+ | Internal layer dependency check (`dependency_mode`) | ✅ |
14
+ | External library dependency check (`external_mode`) | ✅ |
15
+ | DI entrypoint method call check (`allow_call_patterns`) | ✅ |
16
+ | Rust support | ✅ |
17
+ | Go support | ✅ |
18
+ | Python support | ✅ |
19
+ | TypeScript / JavaScript support | ✅ |
20
+
21
+ ## How to Install
22
+
23
+ ### cargo (Rust users)
24
+
25
+ ```sh
26
+ cargo install mille
27
+ ```
28
+
29
+ ### pip / uv (Python users)
30
+
31
+ ```sh
32
+ # uv (recommended)
33
+ uv add --dev mille
34
+ uv run mille check
35
+
36
+ # pip
37
+ pip install mille
38
+ mille check
39
+ ```
40
+
41
+ The Python package is a native extension built with [maturin](https://github.com/PyO3/maturin) (PyO3). It provides both a CLI (`mille check`) and a Python API (`import mille; mille.check(...)`).
42
+
43
+ ### npm (Node.js users)
44
+
45
+ ```sh
46
+ npm install -g @makinzm/mille
47
+ mille check
48
+ ```
49
+
50
+ Or use it without installing globally:
51
+
52
+ ```sh
53
+ npx @makinzm/mille check
54
+ ```
55
+
56
+ Requires Node.js ≥ 18. The npm package bundles `mille.wasm` (the compiled Rust core) and runs it via Node.js's built-in `node:wasi` module — no native compilation or network access required at install time.
57
+
58
+ ### go install
59
+
60
+ ```sh
61
+ go install github.com/makinzm/mille/packages/go@latest
62
+ ```
63
+
64
+ The Go wrapper embeds `mille.wasm` (the compiled Rust core) and runs it via [wazero](https://wazero.io/) — a zero-dependency WebAssembly runtime. No network access or caching required; the binary is fully self-contained.
65
+
66
+ ### Direct binary download
67
+
68
+ Pre-built binaries for each platform are available on [GitHub Releases](https://github.com/makinzm/mille/releases):
69
+
70
+ | Platform | Archive |
71
+ |---|---|
72
+ | Linux x86_64 | `mille-<version>-x86_64-unknown-linux-gnu.tar.gz` |
73
+ | Linux arm64 | `mille-<version>-aarch64-unknown-linux-gnu.tar.gz` |
74
+ | macOS x86_64 | `mille-<version>-x86_64-apple-darwin.tar.gz` |
75
+ | macOS arm64 | `mille-<version>-aarch64-apple-darwin.tar.gz` |
76
+ | Windows x86_64 | `mille-<version>-x86_64-pc-windows-msvc.zip` |
77
+
78
+ ```sh
79
+ # Example: Linux x86_64
80
+ curl -L https://github.com/makinzm/mille/releases/latest/download/mille-<version>-x86_64-unknown-linux-gnu.tar.gz | tar xz
81
+ ./mille check
82
+ ```
83
+
84
+ ## Quick Start
85
+
86
+ ### 1. Create `mille.toml`
87
+
88
+ Place `mille.toml` in your project root:
89
+
90
+ **Rust project example:**
91
+
92
+ ```toml
93
+ [project]
94
+ name = "my-app"
95
+ root = "."
96
+ languages = ["rust"]
97
+
98
+ [[layers]]
99
+ name = "domain"
100
+ paths = ["src/domain/**"]
101
+ dependency_mode = "opt-in"
102
+ allow = []
103
+ external_mode = "opt-in"
104
+ external_allow = []
105
+
106
+ [[layers]]
107
+ name = "usecase"
108
+ paths = ["src/usecase/**"]
109
+ dependency_mode = "opt-in"
110
+ allow = ["domain"]
111
+ external_mode = "opt-in"
112
+ external_allow = []
113
+
114
+ [[layers]]
115
+ name = "infrastructure"
116
+ paths = ["src/infrastructure/**"]
117
+ dependency_mode = "opt-out"
118
+ deny = []
119
+ external_mode = "opt-out"
120
+ external_deny = []
121
+
122
+ [[layers]]
123
+ name = "main"
124
+ paths = ["src/main.rs"]
125
+ dependency_mode = "opt-in"
126
+ allow = ["domain", "infrastructure", "usecase"]
127
+ external_mode = "opt-in"
128
+ external_allow = ["clap"]
129
+
130
+ [[layers.allow_call_patterns]]
131
+ callee_layer = "infrastructure"
132
+ allow_methods = ["new", "build", "create", "init", "setup"]
133
+ ```
134
+
135
+ **Python project example:**
136
+
137
+ ```toml
138
+ [project]
139
+ name = "my-python-app"
140
+ root = "."
141
+ languages = ["python"]
142
+
143
+ [resolve.python]
144
+ src_root = "."
145
+ package_names = ["domain", "usecase", "infrastructure"]
146
+
147
+ [[layers]]
148
+ name = "domain"
149
+ paths = ["domain/**"]
150
+ dependency_mode = "opt-in"
151
+ allow = []
152
+ external_mode = "opt-out"
153
+ external_deny = []
154
+
155
+ [[layers]]
156
+ name = "usecase"
157
+ paths = ["usecase/**"]
158
+ dependency_mode = "opt-in"
159
+ allow = ["domain"]
160
+ external_mode = "opt-out"
161
+ external_deny = []
162
+
163
+ [[layers]]
164
+ name = "infrastructure"
165
+ paths = ["infrastructure/**"]
166
+ dependency_mode = "opt-out"
167
+ deny = []
168
+ external_mode = "opt-out"
169
+ external_deny = []
170
+ ```
171
+
172
+ **TypeScript / JavaScript project example:**
173
+
174
+ ```toml
175
+ [project]
176
+ name = "my-ts-app"
177
+ root = "."
178
+ languages = ["typescript"]
179
+
180
+ [resolve.typescript]
181
+ tsconfig = "./tsconfig.json"
182
+
183
+ [[layers]]
184
+ name = "domain"
185
+ paths = ["domain/**"]
186
+ dependency_mode = "opt-in"
187
+ allow = []
188
+ external_mode = "opt-out"
189
+ external_deny = []
190
+
191
+ [[layers]]
192
+ name = "usecase"
193
+ paths = ["usecase/**"]
194
+ dependency_mode = "opt-in"
195
+ allow = ["domain"]
196
+ external_mode = "opt-in"
197
+ external_allow = ["zod"]
198
+
199
+ [[layers]]
200
+ name = "infrastructure"
201
+ paths = ["infrastructure/**"]
202
+ dependency_mode = "opt-out"
203
+ deny = []
204
+ external_mode = "opt-out"
205
+ external_deny = []
206
+ ```
207
+
208
+ > Use `languages = ["javascript"]` for plain `.js` / `.jsx` projects (no `[resolve.typescript]` needed).
209
+
210
+ **Go project example:**
211
+
212
+ ```toml
213
+ [project]
214
+ name = "my-go-app"
215
+ root = "."
216
+ languages = ["go"]
217
+
218
+ [resolve.go]
219
+ module_name = "github.com/myorg/my-go-app"
220
+
221
+ [[layers]]
222
+ name = "domain"
223
+ paths = ["domain/**"]
224
+ dependency_mode = "opt-in"
225
+ allow = []
226
+
227
+ [[layers]]
228
+ name = "usecase"
229
+ paths = ["usecase/**"]
230
+ dependency_mode = "opt-in"
231
+ allow = ["domain"]
232
+
233
+ [[layers]]
234
+ name = "infrastructure"
235
+ paths = ["infrastructure/**"]
236
+ dependency_mode = "opt-out"
237
+ deny = []
238
+
239
+ [[layers]]
240
+ name = "cmd"
241
+ paths = ["cmd/**"]
242
+ dependency_mode = "opt-in"
243
+ allow = ["domain", "usecase", "infrastructure"]
244
+ ```
245
+
246
+ ### 2. Run `mille check`
247
+
248
+ ```sh
249
+ mille check
250
+ ```
251
+
252
+ Exit codes:
253
+
254
+ | Code | Meaning |
255
+ |---|---|
256
+ | `0` | No violations |
257
+ | `1` | One or more errors detected |
258
+ | `3` | Configuration file error |
259
+
260
+ ## Configuration Reference
261
+
262
+ ### `[project]`
263
+
264
+ | Key | Description |
265
+ |---|---|
266
+ | `name` | Project name |
267
+ | `root` | Root directory for analysis |
268
+ | `languages` | List of languages to check (e.g. `["rust", "go"]`) |
269
+
270
+ ### `[[layers]]`
271
+
272
+ | Key | Description |
273
+ |---|---|
274
+ | `name` | Layer name |
275
+ | `paths` | Glob patterns for files belonging to this layer |
276
+ | `dependency_mode` | `"opt-in"` (deny all except `allow`) or `"opt-out"` (allow all except `deny`) |
277
+ | `allow` | Layers allowed as dependencies (when `dependency_mode = "opt-in"`) |
278
+ | `deny` | Layers forbidden as dependencies (when `dependency_mode = "opt-out"`) |
279
+ | `external_mode` | `"opt-in"` or `"opt-out"` for external library usage |
280
+ | `external_allow` | Regex patterns of allowed external packages (when `external_mode = "opt-in"`) |
281
+ | `external_deny` | Regex patterns of forbidden external packages (when `external_mode = "opt-out"`) |
282
+
283
+ ### `[[layers.allow_call_patterns]]`
284
+
285
+ Restricts which methods may be called on a given layer's types. Only valid on the `main` layer.
286
+
287
+ | Key | Description |
288
+ |---|---|
289
+ | `callee_layer` | The layer whose methods are being restricted |
290
+ | `allow_methods` | List of method names that are permitted |
291
+
292
+ ### `[resolve.typescript]`
293
+
294
+ | Key | Description |
295
+ |---|---|
296
+ | `tsconfig` | Path to `tsconfig.json`. When specified, mille reads `compilerOptions.paths` and resolves path aliases (e.g. `@/*`) as internal imports. |
297
+
298
+ **How TypeScript / JavaScript imports are classified:**
299
+
300
+ | Import | Classification |
301
+ |---|---|
302
+ | `import X from "./module"` (starts with `./`) | Internal |
303
+ | `import X from "../module"` (starts with `../`) | Internal |
304
+ | `import X from "@/module"` (path alias in `tsconfig.json`) | Internal |
305
+ | `import X from "react"` (npm package) | External |
306
+ | `import fs from "node:fs"` (Node.js built-in) | External |
307
+
308
+ For relative imports, mille resolves the path from the importing file and matches it against layer glob patterns. For example, `import { User } from "../domain/user"` in `usecase/user_usecase.ts` resolves to `domain/user`, matching the layer glob `domain/**`.
309
+
310
+ For path aliases, mille expands the alias using `compilerOptions.paths` and treats the result as an internal import. For example, with `"@/*": ["./src/*"]`, `import { User } from "@/domain/user"` resolves to `src/domain/user`.
311
+
312
+ ### `[resolve.go]`
313
+
314
+ | Key | Description |
315
+ |---|---|
316
+ | `module_name` | Go module name (matches the module path in `go.mod`) |
317
+
318
+ ### `[resolve.python]`
319
+
320
+ | Key | Description |
321
+ |---|---|
322
+ | `src_root` | Root directory of the Python source tree (relative to `mille.toml`) |
323
+ | `package_names` | List of your own package names (used to classify absolute imports as internal). e.g. `["domain", "usecase", "infrastructure"]` |
324
+
325
+ **How Python imports are classified:**
326
+
327
+ | Import | Classification |
328
+ |---|---|
329
+ | `from .sibling import X` (relative) | Internal |
330
+ | `import domain.entity` (matches a `package_names` entry) | Internal |
331
+ | `import os`, `import sqlalchemy` (others) | External |
332
+
333
+ ## Python API
334
+
335
+ In addition to the CLI, the Python package exposes a programmatic API:
336
+
337
+ ```python
338
+ import mille
339
+
340
+ # Run architecture check and get a result object
341
+ result = mille.check("path/to/mille.toml") # defaults to "mille.toml"
342
+
343
+ print(f"violations: {len(result.violations)}")
344
+ for v in result.violations:
345
+ print(f" {v.file}:{v.line} {v.from_layer} -> {v.to_layer} ({v.import_path})")
346
+
347
+ for stat in result.layer_stats:
348
+ print(f" {stat.name}: {stat.file_count} file(s), {stat.violation_count} violation(s)")
349
+ ```
350
+
351
+ **Types exposed:**
352
+
353
+ | Class | Attributes |
354
+ |---|---|
355
+ | `CheckResult` | `violations: list[Violation]`, `layer_stats: list[LayerStat]` |
356
+ | `Violation` | `file`, `line`, `from_layer`, `to_layer`, `import_path`, `kind` |
357
+ | `LayerStat` | `name`, `file_count`, `violation_count` |
358
+
359
+ ## How it Works
360
+
361
+ mille uses [tree-sitter](https://tree-sitter.github.io/) for AST-based import extraction — no regex heuristics. The core engine is language-agnostic; language-specific logic is isolated to the `parser` and `resolver` layers.
362
+
363
+ ```
364
+ mille.toml
365
+
366
+
367
+ Layer definitions
368
+
369
+ Source files (*.rs, *.go, *.py, ...)
370
+ │ tree-sitter parse
371
+
372
+ RawImport list
373
+ │ Resolver (stdlib / internal / external)
374
+
375
+ ResolvedImport list
376
+ │ ViolationDetector
377
+
378
+ Violations → terminal output
379
+ ```
380
+
381
+ ## Dogfooding
382
+
383
+ mille checks its own source code on every CI run:
384
+
385
+ ```sh
386
+ mille check # uses ./mille.toml
387
+ ```
388
+
389
+ See [mille.toml](./mille.toml) for the architecture rules applied to mille itself.
390
+
391
+ ## Documentation
392
+
393
+ - [spec.md](./spec.md) — Full specification (in Japanese)
394
+ - [docs/TODO.md](./docs/TODO.md) — Development roadmap
package/index.js CHANGED
@@ -1,2 +1,52 @@
1
1
  #!/usr/bin/env node
2
- console.log("mille (dummy release)");
2
+ 'use strict';
3
+
4
+ // Suppress the WASI experimental warning so that mille's own stderr output
5
+ // is not mixed with Node.js internals noise.
6
+ process.on('warning', (w) => {
7
+ if (w.name === 'ExperimentalWarning' && /WASI/i.test(w.message)) return;
8
+ process.stderr.write(w.stack + '\n');
9
+ });
10
+
11
+ const { WASI } = require('node:wasi');
12
+ const { readFileSync } = require('node:fs');
13
+ const { join } = require('node:path');
14
+
15
+ async function run() {
16
+ const wasmPath = join(__dirname, 'mille.wasm');
17
+ let wasmBuffer;
18
+ try {
19
+ wasmBuffer = readFileSync(wasmPath);
20
+ } catch {
21
+ process.stderr.write(
22
+ 'mille: mille.wasm not found. Please reinstall the package.\n'
23
+ );
24
+ process.exit(3);
25
+ }
26
+
27
+ // NOTE: Mount the host CWD as "/" inside WASI so that paths like
28
+ // "mille.toml" and "src/domain/**" resolve correctly relative
29
+ // to the project root — same as the Go/wazero wrapper.
30
+ const wasi = new WASI({
31
+ version: 'preview1',
32
+ args: ['mille', ...process.argv.slice(2)],
33
+ env: process.env,
34
+ preopens: { '/': process.cwd() },
35
+ });
36
+
37
+ const { instance } = await WebAssembly.instantiate(wasmBuffer, {
38
+ wasi_snapshot_preview1: wasi.wasiImport,
39
+ });
40
+
41
+ // NOTE: wasi.start() calls process.exit() directly when the WASI module
42
+ // invokes proc_exit. Exit codes:
43
+ // 0 — no violations
44
+ // 1 — at least one error-severity violation
45
+ // 3 — configuration or runtime error
46
+ wasi.start(instance);
47
+ }
48
+
49
+ run().catch((err) => {
50
+ process.stderr.write('mille: ' + err.message + '\n');
51
+ process.exit(3);
52
+ });
package/mille.wasm ADDED
Binary file
package/package.json CHANGED
@@ -1,11 +1,18 @@
1
1
  {
2
2
  "name": "@makinzm/mille",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "description": "Architecture Checker — Rust-based multi-language architecture linter",
5
5
  "main": "index.js",
6
6
  "bin": {
7
7
  "mille": "index.js"
8
8
  },
9
+ "files": [
10
+ "index.js",
11
+ "mille.wasm"
12
+ ],
13
+ "engines": {
14
+ "node": ">=18.0.0"
15
+ },
9
16
  "repository": {
10
17
  "type": "git",
11
18
  "url": "git+https://github.com/makinzm/mille.git"