@bull-js/cli 0.0.0-stage → 0.3.0
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/LICENSE +21 -0
- package/README.md +39 -2
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +1578 -0
- package/dist/chunk-FHBUQ5I5.js +356 -0
- package/dist/chunk-KF3FEGRV.js +259 -0
- package/dist/create.d.ts +24 -0
- package/dist/create.js +13 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +8 -0
- package/package.json +59 -4
- package/templates/default/_gitignore +3 -0
- package/templates/default/bull.config.ts +5 -0
- package/templates/default/eslint.config.js +3 -0
- package/templates/default/index.html +12 -0
- package/templates/default/package.json +29 -0
- package/templates/default/src/env.d.ts +1 -0
- package/templates/default/src/main.tsx +14 -0
- package/templates/default/src/model/counter.repository.ts +12 -0
- package/templates/default/src/styles/app.css +52 -0
- package/templates/default/src/view/bull-logo.view.tsx +15 -0
- package/templates/default/src/view/counter.view.tsx +26 -0
- package/templates/default/src/viewmodel/counter.viewmodel.test.ts +36 -0
- package/templates/default/src/viewmodel/counter.viewmodel.ts +26 -0
- package/templates/default/tsconfig.json +16 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { BullArchitectureConfig, ProjectAnalyzer } from '@bull-js/core';
|
|
2
|
+
import { UserConfig, Plugin } from 'vite';
|
|
3
|
+
|
|
4
|
+
interface BullConfig extends BullArchitectureConfig {
|
|
5
|
+
dev?: {
|
|
6
|
+
port?: number;
|
|
7
|
+
open?: boolean;
|
|
8
|
+
/**
|
|
9
|
+
* How the browser overlay behaves when there are architecture violations.
|
|
10
|
+
* - `dismissible` (default): it can be closed to keep trying the app.
|
|
11
|
+
* - `blocking`: it cannot be closed until the code respects MVVM.
|
|
12
|
+
*/
|
|
13
|
+
overlay?: "dismissible" | "blocking" | false;
|
|
14
|
+
};
|
|
15
|
+
/** Extra Vite options (aliases, proxy, additional plugins…). */
|
|
16
|
+
vite?: UserConfig;
|
|
17
|
+
}
|
|
18
|
+
/** Type helper for `bull.config.ts`. */
|
|
19
|
+
declare function defineConfig(config: BullConfig): BullConfig;
|
|
20
|
+
|
|
21
|
+
interface BullPluginOptions {
|
|
22
|
+
/** Project root. Defaults to Vite's `root`. */
|
|
23
|
+
root?: string;
|
|
24
|
+
config?: BullArchitectureConfig;
|
|
25
|
+
/** An existing analyzer (the CLI shares it to avoid analyzing twice). */
|
|
26
|
+
analyzer?: ProjectAnalyzer;
|
|
27
|
+
/** Browser overlay in dev. Defaults to `dismissible`. */
|
|
28
|
+
overlay?: "dismissible" | "blocking" | false;
|
|
29
|
+
/** Check the architecture in `buildStart`. The CLI turns it off because it checks before calling Vite. */
|
|
30
|
+
checkOnBuild?: boolean;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Vite plugin that enforces the MVVM architecture.
|
|
34
|
+
* - On build: fails if there are architecture errors.
|
|
35
|
+
* - On dev: re-analyzes on every change and shows the report in the
|
|
36
|
+
* terminal and in a browser overlay.
|
|
37
|
+
*
|
|
38
|
+
* It can also be used on its own in a `vite.config.ts`.
|
|
39
|
+
*/
|
|
40
|
+
declare function bullPlugin(options?: BullPluginOptions): Plugin;
|
|
41
|
+
|
|
42
|
+
export { type BullConfig, type BullPluginOptions, bullPlugin, defineConfig };
|
package/dist/index.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,61 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bull-js/cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Bull: the opinionated React framework that refuses to build unless you respect MVVM.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://github.com/nicolasandradedesarrollosit/bull.js#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/nicolasandradedesarrollosit/bull.js.git",
|
|
11
|
+
"directory": "packages/cli"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/nicolasandradedesarrollosit/bull.js/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"react",
|
|
18
|
+
"mvvm",
|
|
19
|
+
"vite",
|
|
20
|
+
"framework",
|
|
21
|
+
"cli",
|
|
22
|
+
"architecture",
|
|
23
|
+
"linter"
|
|
24
|
+
],
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=20"
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"bin": {
|
|
32
|
+
"bull": "./dist/bin.js"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"dist",
|
|
36
|
+
"templates",
|
|
37
|
+
"!templates/**/node_modules"
|
|
38
|
+
],
|
|
39
|
+
"exports": {
|
|
40
|
+
".": {
|
|
41
|
+
"types": "./dist/index.d.ts",
|
|
42
|
+
"import": "./dist/index.js"
|
|
43
|
+
},
|
|
44
|
+
"./create": {
|
|
45
|
+
"types": "./dist/create.d.ts",
|
|
46
|
+
"import": "./dist/create.js"
|
|
47
|
+
},
|
|
48
|
+
"./package.json": "./package.json"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "tsup src/index.ts src/bin.ts src/create.ts --format esm --dts --clean --external @bull-js/core"
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"@bull-js/core": "^0.3.0",
|
|
55
|
+
"@clack/prompts": "^1.8.1",
|
|
56
|
+
"@vitejs/plugin-react": "^6.1.2",
|
|
57
|
+
"cac": "^6.7.14",
|
|
58
|
+
"picocolors": "^1.1.1",
|
|
59
|
+
"vite": "^8.3.4"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>{{name}}</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{name}}",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "bull",
|
|
8
|
+
"build": "bull build",
|
|
9
|
+
"preview": "bull preview",
|
|
10
|
+
"check": "bull check",
|
|
11
|
+
"test": "vitest run",
|
|
12
|
+
"lint": "eslint src",
|
|
13
|
+
"typecheck": "tsc --noEmit"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@bull-js/runtime": "^{{bullVersion}}",
|
|
17
|
+
"react": "^19.0.0",
|
|
18
|
+
"react-dom": "^19.0.0"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@bull-js/cli": "^{{bullVersion}}",
|
|
22
|
+
"@bull-js/eslint-plugin": "^{{bullVersion}}",
|
|
23
|
+
"@types/react": "^19.0.0",
|
|
24
|
+
"@types/react-dom": "^19.0.0",
|
|
25
|
+
"eslint": "^10.0.0",
|
|
26
|
+
"typescript": "^6.0.0",
|
|
27
|
+
"vitest": "^5.0.0"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
declare module "*.css";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Composition root: the only file that knows all three layers.
|
|
2
|
+
import "./styles/app.css";
|
|
3
|
+
import { createBullApp } from "@bull-js/runtime/react";
|
|
4
|
+
import { CounterRepository } from "./model/counter.repository";
|
|
5
|
+
import { CounterView } from "./view/counter.view";
|
|
6
|
+
import { CounterViewModel } from "./viewmodel/counter.viewmodel";
|
|
7
|
+
|
|
8
|
+
createBullApp({
|
|
9
|
+
root: CounterView,
|
|
10
|
+
register: (c) => {
|
|
11
|
+
c.model(CounterRepository, () => new CounterRepository());
|
|
12
|
+
c.viewModel(CounterViewModel, (c) => new CounterViewModel(c.get(CounterRepository)));
|
|
13
|
+
},
|
|
14
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Model: data and business rules. Knows nothing about React or the screen.
|
|
2
|
+
const KEY = "counter";
|
|
3
|
+
|
|
4
|
+
export class CounterRepository {
|
|
5
|
+
load(): number {
|
|
6
|
+
return Number(localStorage.getItem(KEY) ?? 0);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
save(value: number): void {
|
|
10
|
+
localStorage.setItem(KEY, String(value));
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
3
|
+
color: #1d1b22;
|
|
4
|
+
background: #f6f5f2;
|
|
5
|
+
}
|
|
6
|
+
@media (prefers-color-scheme: dark) {
|
|
7
|
+
:root {
|
|
8
|
+
color: #ecebf2;
|
|
9
|
+
background: #111016;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
body {
|
|
13
|
+
margin: 0;
|
|
14
|
+
}
|
|
15
|
+
.counter {
|
|
16
|
+
min-height: 100vh;
|
|
17
|
+
display: grid;
|
|
18
|
+
place-content: center;
|
|
19
|
+
justify-items: center;
|
|
20
|
+
text-align: center;
|
|
21
|
+
gap: 8px;
|
|
22
|
+
}
|
|
23
|
+
.layers {
|
|
24
|
+
color: #8f8b9c;
|
|
25
|
+
font-size: 13px;
|
|
26
|
+
letter-spacing: 0.04em;
|
|
27
|
+
margin: 0;
|
|
28
|
+
}
|
|
29
|
+
h1 {
|
|
30
|
+
font-size: 96px;
|
|
31
|
+
margin: 0;
|
|
32
|
+
letter-spacing: -0.04em;
|
|
33
|
+
}
|
|
34
|
+
.actions {
|
|
35
|
+
display: flex;
|
|
36
|
+
gap: 8px;
|
|
37
|
+
justify-content: center;
|
|
38
|
+
}
|
|
39
|
+
button {
|
|
40
|
+
font: inherit;
|
|
41
|
+
font-weight: 600;
|
|
42
|
+
padding: 10px 20px;
|
|
43
|
+
border-radius: 12px;
|
|
44
|
+
cursor: pointer;
|
|
45
|
+
border: 1px solid #6b4dff;
|
|
46
|
+
background: #6b4dff;
|
|
47
|
+
color: white;
|
|
48
|
+
}
|
|
49
|
+
button:disabled {
|
|
50
|
+
opacity: 0.4;
|
|
51
|
+
cursor: default;
|
|
52
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** The Bull logo: three facets for the three layers. */
|
|
2
|
+
export function BullLogoView(props: { size?: number }) {
|
|
3
|
+
const size = props.size ?? 28;
|
|
4
|
+
return (
|
|
5
|
+
<svg width={size} height={size} viewBox="0 0 64 64" role="img" aria-label="Bull">
|
|
6
|
+
<path d="M19 20C10.5 20 4.5 15 4 5.5c3.5 5.8 9 8.6 17 9.2Z" fill="#ffc9a8" />
|
|
7
|
+
<path d="M45 20c8.5 0 14.5-5 15-14.5c-3.5 5.8-9 8.6-17 9.2Z" fill="#ffc9a8" />
|
|
8
|
+
<path d="M18 15h28L32 36Z" fill="#a78bfa" />
|
|
9
|
+
<path d="M18 15l14 21v21h-6L20.5 34Z" fill="#6b4dff" />
|
|
10
|
+
<path d="M46 15 32 36v21h6l5.5-23Z" fill="#f0507c" />
|
|
11
|
+
<rect x="23" y="45" width="18" height="12" rx="6" fill="#33224a" />
|
|
12
|
+
<path d="M28.5 56.5a3.5 3.5 0 0 0 7 0" fill="none" stroke="#ffc9a8" strokeWidth="1.8" strokeLinecap="round" />
|
|
13
|
+
</svg>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// View: only binds and draws. Reads with useBinding, acts with commands.
|
|
2
|
+
import { useBinding, useViewModel } from "@bull-js/runtime/react";
|
|
3
|
+
import { CounterViewModel } from "../viewmodel/counter.viewmodel";
|
|
4
|
+
import { BullLogoView } from "./bull-logo.view";
|
|
5
|
+
|
|
6
|
+
export function CounterView() {
|
|
7
|
+
const vm = useViewModel(CounterViewModel);
|
|
8
|
+
const count = useBinding(vm.count);
|
|
9
|
+
const canReset = useBinding(vm.reset.canExecute);
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<main className="counter">
|
|
13
|
+
<BullLogoView size={56} />
|
|
14
|
+
<p className="layers">View → ViewModel → Model</p>
|
|
15
|
+
<h1>{count}</h1>
|
|
16
|
+
<div className="actions">
|
|
17
|
+
<button type="button" onClick={vm.increment.execute}>
|
|
18
|
+
+1
|
|
19
|
+
</button>
|
|
20
|
+
<button type="button" onClick={vm.reset.execute} disabled={!canReset}>
|
|
21
|
+
Reset
|
|
22
|
+
</button>
|
|
23
|
+
</div>
|
|
24
|
+
</main>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// The ViewModel is tested as a plain class: no React, no DOM, with an in-memory repository.
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import type { CounterRepository } from "../model/counter.repository";
|
|
4
|
+
import { CounterViewModel } from "./counter.viewmodel";
|
|
5
|
+
|
|
6
|
+
function memoryRepository(initial = 0): CounterRepository & { stored: number } {
|
|
7
|
+
return {
|
|
8
|
+
stored: initial,
|
|
9
|
+
load() {
|
|
10
|
+
return this.stored;
|
|
11
|
+
},
|
|
12
|
+
save(value: number) {
|
|
13
|
+
this.stored = value;
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe("CounterViewModel", () => {
|
|
19
|
+
it("increments and saves the count", () => {
|
|
20
|
+
const repo = memoryRepository();
|
|
21
|
+
const vm = new CounterViewModel(repo);
|
|
22
|
+
vm.increment.execute();
|
|
23
|
+
vm.increment.execute();
|
|
24
|
+
expect(vm.count.value).toBe(2);
|
|
25
|
+
expect(repo.stored).toBe(2);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("can only reset when the count is not zero", () => {
|
|
29
|
+
const vm = new CounterViewModel(memoryRepository());
|
|
30
|
+
expect(vm.reset.canExecute.value).toBe(false);
|
|
31
|
+
vm.increment.execute();
|
|
32
|
+
expect(vm.reset.canExecute.value).toBe(true);
|
|
33
|
+
vm.reset.execute();
|
|
34
|
+
expect(vm.count.value).toBe(0);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// ViewModel: screen state and actions. Does not know how it is drawn.
|
|
2
|
+
import { ViewModel } from "@bull-js/runtime";
|
|
3
|
+
import type { CounterRepository } from "../model/counter.repository";
|
|
4
|
+
|
|
5
|
+
export class CounterViewModel extends ViewModel {
|
|
6
|
+
readonly #count = this.state(0);
|
|
7
|
+
|
|
8
|
+
readonly count = this.#count.readonly;
|
|
9
|
+
readonly isZero = this.computed(() => this.#count.value === 0);
|
|
10
|
+
|
|
11
|
+
readonly increment = this.command(() => this.#set(this.#count.value + 1));
|
|
12
|
+
readonly reset = this.command(() => this.#set(0), { canExecute: () => !this.isZero.value });
|
|
13
|
+
|
|
14
|
+
constructor(private readonly repo: CounterRepository) {
|
|
15
|
+
super();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
protected override onActivate() {
|
|
19
|
+
this.#count.set(this.repo.load());
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
#set(value: number): void {
|
|
23
|
+
this.#count.set(value);
|
|
24
|
+
this.repo.save(value);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"lib": ["ES2023", "DOM", "DOM.Iterable"],
|
|
7
|
+
"jsx": "react-jsx",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"noImplicitOverride": true,
|
|
10
|
+
"isolatedModules": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"noEmit": true,
|
|
13
|
+
"types": []
|
|
14
|
+
},
|
|
15
|
+
"include": ["src"]
|
|
16
|
+
}
|