create-bose 0.1.0 → 0.1.1

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.
Files changed (3) hide show
  1. package/README.md +42 -0
  2. package/index.js +301 -168
  3. package/package.json +32 -31
package/README.md ADDED
@@ -0,0 +1,42 @@
1
+ # create-bose
2
+
3
+ > Scaffold a new [Bosejs](https://github.com/ChandanBose666/Bosejs) application instantly.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ npx create-bose my-app
9
+ cd my-app
10
+ npm install
11
+ npm run dev
12
+ ```
13
+
14
+ Then open `http://localhost:5173`.
15
+
16
+ ## What gets created
17
+
18
+ ```
19
+ my-app/
20
+ ├── src/
21
+ │ └── pages/
22
+ │ ├── index.js # Home — signals, lazy chunks, css$(), bose:bind
23
+ │ └── about.md # Docs — markdown routing with frontmatter
24
+ ├── vite.config.js
25
+ ├── package.json
26
+ └── .gitignore
27
+ ```
28
+
29
+ ## What the scaffold demonstrates
30
+
31
+ | Feature | Where |
32
+ |---|---|
33
+ | `useSignal` + `bose:bind` | `index.js` — counter synced to DOM |
34
+ | `$()` lazy chunk extraction | `index.js` — increment/decrement/reset |
35
+ | `css$()` scoped styles | `index.js` — all styling via css$() |
36
+ | `bose:state` serialization | `index.js` — state embedded in HTML |
37
+ | Markdown routing | `about.md` — `.md` file → `/about` route |
38
+ | File-based routing | Both pages — automatic URL mapping |
39
+
40
+ ## License
41
+
42
+ MIT © [Bosejs Contributors](https://github.com/ChandanBose666/Bosejs)
package/index.js CHANGED
@@ -1,168 +1,301 @@
1
- #!/usr/bin/env node
2
- import fs from 'fs';
3
- import path from 'path';
4
-
5
- const projectName = process.argv[2] || 'my-bose-app';
6
- const targetDir = path.resolve(process.cwd(), projectName);
7
-
8
- console.log(`\nCreating a new Bose app in ${targetDir}...\n`);
9
-
10
- if (fs.existsSync(targetDir)) {
11
- console.error(`Error: Directory "${projectName}" already exists. Choose a different name.`);
12
- process.exit(1);
13
- }
14
-
15
- // ── Helpers ──────────────────────────────────────────────────────────────────
16
-
17
- function write(filePath, content) {
18
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
19
- fs.writeFileSync(filePath, content);
20
- }
21
-
22
- function file(...segments) {
23
- return path.join(targetDir, ...segments);
24
- }
25
-
26
- // ── package.json ─────────────────────────────────────────────────────────────
27
-
28
- write(file('package.json'), JSON.stringify({
29
- name: projectName,
30
- version: '0.1.0',
31
- private: true,
32
- type: 'module',
33
- scripts: {
34
- dev: 'vite',
35
- build: 'vite build',
36
- preview: 'vite preview',
37
- },
38
- dependencies: {
39
- '@bosejs/core': 'latest',
40
- '@bosejs/state': 'latest',
41
- },
42
- devDependencies: {
43
- vite: 'latest',
44
- },
45
- }, null, 2));
46
-
47
- // ── vite.config.js ───────────────────────────────────────────────────────────
48
-
49
- write(file('vite.config.js'), `\
50
- import { defineConfig } from 'vite';
51
- import bosePlugin from '@bosejs/core';
52
-
53
- export default defineConfig({
54
- plugins: [bosePlugin()],
55
- });
56
- `);
57
-
58
- // ── src/pages/index.js ───────────────────────────────────────────────────────
59
- // This is the home page component maps to the "/" route.
60
- //
61
- // Key concepts shown here:
62
- // useSignal — import from @bosejs/state. Creates reactive state that
63
- // the compiler serialises into HTML for zero-hydration.
64
- //
65
- // $( ) — a GLOBAL compiler marker (no import needed). The Bose
66
- // compiler extracts the closure into its own lazy chunk.
67
- // The page loads with 0 JS; the chunk is fetched only on
68
- // the first interaction.
69
- //
70
- // bose:bind reactive text binding. Updated instantly by the runtime
71
- // when the signal changes no re-render.
72
- //
73
- // bose:state serialised state embedded in HTML. The chunk reads this
74
- // on resumption so it never loses context.
75
-
76
- write(file('src', 'pages', 'index.js'), `\
77
- import { useSignal } from '@bosejs/state';
78
-
79
- export default function Home() {
80
- // useSignal creates reactive state. The compiler injects a stable ID
81
- // so the runtime can sync DOM nodes to this signal after resumption.
82
- const count = useSignal(0);
83
-
84
- // $() is a global compiler marker — no import needed.
85
- // The closure is extracted into a separate JS chunk that is fetched
86
- // lazily on the first click. Until then, 0 bytes of JS are loaded.
87
- const increment = $(() => {
88
- count.value++;
89
- });
90
-
91
- const decrement = $(() => {
92
- count.value--;
93
- });
94
-
95
- const reset = $(() => {
96
- count.value = 0;
97
- });
98
-
99
- return \`
100
- <div style="
101
- font-family: system-ui, sans-serif;
102
- display: flex;
103
- flex-direction: column;
104
- align-items: center;
105
- justify-content: center;
106
- min-height: 100vh;
107
- margin: 0;
108
- background: #020617;
109
- color: #f8fafc;
110
- ">
111
- <h1 style="font-size: 2rem; margin-bottom: 0.25rem;">Bose Counter</h1>
112
- <p style="color: #94a3b8; margin-bottom: 3rem;">
113
- Resumable island &mdash; <strong>0 JS</strong> on page load.
114
- </p>
115
-
116
- <!-- bose:bind keeps this span in sync with the 'count' signal -->
117
- <div style="font-size: 6rem; font-weight: 900; line-height: 1; margin-bottom: 2rem;">
118
- <span bose:bind="count">\${count.value}</span>
119
- </div>
120
-
121
- <div style="display: flex; gap: 1rem;">
122
- <button
123
- style="padding: 0.75rem 1.5rem; border-radius: 0.5rem; border: 1px solid #334155; background: #1e293b; color: #f8fafc; font-size: 1.25rem; cursor: pointer;"
124
- bose:on:click="\${decrement.chunk}"
125
- bose:state='\${JSON.stringify({ count: count.value })}'>
126
-
127
- </button>
128
-
129
- <button
130
- style="padding: 0.75rem 1.5rem; border-radius: 0.5rem; border: 1px solid #334155; background: #1e293b; color: #f8fafc; font-size: 1.25rem; cursor: pointer;"
131
- bose:on:click="\${reset.chunk}"
132
- bose:state='\${JSON.stringify({ count: count.value })}'>
133
- Reset
134
- </button>
135
-
136
- <button
137
- style="padding: 0.75rem 1.5rem; border-radius: 0.5rem; border: none; background: #6366f1; color: #fff; font-size: 1.25rem; cursor: pointer;"
138
- bose:on:click="\${increment.chunk}"
139
- bose:state='\${JSON.stringify({ count: count.value })}'>
140
- +
141
- </button>
142
- </div>
143
- </div>
144
- \`;
145
- }
146
- `);
147
-
148
- // ── .gitignore ────────────────────────────────────────────────────────────────
149
-
150
- write(file('.gitignore'), `\
151
- node_modules/
152
- dist/
153
- public/chunks/
154
- bose-error.log
155
- .env*.local
156
- `);
157
-
158
- // ── Done ─────────────────────────────────────────────────────────────────────
159
-
160
- const steps = [
161
- ` cd ${projectName}`,
162
- ` npm install`,
163
- ` npm run dev`,
164
- ];
165
-
166
- console.log('Done! Next steps:\n');
167
- console.log(steps.join('\n'));
168
- console.log('\nThen open http://localhost:5173\n');
1
+ #!/usr/bin/env node
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+
5
+ const projectName = process.argv[2] || 'my-bose-app';
6
+ const targetDir = path.resolve(process.cwd(), projectName);
7
+
8
+ console.log(`\nCreating a new Bose app in ${targetDir}...\n`);
9
+
10
+ if (fs.existsSync(targetDir)) {
11
+ console.error(`Error: Directory "${projectName}" already exists. Choose a different name.`);
12
+ process.exit(1);
13
+ }
14
+
15
+ // ── Helpers ───────────────────────────────────────────────────────────────────
16
+
17
+ function write(filePath, content) {
18
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
19
+ fs.writeFileSync(filePath, content);
20
+ }
21
+
22
+ function file(...segments) {
23
+ return path.join(targetDir, ...segments);
24
+ }
25
+
26
+ // ── package.json ──────────────────────────────────────────────────────────────
27
+
28
+ write(file('package.json'), JSON.stringify({
29
+ name: projectName,
30
+ version: '0.1.0',
31
+ private: true,
32
+ type: 'module',
33
+ scripts: {
34
+ dev: 'vite',
35
+ build: 'vite build',
36
+ preview: 'vite preview',
37
+ },
38
+ dependencies: {
39
+ '@bosejs/core': 'latest',
40
+ '@bosejs/state': 'latest',
41
+ },
42
+ devDependencies: {
43
+ vite: 'latest',
44
+ },
45
+ }, null, 2));
46
+
47
+ // ── vite.config.js ────────────────────────────────────────────────────────────
48
+
49
+ write(file('vite.config.js'), `\
50
+ import { defineConfig } from 'vite';
51
+ import bosePlugin from '@bosejs/core';
52
+
53
+ export default defineConfig({
54
+ plugins: [bosePlugin()],
55
+ });
56
+ `);
57
+
58
+ // ── src/pages/index.js ────────────────────────────────────────────────────────
59
+ // Home page — showcases: useSignal, $(), bose:bind, bose:state, css$()
60
+ // Route: /
61
+
62
+ write(file('src', 'pages', 'index.js'), `\
63
+ import { useSignal } from '@bosejs/state';
64
+
65
+ export default function Home() {
66
+ // useSignal reactive state. The compiler injects a stable ID so the
67
+ // runtime can sync any DOM node bound with bose:bind after resumption.
68
+ const count = useSignal(0);
69
+
70
+ // css$() scoped styles extracted at build time. Zero runtime overhead.
71
+ // Class names are hashed so there are no collisions across components.
72
+ const s = css$(\`
73
+ .wrap { font-family: system-ui, sans-serif; min-height: 100vh; background: #020617; color: #f8fafc; margin: 0; }
74
+ .nav { display: flex; justify-content: space-between; align-items: center; padding: 1.25rem 2rem; border-bottom: 1px solid #1e293b; }
75
+ .brand { font-weight: 800; font-size: 1.2rem; color: #f8fafc; text-decoration: none; }
76
+ .links a { color: #94a3b8; text-decoration: none; font-size: 0.9rem; margin-left: 1.5rem; }
77
+ .links a:hover { color: #f8fafc; }
78
+ .hero { text-align: center; padding: 4rem 2rem 2.5rem; }
79
+ .title { font-size: clamp(2rem, 6vw, 3.75rem); font-weight: 900; margin: 0 0 1rem; }
80
+ .sub { color: #94a3b8; font-size: 1.1rem; max-width: 500px; margin: 0 auto 2rem; line-height: 1.7; }
81
+ .pills { display: flex; gap: 0.6rem; justify-content: center; flex-wrap: wrap; margin-bottom: 3rem; }
82
+ .pill { padding: 0.3rem 0.85rem; border-radius: 99px; font-size: 0.75rem; font-weight: 600; }
83
+ .pBlue { background: #172554; color: #93c5fd; }
84
+ .pPurple { background: #2e1065; color: #c4b5fd; }
85
+ .pGreen { background: #14532d; color: #86efac; }
86
+ .cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 1.25rem; max-width: 840px; margin: 0 auto 3.5rem; padding: 0 2rem; }
87
+ .card { background: #0f172a; border: 1px solid #1e293b; border-radius: 0.75rem; padding: 1.5rem; }
88
+ .icon { font-size: 1.5rem; margin-bottom: 0.6rem; }
89
+ .ctitle { font-size: 0.95rem; font-weight: 700; margin: 0 0 0.4rem; }
90
+ .cbody { font-size: 0.85rem; color: #64748b; line-height: 1.6; margin: 0; }
91
+ .demo { text-align: center; padding: 0 2rem 2.5rem; }
92
+ .hint { color: #475569; font-size: 0.82rem; margin-bottom: 1.25rem; }
93
+ .num { font-size: 6rem; font-weight: 900; color: #6366f1; line-height: 1; margin-bottom: 1.25rem; }
94
+ .row { display: flex; gap: 0.75rem; justify-content: center; }
95
+ .btn { padding: 0.65rem 1.5rem; border-radius: 0.5rem; border: 1px solid #334155; background: #1e293b; color: #f8fafc; font-size: 1.1rem; cursor: pointer; }
96
+ .btnP { background: #6366f1; border-color: #6366f1; }
97
+ .foot { text-align: center; padding: 2rem; border-top: 1px solid #1e293b; color: #475569; font-size: 0.85rem; }
98
+ .foot a { color: #6366f1; text-decoration: none; }
99
+ \`);
100
+
101
+ // $() — global compiler marker. No import needed.
102
+ // Each closure is extracted into its own lazy JS chunk at build time.
103
+ // 0 bytes of JS ship with the page — chunks are fetched on first use.
104
+ const increment = $(() => { count.value++; });
105
+ const decrement = $(() => { count.value--; });
106
+ const reset = $(() => { count.value = 0; });
107
+
108
+ return \`
109
+ <div class="\${s.wrap}">
110
+
111
+ <nav class="\${s.nav}">
112
+ <a href="/" class="\${s.brand}">Bosejs</a>
113
+ <div class="\${s.links}">
114
+ <a href="/">Home</a>
115
+ <a href="/about">About</a>
116
+ </div>
117
+ </nav>
118
+
119
+ <div class="\${s.hero}">
120
+ <h1 class="\${s.title}">Resumable Islands</h1>
121
+ <p class="\${s.sub}">
122
+ Zero JS on page load. Closures extracted at build time,
123
+ resumed on first interaction no hydration, no virtual DOM.
124
+ </p>
125
+ <div class="\${s.pills}">
126
+ <span class="\${s.pill} \${s.pBlue}">Zero Hydration</span>
127
+ <span class="\${s.pill} \${s.pPurple}">Lazy Chunks</span>
128
+ <span class="\${s.pill} \${s.pGreen}">Fine-Grained Signals</span>
129
+ </div>
130
+ </div>
131
+
132
+ <div class="\${s.cards}">
133
+ <div class="\${s.card}">
134
+ <div class="\${s.icon}">⚡</div>
135
+ <p class="\${s.ctitle}">Zero Hydration</p>
136
+ <p class="\${s.cbody}">State is serialized into HTML. The browser resumes execution — never re-runs your code.</p>
137
+ </div>
138
+ <div class="\${s.card}">
139
+ <div class="\${s.icon}">✂️</div>
140
+ <p class="\${s.ctitle}">Automatic Code Splitting</p>
141
+ <p class="\${s.cbody}">Wrap any logic in <code>\$()</code> and the compiler extracts it into a lazy chunk — 0 bytes until needed.</p>
142
+ </div>
143
+ <div class="\${s.card}">
144
+ <div class="\${s.icon}">🔗</div>
145
+ <p class="\${s.ctitle}">Reactive Signals</p>
146
+ <p class="\${s.cbody}"><code>useSignal</code> creates fine-grained reactive state. DOM updates are surgical — no diffing.</p>
147
+ </div>
148
+ </div>
149
+
150
+ <div class="\${s.demo}">
151
+ <p class="\${s.hint}">Open DevTools → Network → click a button to see the lazy chunk load</p>
152
+ <!-- bose:bind keeps this span in sync with the 'count' signal -->
153
+ <div class="\${s.num}">
154
+ <span bose:bind="count">\${count.value}</span>
155
+ </div>
156
+ <div class="\${s.row}">
157
+ <button class="\${s.btn}"
158
+ bose:on:click="\${decrement.chunk}"
159
+ bose:state='\${JSON.stringify({ count: count.value })}'>−</button>
160
+ <button class="\${s.btn}"
161
+ bose:on:click="\${reset.chunk}"
162
+ bose:state='\${JSON.stringify({ count: count.value })}'>Reset</button>
163
+ <button class="\${s.btn} \${s.btnP}"
164
+ bose:on:click="\${increment.chunk}"
165
+ bose:state='\${JSON.stringify({ count: count.value })}'>+</button>
166
+ </div>
167
+ </div>
168
+
169
+ <div class="\${s.foot}">
170
+ <a href="/about">Read the docs →</a>
171
+ </div>
172
+
173
+ </div>
174
+ \`;
175
+ }
176
+ `);
177
+
178
+ // ── src/pages/about.md ────────────────────────────────────────────────────────
179
+ // Markdown page — showcases file-based routing with .md files.
180
+ // Route: /about
181
+
182
+ write(file('src', 'pages', 'about.md'), `\
183
+ ---
184
+ title: About Bosejs
185
+ ---
186
+
187
+ # About Bosejs
188
+
189
+ Bosejs combines Astro's Islands Architecture with Qwik's Resumability to deliver
190
+ web pages that ship **zero JavaScript** until the user interacts.
191
+
192
+ ---
193
+
194
+ ## How it works
195
+
196
+ 1. **You write standard JS** — functions, closures, signals. No new syntax to learn.
197
+ 2. **The compiler shreds it** — every \`$()\` closure is extracted into its own chunk at build time.
198
+ 3. **HTML ships with 0 JS** — event handlers live as HTML attributes; state is serialized inline.
199
+ 4. **On first interaction** — the runtime fetches only the chunk needed. Execution resumes instantly.
200
+
201
+ ---
202
+
203
+ ## Core concepts
204
+
205
+ ### \`$()\` — Lazy closure extraction
206
+
207
+ Wrap any event handler in \`$()\`. The Babel compiler extracts it into a separate JS file.
208
+ No import needed — \`$\` is a global injected by the framework.
209
+
210
+ \`\`\`js
211
+ const increment = $(() => {
212
+ count.value++;
213
+ });
214
+
215
+ // In your HTML:
216
+ // bose:on:click="\${increment.chunk}"
217
+ \`\`\`
218
+
219
+ ### \`useSignal\` — Reactive state
220
+
221
+ Fine-grained reactive state from \`@bosejs/state\`. DOM nodes bound with
222
+ \`bose:bind\` update surgically when the signal changes — no re-render, no diffing.
223
+
224
+ \`\`\`js
225
+ import { useSignal } from '@bosejs/state';
226
+
227
+ const count = useSignal(0);
228
+ // <span bose:bind="count">\${count.value}</span>
229
+ \`\`\`
230
+
231
+ ### \`bose:state\` — Serialized context
232
+
233
+ State is embedded directly in HTML so the chunk always has the context it needs
234
+ on resumption — even after the page has been cached or served from a CDN.
235
+
236
+ \`\`\`html
237
+ <button
238
+ bose:on:click="\${increment.chunk}"
239
+ bose:state='{"count":0}'>
240
+ +
241
+ </button>
242
+ \`\`\`
243
+
244
+ ### \`css$()\` — Scoped styles
245
+
246
+ Write CSS next to your component. Styles are extracted at build time and scoped
247
+ automatically. Zero runtime overhead.
248
+
249
+ \`\`\`js
250
+ const s = css$(\`.btn { background: #6366f1; color: white; }\`);
251
+ // <button class="\${s.btn}">Click me</button>
252
+ \`\`\`
253
+
254
+ ### File-based routing
255
+
256
+ Drop a file in \`src/pages/\` and it becomes a route automatically.
257
+
258
+ | File | Route |
259
+ |---|---|
260
+ | \`src/pages/index.js\` | \`/\` |
261
+ | \`src/pages/about.md\` | \`/about\` |
262
+ | \`src/pages/product/[id].js\` | \`/product/123\` |
263
+
264
+ ---
265
+
266
+ ## Packages
267
+
268
+ | Package | You install? | Role |
269
+ |---|---|---|
270
+ | \`@bosejs/core\` | ✅ Yes | Vite plugin — routing, SSR, dev server |
271
+ | \`@bosejs/state\` | ✅ Yes | Signals — \`useSignal\` |
272
+ | \`@bosejs/compiler\` | 🔧 Auto | Babel plugin — extracts \`$()\` closures |
273
+ | \`@bosejs/runtime\` | 🔧 Auto | Tiny browser loader — resumes event handlers |
274
+
275
+ ---
276
+
277
+ [← Back to home](/)
278
+ `);
279
+
280
+ // ── .gitignore ────────────────────────────────────────────────────────────────
281
+
282
+ write(file('.gitignore'), `\
283
+ node_modules/
284
+ dist/
285
+ public/chunks/
286
+ bose-error.log
287
+ .env*.local
288
+ `);
289
+
290
+ // ── Done ──────────────────────────────────────────────────────────────────────
291
+
292
+ const steps = [
293
+ ` cd ${projectName}`,
294
+ ` npm install`,
295
+ ` npm run dev`,
296
+ ];
297
+
298
+ console.log('Done! Next steps:\n');
299
+ console.log(steps.join('\n'));
300
+ console.log('\nThen open http://localhost:5173\n');
301
+ console.log('Pages:\n / → Home (signals, lazy chunks, css$)\n /about → Docs (markdown routing)\n');
package/package.json CHANGED
@@ -1,31 +1,32 @@
1
- {
2
- "name": "create-bose",
3
- "version": "0.1.0",
4
- "description": "Scaffold a new Bose framework application.",
5
- "type": "module",
6
- "bin": {
7
- "create-bose": "index.js"
8
- },
9
- "files": [
10
- "index.js",
11
- "LICENSE"
12
- ],
13
- "engines": {
14
- "node": ">=18.0.0"
15
- },
16
- "publishConfig": {
17
- "access": "public"
18
- },
19
- "repository": {
20
- "type": "git",
21
- "url": "git+https://github.com/bosejs/bosejs.git",
22
- "directory": "packages/create-bose"
23
- },
24
- "keywords": [
25
- "bose",
26
- "scaffold",
27
- "cli",
28
- "create"
29
- ],
30
- "license": "MIT"
31
- }
1
+ {
2
+ "name": "create-bose",
3
+ "version": "0.1.1",
4
+ "description": "Scaffold a new Bose framework application.",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-bose": "index.js"
8
+ },
9
+ "files": [
10
+ "index.js",
11
+ "LICENSE",
12
+ "README.md"
13
+ ],
14
+ "engines": {
15
+ "node": ">=18.0.0"
16
+ },
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/bosejs/bosejs.git",
23
+ "directory": "packages/create-bose"
24
+ },
25
+ "keywords": [
26
+ "bose",
27
+ "scaffold",
28
+ "cli",
29
+ "create"
30
+ ],
31
+ "license": "MIT"
32
+ }