@nexus_js/cli 0.6.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 +52 -0
- package/dist/add.d.ts +36 -0
- package/dist/add.d.ts.map +1 -0
- package/dist/add.js +342 -0
- package/dist/add.js.map +1 -0
- package/dist/analyzer.d.ts +70 -0
- package/dist/analyzer.d.ts.map +1 -0
- package/dist/analyzer.js +247 -0
- package/dist/analyzer.js.map +1 -0
- package/dist/audit.d.ts +35 -0
- package/dist/audit.d.ts.map +1 -0
- package/dist/audit.js +383 -0
- package/dist/audit.js.map +1 -0
- package/dist/bin.d.ts +6 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +367 -0
- package/dist/bin.js.map +1 -0
- package/dist/config.d.ts +41 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -0
- package/dist/create.d.ts +7 -0
- package/dist/create.d.ts.map +1 -0
- package/dist/create.js +256 -0
- package/dist/create.js.map +1 -0
- package/dist/fix.d.ts +22 -0
- package/dist/fix.d.ts.map +1 -0
- package/dist/fix.js +199 -0
- package/dist/fix.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/studio.d.ts +136 -0
- package/dist/studio.d.ts.map +1 -0
- package/dist/studio.js +721 -0
- package/dist/studio.js.map +1 -0
- package/package.json +63 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nexus Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Nexus CLI (`nexus`, `create-nexus`)
|
|
2
|
+
|
|
3
|
+
Command-line tools for [Nexus](https://nexusjs.dev) — the full-stack web framework with islands architecture, Svelte 5 runes, and server actions.
|
|
4
|
+
|
|
5
|
+
- **Documentation:** [https://nexusjs.dev](https://nexusjs.dev)
|
|
6
|
+
- **Repository:** [github.com/bierfor/nexus](https://github.com/bierfor/nexus)
|
|
7
|
+
- **Issues:** [github.com/bierfor/nexus/issues](https://github.com/bierfor/nexus/issues)
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pnpm add -D @nexus_js/cli
|
|
13
|
+
# or
|
|
14
|
+
npm install -D @nexus_js/cli
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Global install (optional):
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install -g @nexus_js/cli
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Commands
|
|
24
|
+
|
|
25
|
+
| Command | Description |
|
|
26
|
+
|--------|-------------|
|
|
27
|
+
| `nexus dev` | Start the development server with HMR |
|
|
28
|
+
| `nexus build` | Production build |
|
|
29
|
+
| `nexus start` | Run the production server |
|
|
30
|
+
| `nexus studio` | Open Nexus Studio (dev dashboard) |
|
|
31
|
+
| `nexus routes` | Print the route manifest |
|
|
32
|
+
| `nexus check` | TypeScript check |
|
|
33
|
+
| `create-nexus <name>` | Scaffold a new project |
|
|
34
|
+
|
|
35
|
+
## New project
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm exec --package=@nexus_js/cli@latest -- create-nexus my-app
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Or install globally and run the `create-nexus` binary:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm install -g @nexus_js/cli
|
|
45
|
+
create-nexus my-app
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
See the [full documentation](https://nexusjs.dev) for configuration, routing, `.nx` components, and deployment.
|
|
49
|
+
|
|
50
|
+
## License
|
|
51
|
+
|
|
52
|
+
MIT © Nexus contributors
|
package/dist/add.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nexus Marketplace — `nexus add <block>`
|
|
3
|
+
*
|
|
4
|
+
* Installs pre-built "Nexus Blocks": components, auth systems, DB schemas,
|
|
5
|
+
* and more — directly scaffolded into your project.
|
|
6
|
+
*
|
|
7
|
+
* Registry format (fetched from GitHub or local for offline):
|
|
8
|
+
* blocks.json → { id, name, description, files: [{ path, content }] }
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* nexus add auth → Full auth system (middleware + routes + actions)
|
|
12
|
+
* nexus add pokecard → Pre-styled island with Runes configured
|
|
13
|
+
* nexus add analytics → Privacy-first page-view tracker
|
|
14
|
+
* nexus add rate-limit → Edge rate limiter middleware
|
|
15
|
+
* nexus add → Interactive picker (lists available blocks)
|
|
16
|
+
*/
|
|
17
|
+
export interface NexusBlock {
|
|
18
|
+
id: string;
|
|
19
|
+
name: string;
|
|
20
|
+
category: 'auth' | 'ui' | 'data' | 'middleware' | 'analytics';
|
|
21
|
+
description: string;
|
|
22
|
+
tags: string[];
|
|
23
|
+
files: BlockFile[];
|
|
24
|
+
postInstall?: string;
|
|
25
|
+
}
|
|
26
|
+
interface BlockFile {
|
|
27
|
+
path: string;
|
|
28
|
+
content: string;
|
|
29
|
+
}
|
|
30
|
+
/** Entry point called by packages/cli/src/bin.ts */
|
|
31
|
+
export declare function runAdd(opts: {
|
|
32
|
+
blockId?: string;
|
|
33
|
+
root: string;
|
|
34
|
+
}): Promise<void>;
|
|
35
|
+
export {};
|
|
36
|
+
//# sourceMappingURL=add.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"add.d.ts","sourceRoot":"","sources":["../src/add.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAcH,MAAM,WAAW,UAAU;IACzB,EAAE,EAAW,MAAM,CAAC;IACpB,IAAI,EAAS,MAAM,CAAC;IACpB,QAAQ,EAAK,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,YAAY,GAAG,WAAW,CAAC;IACjE,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAS,MAAM,EAAE,CAAC;IACtB,KAAK,EAAQ,SAAS,EAAE,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,UAAU,SAAS;IACjB,IAAI,EAAK,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAkUD,oDAAoD;AACpD,wBAAsB,MAAM,CAAC,IAAI,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAMpF"}
|
package/dist/add.js
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nexus Marketplace — `nexus add <block>`
|
|
3
|
+
*
|
|
4
|
+
* Installs pre-built "Nexus Blocks": components, auth systems, DB schemas,
|
|
5
|
+
* and more — directly scaffolded into your project.
|
|
6
|
+
*
|
|
7
|
+
* Registry format (fetched from GitHub or local for offline):
|
|
8
|
+
* blocks.json → { id, name, description, files: [{ path, content }] }
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* nexus add auth → Full auth system (middleware + routes + actions)
|
|
12
|
+
* nexus add pokecard → Pre-styled island with Runes configured
|
|
13
|
+
* nexus add analytics → Privacy-first page-view tracker
|
|
14
|
+
* nexus add rate-limit → Edge rate limiter middleware
|
|
15
|
+
* nexus add → Interactive picker (lists available blocks)
|
|
16
|
+
*/
|
|
17
|
+
import { writeFile, mkdir } from 'node:fs/promises';
|
|
18
|
+
import { join, dirname } from 'node:path';
|
|
19
|
+
import { createInterface } from 'node:readline';
|
|
20
|
+
const c = {
|
|
21
|
+
reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
|
|
22
|
+
red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m',
|
|
23
|
+
mag: '\x1b[35m', cyan: '\x1b[36m', gray: '\x1b[90m',
|
|
24
|
+
};
|
|
25
|
+
// ── Built-in blocks ───────────────────────────────────────────────────────────
|
|
26
|
+
const BLOCKS = [
|
|
27
|
+
{
|
|
28
|
+
id: 'auth',
|
|
29
|
+
name: 'Nexus Auth',
|
|
30
|
+
category: 'auth',
|
|
31
|
+
description: 'Full auth system: login/register routes + session middleware + Server Actions',
|
|
32
|
+
tags: ['authentication', 'sessions', 'middleware'],
|
|
33
|
+
postInstall: 'Set AUTH_SECRET in your .env file and run nexus build.',
|
|
34
|
+
files: [
|
|
35
|
+
{
|
|
36
|
+
path: 'src/lib/auth.ts',
|
|
37
|
+
content: `/**
|
|
38
|
+
* Nexus Auth — Server-side session helpers.
|
|
39
|
+
* Generated by: nexus add auth
|
|
40
|
+
*/
|
|
41
|
+
import type { NexusContext } from '@nexus_js/server';
|
|
42
|
+
|
|
43
|
+
export interface Session {
|
|
44
|
+
userId: string;
|
|
45
|
+
email: string;
|
|
46
|
+
role: 'user' | 'admin';
|
|
47
|
+
expiresAt: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function getSession(ctx: NexusContext): Session | null {
|
|
51
|
+
const cookie = ctx.request.headers.get('cookie');
|
|
52
|
+
if (!cookie) return null;
|
|
53
|
+
const match = /nx-session=([^;]+)/.exec(cookie);
|
|
54
|
+
if (!match) return null;
|
|
55
|
+
try {
|
|
56
|
+
return JSON.parse(atob(match[1])) as Session;
|
|
57
|
+
} catch { return null; }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function requireSession(ctx: NexusContext): Session {
|
|
61
|
+
const session = getSession(ctx);
|
|
62
|
+
if (!session || session.expiresAt < Date.now()) {
|
|
63
|
+
ctx.redirect('/login');
|
|
64
|
+
throw new Error('Unreachable'); // redirect throws
|
|
65
|
+
}
|
|
66
|
+
return session;
|
|
67
|
+
}
|
|
68
|
+
`,
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
path: 'src/routes/login/+page.nx',
|
|
72
|
+
content: `---
|
|
73
|
+
import { createAction } from '@nexus_js/server';
|
|
74
|
+
import { defineHead } from '@nexus_js/head';
|
|
75
|
+
|
|
76
|
+
defineHead({ title: 'Login — Nexus Auth' });
|
|
77
|
+
|
|
78
|
+
const loginAction = createAction(async (formData: FormData, ctx) => {
|
|
79
|
+
const email = formData.get('email')?.toString() ?? '';
|
|
80
|
+
const password = formData.get('password')?.toString() ?? '';
|
|
81
|
+
|
|
82
|
+
// TODO: validate against your DB
|
|
83
|
+
if (email && password) {
|
|
84
|
+
ctx.setCookie('nx-session', btoa(JSON.stringify({
|
|
85
|
+
userId: '1', email, role: 'user', expiresAt: Date.now() + 86400_000
|
|
86
|
+
})), { httpOnly: true, sameSite: 'lax' });
|
|
87
|
+
ctx.redirect('/');
|
|
88
|
+
}
|
|
89
|
+
}, { idempotent: false, race: 'reject' });
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
<main class="auth-page">
|
|
93
|
+
<form method="POST" class="auth-form">
|
|
94
|
+
<h1>Sign in</h1>
|
|
95
|
+
<input type="email" name="email" placeholder="Email" required />
|
|
96
|
+
<input type="password" name="password" placeholder="Password" required />
|
|
97
|
+
<button type="submit">Sign in</button>
|
|
98
|
+
</form>
|
|
99
|
+
</main>
|
|
100
|
+
|
|
101
|
+
<style>
|
|
102
|
+
.auth-page { display:flex; align-items:center; justify-content:center; min-height:80vh }
|
|
103
|
+
.auth-form { display:flex; flex-direction:column; gap:12px; width:320px }
|
|
104
|
+
.auth-form h1 { font-size:24px; font-weight:700; margin-bottom:8px }
|
|
105
|
+
.auth-form input { padding:10px 14px; border:1px solid var(--border,#e5e7eb); border-radius:8px }
|
|
106
|
+
.auth-form button { padding:10px; background:#7c3aed; color:#fff; border:none; border-radius:8px; font-weight:600; cursor:pointer }
|
|
107
|
+
</style>
|
|
108
|
+
`,
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
path: 'src/middleware/auth.ts',
|
|
112
|
+
content: `/**
|
|
113
|
+
* Nexus Auth Middleware — protects routes requiring a session.
|
|
114
|
+
* Generated by: nexus add auth
|
|
115
|
+
*/
|
|
116
|
+
import { defineMiddleware } from '@nexus_js/middleware';
|
|
117
|
+
import { getSession } from '../lib/auth.js';
|
|
118
|
+
|
|
119
|
+
export const authMiddleware = defineMiddleware(async (ctx, next) => {
|
|
120
|
+
const session = getSession(ctx);
|
|
121
|
+
const isProtected = ctx.request.url.includes('/dashboard') ||
|
|
122
|
+
ctx.request.url.includes('/profile');
|
|
123
|
+
|
|
124
|
+
if (isProtected && !session) {
|
|
125
|
+
return ctx.redirect('/login');
|
|
126
|
+
}
|
|
127
|
+
return next();
|
|
128
|
+
});
|
|
129
|
+
`,
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
id: 'analytics',
|
|
135
|
+
name: 'Nexus Analytics',
|
|
136
|
+
category: 'analytics',
|
|
137
|
+
description: 'Privacy-first page-view counter using Server Actions + Shield Cache.',
|
|
138
|
+
tags: ['analytics', 'privacy', 'pageviews'],
|
|
139
|
+
postInstall: 'Add <Analytics /> to your root layout.',
|
|
140
|
+
files: [
|
|
141
|
+
{
|
|
142
|
+
path: 'src/components/Analytics.nx',
|
|
143
|
+
content: `---
|
|
144
|
+
// Privacy-first analytics — no cookies, no fingerprinting.
|
|
145
|
+
// Generated by: nexus add analytics
|
|
146
|
+
import { cache } from '@nexus_js/runtime';
|
|
147
|
+
const key = 'nx-' + crypto.randomUUID().slice(0, 8); // ephemeral session id
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
<script client:idle>
|
|
151
|
+
// Fires a page-view beacon on mount (client:idle = after page paint)
|
|
152
|
+
const path = window.location.pathname;
|
|
153
|
+
navigator.sendBeacon?.('/_nexus/action/track-view', JSON.stringify({ path }));
|
|
154
|
+
</script>
|
|
155
|
+
`,
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
path: 'src/routes/api/analytics/+page.nx',
|
|
159
|
+
content: `---
|
|
160
|
+
import { createAction, registerAction } from '@nexus_js/server';
|
|
161
|
+
|
|
162
|
+
// In-memory counter (replace with DB in production)
|
|
163
|
+
const views = new Map<string, number>();
|
|
164
|
+
|
|
165
|
+
registerAction('track-view', async (input: { path: string }) => {
|
|
166
|
+
views.set(input.path, (views.get(input.path) ?? 0) + 1);
|
|
167
|
+
}, { idempotent: false });
|
|
168
|
+
|
|
169
|
+
const data = Object.fromEntries(views);
|
|
170
|
+
---
|
|
171
|
+
<pre>{JSON.stringify(data, null, 2)}</pre>
|
|
172
|
+
`,
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
id: 'pokecard',
|
|
178
|
+
name: 'PokéCard Island',
|
|
179
|
+
category: 'ui',
|
|
180
|
+
description: 'Pre-styled Pokémon card island with shiny toggle Rune. Demo component.',
|
|
181
|
+
tags: ['ui', 'island', 'demo', 'runes'],
|
|
182
|
+
files: [
|
|
183
|
+
{
|
|
184
|
+
path: 'src/components/PokéCard.nx',
|
|
185
|
+
content: `---
|
|
186
|
+
export interface Props {
|
|
187
|
+
id: number;
|
|
188
|
+
name: string;
|
|
189
|
+
sprite: string;
|
|
190
|
+
types: string[];
|
|
191
|
+
color: string;
|
|
192
|
+
}
|
|
193
|
+
const { id, name, sprite, types, color } = $props<Props>();
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
<script>
|
|
197
|
+
let shiny = $state(false);
|
|
198
|
+
</script>
|
|
199
|
+
|
|
200
|
+
<a href="/pokemon/{id}" class="pokecard" style="--color: {color}">
|
|
201
|
+
<div class="card-bg">
|
|
202
|
+
<img src="{sprite}" alt="{name}" width="120" height="120" />
|
|
203
|
+
<button onclick="{() => shiny = !shiny}" class="shiny-btn">
|
|
204
|
+
{shiny ? '✨ Shiny' : '✨ Normal'}
|
|
205
|
+
</button>
|
|
206
|
+
</div>
|
|
207
|
+
<div class="card-body">
|
|
208
|
+
<span class="dex-num">#{String(id).padStart(3, '0')}</span>
|
|
209
|
+
<h3>{name}</h3>
|
|
210
|
+
<div class="types">
|
|
211
|
+
{#each types as type}
|
|
212
|
+
<span class="type-badge type-{type}">{type}</span>
|
|
213
|
+
{/each}
|
|
214
|
+
</div>
|
|
215
|
+
</div>
|
|
216
|
+
</a>
|
|
217
|
+
|
|
218
|
+
<style>
|
|
219
|
+
.pokecard { display:block; border-radius:16px; overflow:hidden; text-decoration:none; color:inherit; background:var(--surface, #13131f); border:1px solid var(--border, #1e1e30); transition:transform .2s }
|
|
220
|
+
.pokecard:hover { transform:translateY(-4px) }
|
|
221
|
+
.card-bg { background:linear-gradient(135deg, color-mix(in srgb, var(--color) 20%, transparent), transparent); padding:20px; display:flex; flex-direction:column; align-items:center; gap:8px }
|
|
222
|
+
.card-body { padding:12px 16px 16px }
|
|
223
|
+
.dex-num { font-size:11px; color:#64748b; font-family:monospace }
|
|
224
|
+
h3 { font-size:16px; font-weight:700; text-transform:capitalize; margin:4px 0 8px }
|
|
225
|
+
.types { display:flex; gap:4px; flex-wrap:wrap }
|
|
226
|
+
.type-badge { padding:2px 10px; border-radius:999px; font-size:11px; font-weight:700; background:#374151; color:#fff }
|
|
227
|
+
.shiny-btn { padding:4px 12px; border-radius:999px; border:1px solid #374151; background:transparent; color:#94a3b8; font-size:11px; cursor:pointer }
|
|
228
|
+
</style>
|
|
229
|
+
`,
|
|
230
|
+
},
|
|
231
|
+
],
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
id: 'rate-limit',
|
|
235
|
+
name: 'Edge Rate Limiter',
|
|
236
|
+
category: 'middleware',
|
|
237
|
+
description: 'IP-based rate limiting middleware for API routes and Server Actions.',
|
|
238
|
+
tags: ['security', 'middleware', 'edge'],
|
|
239
|
+
files: [
|
|
240
|
+
{
|
|
241
|
+
path: 'src/middleware/rate-limit.ts',
|
|
242
|
+
content: `/**
|
|
243
|
+
* Nexus Edge Rate Limiter — in-memory sliding window.
|
|
244
|
+
* Generated by: nexus add rate-limit
|
|
245
|
+
*
|
|
246
|
+
* For production, replace the Map with an Upstash/Redis adapter.
|
|
247
|
+
*/
|
|
248
|
+
import { defineMiddleware } from '@nexus_js/middleware';
|
|
249
|
+
|
|
250
|
+
const windows = new Map<string, { count: number; reset: number }>();
|
|
251
|
+
|
|
252
|
+
export function createRateLimit(opts: {
|
|
253
|
+
requests: number;
|
|
254
|
+
windowMs: number;
|
|
255
|
+
message?: string;
|
|
256
|
+
}) {
|
|
257
|
+
return defineMiddleware(async (ctx, next) => {
|
|
258
|
+
const ip = ctx.request.headers.get('x-forwarded-for')?.split(',')[0].trim() ?? 'unknown';
|
|
259
|
+
const now = Date.now();
|
|
260
|
+
const win = windows.get(ip);
|
|
261
|
+
|
|
262
|
+
if (!win || now > win.reset) {
|
|
263
|
+
windows.set(ip, { count: 1, reset: now + opts.windowMs });
|
|
264
|
+
return next();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (win.count >= opts.requests) {
|
|
268
|
+
return new Response(
|
|
269
|
+
JSON.stringify({ error: opts.message ?? 'Too Many Requests' }),
|
|
270
|
+
{ status: 429, headers: { 'content-type': 'application/json', 'retry-after': String(Math.ceil((win.reset - now) / 1000)) } }
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
win.count++;
|
|
275
|
+
return next();
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Default: 100 req / 60s per IP
|
|
280
|
+
export const rateLimitMiddleware = createRateLimit({ requests: 100, windowMs: 60_000 });
|
|
281
|
+
`,
|
|
282
|
+
},
|
|
283
|
+
],
|
|
284
|
+
},
|
|
285
|
+
];
|
|
286
|
+
// ── CLI logic ─────────────────────────────────────────────────────────────────
|
|
287
|
+
function listBlocks() {
|
|
288
|
+
console.log(`\n ${c.mag}${c.bold}◆ Nexus Marketplace${c.reset}\n`);
|
|
289
|
+
const byCategory = new Map();
|
|
290
|
+
for (const block of BLOCKS) {
|
|
291
|
+
if (!byCategory.has(block.category))
|
|
292
|
+
byCategory.set(block.category, []);
|
|
293
|
+
byCategory.get(block.category).push(block);
|
|
294
|
+
}
|
|
295
|
+
for (const [cat, blocks] of byCategory) {
|
|
296
|
+
console.log(` ${c.cyan}${cat}${c.reset}`);
|
|
297
|
+
for (const b of blocks) {
|
|
298
|
+
console.log(` ${c.bold}nexus add ${b.id}${c.reset} ${c.dim}${b.description}${c.reset}`);
|
|
299
|
+
}
|
|
300
|
+
console.log('');
|
|
301
|
+
}
|
|
302
|
+
console.log(` ${c.dim}Run ${c.reset}${c.bold}nexus add <id>${c.reset}${c.dim} to install a block.${c.reset}\n`);
|
|
303
|
+
}
|
|
304
|
+
async function installBlock(blockId, root) {
|
|
305
|
+
const block = BLOCKS.find((b) => b.id === blockId);
|
|
306
|
+
if (!block) {
|
|
307
|
+
console.error(`\n ${c.red}✖${c.reset} Block "${blockId}" not found.\n`);
|
|
308
|
+
listBlocks();
|
|
309
|
+
process.exit(1);
|
|
310
|
+
}
|
|
311
|
+
console.log(`\n ${c.mag}${c.bold}◆ Nexus Marketplace${c.reset} Installing ${c.bold}${block.name}${c.reset}...\n`);
|
|
312
|
+
for (const file of block.files) {
|
|
313
|
+
const fullPath = join(root, file.path);
|
|
314
|
+
await mkdir(dirname(fullPath), { recursive: true });
|
|
315
|
+
await writeFile(fullPath, file.content, 'utf-8');
|
|
316
|
+
console.log(` ${c.green}✔${c.reset} Created ${c.cyan}${file.path}${c.reset}`);
|
|
317
|
+
}
|
|
318
|
+
if (block.postInstall) {
|
|
319
|
+
console.log(`\n ${c.yellow}→${c.reset} ${block.postInstall}`);
|
|
320
|
+
}
|
|
321
|
+
console.log(`\n ${c.green}◆${c.reset} ${block.name} installed successfully.\n`);
|
|
322
|
+
}
|
|
323
|
+
/** Interactive block picker when no ID is given */
|
|
324
|
+
async function pickBlock(root) {
|
|
325
|
+
listBlocks();
|
|
326
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
327
|
+
const answer = await new Promise((resolve) => {
|
|
328
|
+
rl.question(` Which block? ${c.dim}(id)${c.reset} `, resolve);
|
|
329
|
+
});
|
|
330
|
+
rl.close();
|
|
331
|
+
await installBlock(answer.trim(), root);
|
|
332
|
+
}
|
|
333
|
+
/** Entry point called by packages/cli/src/bin.ts */
|
|
334
|
+
export async function runAdd(opts) {
|
|
335
|
+
if (!opts.blockId) {
|
|
336
|
+
await pickBlock(opts.root);
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
await installBlock(opts.blockId, opts.root);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
//# sourceMappingURL=add.js.map
|
package/dist/add.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"add.js","sourceRoot":"","sources":["../src/add.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nexus Bundle Budget Analyzer
|
|
3
|
+
*
|
|
4
|
+
* Generates a detailed visual report of JS payload per route.
|
|
5
|
+
* Runs automatically after `nexus build` or standalone with `nexus analyze`.
|
|
6
|
+
*
|
|
7
|
+
* Terminal output:
|
|
8
|
+
*
|
|
9
|
+
* ◆ Nexus Bundle Analysis
|
|
10
|
+
* ════════════════════════════════════════════════════════
|
|
11
|
+
*
|
|
12
|
+
* Route: /
|
|
13
|
+
* ┌─────────────────────────────┬────────┬────────┬───────┐
|
|
14
|
+
* │ Module │ Raw │ Gzip │ Share │
|
|
15
|
+
* ├─────────────────────────────┼────────┼────────┼───────┤
|
|
16
|
+
* │ @nexus_js/runtime (shared) │ 4.2KB │ 1.8KB │ ███░ │
|
|
17
|
+
* │ islands/Counter.client.js │ 1.1KB │ 0.5KB │ █░░░ │
|
|
18
|
+
* │ islands/SearchBar.client.js │ 2.8KB │ 1.1KB │ ██░░ │
|
|
19
|
+
* ├─────────────────────────────┼────────┼────────┼───────┤
|
|
20
|
+
* │ TOTAL │ 8.1KB │ 3.4KB │ │
|
|
21
|
+
* └─────────────────────────────┴────────┴────────┴───────┘
|
|
22
|
+
* Budget: ✓ Under 10KB limit
|
|
23
|
+
*
|
|
24
|
+
* Route: /blog/[slug] ← OVER BUDGET ⚠
|
|
25
|
+
* ...
|
|
26
|
+
*
|
|
27
|
+
* ─── Summary ─────────────────────────────────────────────
|
|
28
|
+
* Routes analyzed: 12
|
|
29
|
+
* Under budget: 10
|
|
30
|
+
* Over budget: 2 ⚠ /dashboard, /checkout
|
|
31
|
+
* Largest route: /checkout (48KB gzip)
|
|
32
|
+
* Zero-JS routes: 4 (/, /about, /blog, /contact)
|
|
33
|
+
*/
|
|
34
|
+
export interface BundleModule {
|
|
35
|
+
name: string;
|
|
36
|
+
path: string;
|
|
37
|
+
rawBytes: number;
|
|
38
|
+
gzipBytes: number;
|
|
39
|
+
kind: 'runtime' | 'island' | 'shared' | 'action';
|
|
40
|
+
}
|
|
41
|
+
export interface RouteBudget {
|
|
42
|
+
route: string;
|
|
43
|
+
modules: BundleModule[];
|
|
44
|
+
totalRaw: number;
|
|
45
|
+
totalGzip: number;
|
|
46
|
+
isZeroJS: boolean;
|
|
47
|
+
overBudget: boolean;
|
|
48
|
+
budgetBytes: number;
|
|
49
|
+
}
|
|
50
|
+
export interface AnalysisReport {
|
|
51
|
+
routes: RouteBudget[];
|
|
52
|
+
runtimeSize: number;
|
|
53
|
+
generatedAt: string;
|
|
54
|
+
totalIslands: number;
|
|
55
|
+
zeroJSRoutes: number;
|
|
56
|
+
overBudgetRoutes: string[];
|
|
57
|
+
}
|
|
58
|
+
export interface AnalyzerOptions {
|
|
59
|
+
root: string;
|
|
60
|
+
/** Gzip budget per route in bytes (default: 50KB) */
|
|
61
|
+
budgetBytes?: number;
|
|
62
|
+
/** Show per-module breakdown (default: true) */
|
|
63
|
+
verbose?: boolean;
|
|
64
|
+
/** Output format */
|
|
65
|
+
format?: 'terminal' | 'json' | 'html';
|
|
66
|
+
/** Write report to file */
|
|
67
|
+
outFile?: string;
|
|
68
|
+
}
|
|
69
|
+
export declare function analyzeBundles(opts: AnalyzerOptions): Promise<AnalysisReport>;
|
|
70
|
+
//# sourceMappingURL=analyzer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAOH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;CAClD;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,qDAAqD;IACrD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gDAAgD;IAChD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,oBAAoB;IACpB,MAAM,CAAC,EAAE,UAAU,GAAG,MAAM,GAAG,MAAM,CAAC;IACtC,2BAA2B;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAmBD,wBAAsB,cAAc,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CA+BnF"}
|