@lenne.tech/cli 1.7.0 → 1.9.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/build/commands/blocks/add.js +4 -3
- package/build/commands/blocks/blocks.js +1 -1
- package/build/commands/claude/plugins.js +17 -12
- package/build/commands/cli/create.js +7 -6
- package/build/commands/cli/rename.js +2 -2
- package/build/commands/completion.js +1 -1
- package/build/commands/components/add.js +4 -3
- package/build/commands/components/components.js +1 -1
- package/build/commands/config/help.js +1 -1
- package/build/commands/config/validate.js +11 -1
- package/build/commands/directus/docker-setup.js +1 -1
- package/build/commands/directus/remove.js +2 -3
- package/build/commands/directus/typegen.js +4 -4
- package/build/commands/doctor.js +15 -26
- package/build/commands/frontend/angular.js +1 -1
- package/build/commands/frontend/nuxt.js +2 -2
- package/build/commands/fullstack/init.js +59 -45
- package/build/commands/git/clean.js +6 -6
- package/build/commands/git/create.js +2 -2
- package/build/commands/git/force-pull.js +6 -3
- package/build/commands/git/get.js +3 -4
- package/build/commands/git/install-scripts.js +2 -2
- package/build/commands/git/rebase.js +5 -2
- package/build/commands/git/rename.js +3 -4
- package/build/commands/git/reset.js +15 -12
- package/build/commands/git/squash.js +5 -2
- package/build/commands/git/undo.js +6 -3
- package/build/commands/git/update.js +5 -5
- package/build/commands/history.js +1 -1
- package/build/commands/mongodb/s3-restore.js +10 -7
- package/build/commands/npm/reinit.js +12 -10
- package/build/commands/qdrant/delete.js +2 -2
- package/build/commands/qdrant/stats.js +2 -11
- package/build/commands/server/add-property.js +47 -32
- package/build/commands/server/create.js +10 -6
- package/build/commands/server/module.js +33 -14
- package/build/commands/server/object.js +2 -2
- package/build/commands/server/permissions.js +318 -0
- package/build/commands/server/set-secrets.js +2 -2
- package/build/commands/starter/chrome-extension.js +5 -5
- package/build/commands/status.js +17 -21
- package/build/commands/templates/list.js +4 -4
- package/build/commands/templates/llm.js +8 -14
- package/build/commands/tools/install-scripts.js +105 -0
- package/build/commands/tools/regex.js +1 -1
- package/build/commands/typescript/create.js +6 -6
- package/build/extensions/api-mode.js +3 -2
- package/build/extensions/config.js +5 -5
- package/build/extensions/frontend-helper.js +10 -6
- package/build/extensions/git.js +11 -11
- package/build/extensions/history.js +1 -1
- package/build/extensions/logger.js +3 -6
- package/build/extensions/package-manager.js +249 -0
- package/build/extensions/parse-properties.js +4 -4
- package/build/extensions/server.js +91 -36
- package/build/extensions/template.js +5 -5
- package/build/extensions/tools.js +23 -5
- package/build/extensions/typescript.js +2 -2
- package/build/lib/claude-cli.js +2 -6
- package/build/lib/fallback-scanner.js +852 -0
- package/build/lib/marketplace.js +7 -7
- package/build/lib/nuxt-base-components.js +9 -8
- package/build/lib/plugin-utils.js +9 -13
- package/build/lib/shell-config.js +5 -8
- package/build/lib/validation.js +5 -5
- package/build/templates/bash-scripts/tools/pcf +9 -0
- package/build/templates/permissions/report.html.ejs +402 -0
- package/docs/commands.md +28 -2
- package/docs/lt.config.md +53 -3
- package/package.json +5 -4
|
@@ -0,0 +1,402 @@
|
|
|
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>Permissions Report</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
--bg: #ffffff; --fg: #1a1a2e; --bg-card: #f8f9fa; --border: #dee2e6;
|
|
10
|
+
--primary: #0d6efd; --success: #198754; --warning: #ffc107; --danger: #dc3545;
|
|
11
|
+
--role-everyone: #198754; --role-noone: #dc3545; --role-admin: #0d6efd;
|
|
12
|
+
--role-user: #e6a817; --role-self: #6c757d; --role-custom: #fd7e14;
|
|
13
|
+
--shadow: 0 1px 3px rgba(0,0,0,0.12);
|
|
14
|
+
}
|
|
15
|
+
@media (prefers-color-scheme: dark) {
|
|
16
|
+
:root {
|
|
17
|
+
--bg: #1a1a2e; --fg: #e0e0e0; --bg-card: #16213e; --border: #3a3a5c;
|
|
18
|
+
--shadow: 0 1px 3px rgba(0,0,0,0.4);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
22
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: var(--bg); color: var(--fg); line-height: 1.6; }
|
|
23
|
+
.layout { display: flex; min-height: 100vh; }
|
|
24
|
+
.sidebar { width: 260px; position: sticky; top: 0; height: 100vh; overflow-y: auto; background: var(--bg-card); border-right: 1px solid var(--border); padding: 1rem; flex-shrink: 0; }
|
|
25
|
+
.sidebar h3 { font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--primary); margin-bottom: 0.5rem; }
|
|
26
|
+
.sidebar a { display: block; padding: 0.25rem 0.5rem; color: var(--fg); text-decoration: none; font-size: 0.85rem; border-radius: 4px; }
|
|
27
|
+
.sidebar a:hover { background: var(--border); }
|
|
28
|
+
.sidebar a.indent { padding-left: 1.5rem; font-size: 0.8rem; opacity: 0.8; }
|
|
29
|
+
.main { flex: 1; padding: 2rem; max-width: 1200px; }
|
|
30
|
+
.dashboard { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 1rem; margin-bottom: 2rem; }
|
|
31
|
+
.stat { background: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; padding: 1rem; text-align: center; box-shadow: var(--shadow); }
|
|
32
|
+
.stat .num { font-size: 2rem; font-weight: 700; color: var(--primary); }
|
|
33
|
+
.stat .label { font-size: 0.8rem; opacity: 0.7; }
|
|
34
|
+
.stat.warn .num { color: var(--warning); }
|
|
35
|
+
.stat.danger .num { color: var(--danger); }
|
|
36
|
+
.controls { display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: 1.5rem; align-items: center; }
|
|
37
|
+
.controls input, .controls select { padding: 0.4rem 0.8rem; border: 1px solid var(--border); border-radius: 6px; background: var(--bg); color: var(--fg); font-size: 0.9rem; }
|
|
38
|
+
.controls input { flex: 1; min-width: 200px; }
|
|
39
|
+
.controls label { font-size: 0.85rem; display: flex; align-items: center; gap: 0.3rem; }
|
|
40
|
+
.badge { display: inline-block; padding: 0.15rem 0.5rem; border-radius: 12px; font-size: 0.75rem; font-weight: 600; color: #fff; }
|
|
41
|
+
.badge-everyone { background: var(--role-everyone); }
|
|
42
|
+
.badge-noone { background: var(--role-noone); }
|
|
43
|
+
.badge-admin { background: var(--role-admin); }
|
|
44
|
+
.badge-user { background: var(--role-user); color: #000; }
|
|
45
|
+
.badge-self, .badge-creator { background: var(--role-self); }
|
|
46
|
+
.badge-custom { background: var(--role-custom); }
|
|
47
|
+
.badge-warn { background: var(--warning); color: #000; }
|
|
48
|
+
section { margin-bottom: 2rem; }
|
|
49
|
+
h1 { font-size: 1.8rem; margin-bottom: 0.5rem; }
|
|
50
|
+
h2 { font-size: 1.4rem; margin: 1.5rem 0 0.75rem; padding-bottom: 0.3rem; border-bottom: 2px solid var(--primary); }
|
|
51
|
+
h3 { font-size: 1.1rem; margin: 1rem 0 0.5rem; }
|
|
52
|
+
.meta { font-size: 0.85rem; opacity: 0.7; margin-bottom: 0.25rem; }
|
|
53
|
+
table { width: 100%; border-collapse: collapse; margin: 0.5rem 0 1rem; font-size: 0.85rem; }
|
|
54
|
+
th, td { padding: 0.4rem 0.6rem; text-align: left; border: 1px solid var(--border); }
|
|
55
|
+
th { background: var(--bg-card); cursor: pointer; user-select: none; white-space: nowrap; }
|
|
56
|
+
th:hover { background: var(--border); }
|
|
57
|
+
tr:nth-child(even) { background: var(--bg-card); }
|
|
58
|
+
.collapsible { cursor: pointer; }
|
|
59
|
+
.collapsible::before { content: '\25B6'; display: inline-block; margin-right: 0.5rem; transition: transform 0.2s; font-size: 0.8rem; }
|
|
60
|
+
.collapsible.open::before { transform: rotate(90deg); }
|
|
61
|
+
.collapse-content { display: none; }
|
|
62
|
+
.collapse-content.open { display: block; }
|
|
63
|
+
.warning-row { background: rgba(255, 193, 7, 0.1) !important; }
|
|
64
|
+
.btn { padding: 0.4rem 0.8rem; border: 1px solid var(--border); border-radius: 6px; background: var(--bg-card); color: var(--fg); cursor: pointer; font-size: 0.8rem; }
|
|
65
|
+
.btn:hover { background: var(--border); }
|
|
66
|
+
.module-header { position: sticky; top: 0; background: var(--bg); z-index: 10; padding: 0.5rem 0; border-bottom: 1px solid var(--border); }
|
|
67
|
+
@media (max-width: 768px) {
|
|
68
|
+
.sidebar { display: none; }
|
|
69
|
+
.main { padding: 1rem; }
|
|
70
|
+
.dashboard { grid-template-columns: repeat(2, 1fr); }
|
|
71
|
+
}
|
|
72
|
+
</style>
|
|
73
|
+
</head>
|
|
74
|
+
<body>
|
|
75
|
+
<div class="layout">
|
|
76
|
+
<nav class="sidebar">
|
|
77
|
+
<h3>Permissions Report</h3>
|
|
78
|
+
<a href="#dashboard">Dashboard</a>
|
|
79
|
+
<a href="#role-index">Role Index</a>
|
|
80
|
+
<a href="#warnings">Warnings (<%= props.warnings.length %>)</a>
|
|
81
|
+
<hr style="margin:0.5rem 0;border-color:var(--border)">
|
|
82
|
+
<% for (const mod of props.modules) { %>
|
|
83
|
+
<a href="#mod-<%= mod.name %>"><%= mod.name %></a>
|
|
84
|
+
<% for (const model of mod.models) { %><a href="#model-<%= mod.name %>-<%= model.className %>" class="indent">Model: <%= model.className %></a><% } %>
|
|
85
|
+
<% for (const ctrl of mod.controllers) { %><a href="#ctrl-<%= mod.name %>-<%= ctrl.className %>" class="indent">Ctrl: <%= ctrl.className %></a><% } %>
|
|
86
|
+
<% for (const res of mod.resolvers) { %><a href="#res-<%= mod.name %>-<%= res.className %>" class="indent">Resolver: <%= res.className %></a><% } %>
|
|
87
|
+
<% } %>
|
|
88
|
+
<% if (props.objects.length > 0) { %>
|
|
89
|
+
<hr style="margin:0.5rem 0;border-color:var(--border)">
|
|
90
|
+
<a href="#subobjects">SubObjects</a>
|
|
91
|
+
<% } %>
|
|
92
|
+
<hr style="margin:0.5rem 0;border-color:var(--border)">
|
|
93
|
+
<button class="btn" onclick="exportAs('md')">Export MD</button>
|
|
94
|
+
<button class="btn" onclick="exportAs('json')">Export JSON</button>
|
|
95
|
+
</nav>
|
|
96
|
+
|
|
97
|
+
<div class="main">
|
|
98
|
+
<h1>Permissions Report</h1>
|
|
99
|
+
<p class="meta">Generated: <%= props.generated %></p>
|
|
100
|
+
<p class="meta">Project: <%= props.projectPath %></p>
|
|
101
|
+
|
|
102
|
+
<section id="dashboard">
|
|
103
|
+
<div class="dashboard">
|
|
104
|
+
<div class="stat"><div class="num"><%= props.stats.totalModules %></div><div class="label">Modules</div></div>
|
|
105
|
+
<div class="stat"><div class="num"><%= props.stats.totalModels %></div><div class="label">Models</div></div>
|
|
106
|
+
<div class="stat"><div class="num"><%= props.stats.totalEndpoints %></div><div class="label">Endpoints</div></div>
|
|
107
|
+
<div class="stat"><div class="num"><%= props.stats.totalSubObjects %></div><div class="label">SubObjects</div></div>
|
|
108
|
+
<div class="stat <%= props.stats.totalWarnings > 0 ? 'danger' : '' %>"><div class="num"><%= props.stats.totalWarnings %></div><div class="label">Warnings</div></div>
|
|
109
|
+
<% var ecClass = props.stats.endpointCoverage >= 90 ? '' : props.stats.endpointCoverage >= 70 ? 'warn' : 'danger'; %>
|
|
110
|
+
<div class="stat <%= ecClass %>"><div class="num"><%= props.stats.endpointCoverage %>%</div><div class="label">Endpoint Coverage</div></div>
|
|
111
|
+
<% var scClass = props.stats.securityCoverage >= 90 ? '' : props.stats.securityCoverage >= 70 ? 'warn' : 'danger'; %>
|
|
112
|
+
<div class="stat <%= scClass %>"><div class="num"><%= props.stats.securityCoverage %>%</div><div class="label">Security Coverage</div></div>
|
|
113
|
+
</div>
|
|
114
|
+
</section>
|
|
115
|
+
|
|
116
|
+
<section>
|
|
117
|
+
<div class="controls">
|
|
118
|
+
<input type="text" id="search" placeholder="Search modules, fields, roles..." oninput="filterAll()">
|
|
119
|
+
<select id="roleFilter" onchange="filterAll()">
|
|
120
|
+
<option value="">All Roles</option>
|
|
121
|
+
</select>
|
|
122
|
+
<label><input type="checkbox" id="warnOnly" onchange="filterAll()"> Warnings only</label>
|
|
123
|
+
</div>
|
|
124
|
+
</section>
|
|
125
|
+
|
|
126
|
+
<section id="role-index">
|
|
127
|
+
<h2 class="collapsible open" onclick="toggle(this)">Role Index</h2>
|
|
128
|
+
<div class="collapse-content open">
|
|
129
|
+
<% if (props.roleEnums.length > 0) { %>
|
|
130
|
+
<table>
|
|
131
|
+
<thead><tr><th>Enum</th><th>Value</th><th>Type</th></tr></thead>
|
|
132
|
+
<tbody>
|
|
133
|
+
<% for (const e of props.roleEnums) { for (const v of e.values) { %>
|
|
134
|
+
<tr>
|
|
135
|
+
<td><%= e.name %>.<%= v.key %></td>
|
|
136
|
+
<td><%= v.key.startsWith('S_') ? '(system)' : v.value %></td>
|
|
137
|
+
<td><span class="badge <%= getBadgeClass(v.key) %>"><%= v.key.startsWith('S_') ? 'System' : 'Real' %></span></td>
|
|
138
|
+
</tr>
|
|
139
|
+
<% } } %>
|
|
140
|
+
</tbody>
|
|
141
|
+
</table>
|
|
142
|
+
<% } else { %>
|
|
143
|
+
<p><em>No role enums found.</em></p>
|
|
144
|
+
<% } %>
|
|
145
|
+
</div>
|
|
146
|
+
</section>
|
|
147
|
+
|
|
148
|
+
<section id="warnings">
|
|
149
|
+
<h2 class="collapsible open" onclick="toggle(this)">Warnings (<%= props.warnings.length %>)</h2>
|
|
150
|
+
<div class="collapse-content open">
|
|
151
|
+
<% if (props.warnings.length > 0) { %>
|
|
152
|
+
<table>
|
|
153
|
+
<thead><tr><th>#</th><th>Module</th><th>File</th><th>Type</th><th>Details</th></tr></thead>
|
|
154
|
+
<tbody>
|
|
155
|
+
<% props.warnings.forEach((w, i) => { %>
|
|
156
|
+
<tr class="warning-row">
|
|
157
|
+
<td><%= i + 1 %></td>
|
|
158
|
+
<td><%= w.module %></td>
|
|
159
|
+
<td><%= w.file.split('/').pop() %></td>
|
|
160
|
+
<td><span class="badge badge-warn"><%= w.type %></span></td>
|
|
161
|
+
<td><%= w.details %></td>
|
|
162
|
+
</tr>
|
|
163
|
+
<% }) %>
|
|
164
|
+
</tbody>
|
|
165
|
+
</table>
|
|
166
|
+
<% } else { %>
|
|
167
|
+
<p><em>No warnings found.</em></p>
|
|
168
|
+
<% } %>
|
|
169
|
+
</div>
|
|
170
|
+
</section>
|
|
171
|
+
|
|
172
|
+
<% for (const mod of props.modules) { %>
|
|
173
|
+
<section class="module-section" id="mod-<%= mod.name %>" data-module="<%= mod.name %>" data-has-warnings="<%= props.warnings.some(w => w.module === mod.name) %>">
|
|
174
|
+
<div class="module-header">
|
|
175
|
+
<h2 class="collapsible open" onclick="toggle(this)">Module: <%= mod.name %></h2>
|
|
176
|
+
</div>
|
|
177
|
+
<div class="collapse-content open">
|
|
178
|
+
|
|
179
|
+
<% for (const model of mod.models) { %>
|
|
180
|
+
<div id="model-<%= mod.name %>-<%= model.className %>">
|
|
181
|
+
<h3>Model: <%= model.className %></h3>
|
|
182
|
+
<p class="meta">File: <%= model.filePath %></p>
|
|
183
|
+
<% if (model.extendsClass) { %><p class="meta">Extends: <%= model.extendsClass %></p><% } %>
|
|
184
|
+
<p class="meta">Class Restriction: <% if (model.classRestriction.length > 0) { model.classRestriction.forEach(r => { %><span class="badge <%= getBadgeClass(r) %>"><%= r %></span> <% }) } else { %><em>(none)</em><% } %></p>
|
|
185
|
+
<p class="meta">securityCheck: <% if (model.securityCheck) { %><%= model.securityCheck.summary %><% } else { %><em>Not present</em><% } %></p>
|
|
186
|
+
<% if (model.fields.length > 0) { %>
|
|
187
|
+
<table>
|
|
188
|
+
<thead><tr><th>Field</th><th>Roles</th><th>Source</th></tr></thead>
|
|
189
|
+
<tbody>
|
|
190
|
+
<% for (const f of model.fields) { %>
|
|
191
|
+
<tr>
|
|
192
|
+
<td><%= f.name %></td>
|
|
193
|
+
<td><%= f.roles %></td>
|
|
194
|
+
<td><%= f.inherited ? 'inherited' : 'local' %></td>
|
|
195
|
+
</tr>
|
|
196
|
+
<% } %>
|
|
197
|
+
</tbody>
|
|
198
|
+
</table>
|
|
199
|
+
<% } %>
|
|
200
|
+
</div>
|
|
201
|
+
<% } %>
|
|
202
|
+
|
|
203
|
+
<% for (const input of mod.inputs) { %>
|
|
204
|
+
<div>
|
|
205
|
+
<h3>Input: <%= input.className %></h3>
|
|
206
|
+
<p class="meta">File: <%= input.filePath %></p>
|
|
207
|
+
<% if (input.extendsClass) { %><p class="meta">Extends: <%= input.extendsClass %></p><% } %>
|
|
208
|
+
<% if (input.fields.length > 0) { %>
|
|
209
|
+
<table>
|
|
210
|
+
<thead><tr><th>Field</th><th>Roles</th></tr></thead>
|
|
211
|
+
<tbody>
|
|
212
|
+
<% for (const f of input.fields) { %>
|
|
213
|
+
<tr><td><%= f.name %></td><td><%= f.roles %></td></tr>
|
|
214
|
+
<% } %>
|
|
215
|
+
</tbody>
|
|
216
|
+
</table>
|
|
217
|
+
<% } %>
|
|
218
|
+
</div>
|
|
219
|
+
<% } %>
|
|
220
|
+
|
|
221
|
+
<% for (const ctrl of mod.controllers) { %>
|
|
222
|
+
<div id="ctrl-<%= mod.name %>-<%= ctrl.className %>">
|
|
223
|
+
<h3>Controller: <%= ctrl.className %></h3>
|
|
224
|
+
<p class="meta">File: <%= ctrl.filePath %></p>
|
|
225
|
+
<p class="meta">Class Roles: <% if (ctrl.classRoles.length > 0) { ctrl.classRoles.forEach(r => { %><span class="badge <%= getBadgeClass(r) %>"><%= r %></span> <% }) } else { %><em>(none)</em><% } %></p>
|
|
226
|
+
<% if (ctrl.methods.length > 0) { %>
|
|
227
|
+
<table>
|
|
228
|
+
<thead><tr><th>Method</th><th>HTTP</th><th>Route</th><th>Roles</th><th>Effective</th></tr></thead>
|
|
229
|
+
<tbody>
|
|
230
|
+
<% for (const m of ctrl.methods) { const eff = m.roles.length > 0 ? m.roles : ctrl.classRoles; %>
|
|
231
|
+
<tr>
|
|
232
|
+
<td><%= m.name %></td>
|
|
233
|
+
<td><%= m.httpMethod %></td>
|
|
234
|
+
<td><%= m.route || '/' %></td>
|
|
235
|
+
<td><% if (m.roles.length > 0) { m.roles.forEach(r => { %><span class="badge <%= getBadgeClass(r) %>"><%= r %></span> <% }) } else { %><em>(none)</em><% } %></td>
|
|
236
|
+
<td><% eff.forEach(r => { %><span class="badge <%= getBadgeClass(r) %>"><%= r %></span> <% }) %><%= m.roles.length === 0 && ctrl.classRoles.length > 0 ? '(class)' : '' %></td>
|
|
237
|
+
</tr>
|
|
238
|
+
<% } %>
|
|
239
|
+
</tbody>
|
|
240
|
+
</table>
|
|
241
|
+
<% } %>
|
|
242
|
+
</div>
|
|
243
|
+
<% } %>
|
|
244
|
+
|
|
245
|
+
<% for (const res of mod.resolvers) { %>
|
|
246
|
+
<div id="res-<%= mod.name %>-<%= res.className %>">
|
|
247
|
+
<h3>Resolver: <%= res.className %></h3>
|
|
248
|
+
<p class="meta">File: <%= res.filePath %></p>
|
|
249
|
+
<p class="meta">Class Roles: <% if (res.classRoles.length > 0) { res.classRoles.forEach(r => { %><span class="badge <%= getBadgeClass(r) %>"><%= r %></span> <% }) } else { %><em>(none)</em><% } %></p>
|
|
250
|
+
<% if (res.methods.length > 0) { %>
|
|
251
|
+
<table>
|
|
252
|
+
<thead><tr><th>Method</th><th>Type</th><th>Roles</th><th>Effective</th></tr></thead>
|
|
253
|
+
<tbody>
|
|
254
|
+
<% for (const m of res.methods) { const eff = m.roles.length > 0 ? m.roles : res.classRoles; %>
|
|
255
|
+
<tr>
|
|
256
|
+
<td><%= m.name %></td>
|
|
257
|
+
<td><%= m.httpMethod %></td>
|
|
258
|
+
<td><% if (m.roles.length > 0) { m.roles.forEach(r => { %><span class="badge <%= getBadgeClass(r) %>"><%= r %></span> <% }) } else { %><em>(none)</em><% } %></td>
|
|
259
|
+
<td><% eff.forEach(r => { %><span class="badge <%= getBadgeClass(r) %>"><%= r %></span> <% }) %><%= m.roles.length === 0 && res.classRoles.length > 0 ? '(class)' : '' %></td>
|
|
260
|
+
</tr>
|
|
261
|
+
<% } %>
|
|
262
|
+
</tbody>
|
|
263
|
+
</table>
|
|
264
|
+
<% } %>
|
|
265
|
+
</div>
|
|
266
|
+
<% } %>
|
|
267
|
+
|
|
268
|
+
</div>
|
|
269
|
+
</section>
|
|
270
|
+
<% } %>
|
|
271
|
+
|
|
272
|
+
<% if (props.objects.length > 0) { %>
|
|
273
|
+
<section id="subobjects">
|
|
274
|
+
<h2>SubObjects</h2>
|
|
275
|
+
<% for (const obj of props.objects) { %>
|
|
276
|
+
<div>
|
|
277
|
+
<h3><%= obj.className %></h3>
|
|
278
|
+
<p class="meta">File: <%= obj.filePath %></p>
|
|
279
|
+
<% if (obj.extendsClass) { %><p class="meta">Extends: <%= obj.extendsClass %></p><% } %>
|
|
280
|
+
<% if (obj.fields.length > 0) { %>
|
|
281
|
+
<table>
|
|
282
|
+
<thead><tr><th>Field</th><th>Roles</th><th>Source</th></tr></thead>
|
|
283
|
+
<tbody>
|
|
284
|
+
<% for (const f of obj.fields) { %>
|
|
285
|
+
<tr><td><%= f.name %></td><td><%= f.roles %></td><td><%= f.inherited ? 'inherited' : 'local' %></td></tr>
|
|
286
|
+
<% } %>
|
|
287
|
+
</tbody>
|
|
288
|
+
</table>
|
|
289
|
+
<% } %>
|
|
290
|
+
</div>
|
|
291
|
+
<% } %>
|
|
292
|
+
</section>
|
|
293
|
+
<% } %>
|
|
294
|
+
|
|
295
|
+
</div>
|
|
296
|
+
</div>
|
|
297
|
+
|
|
298
|
+
<script>
|
|
299
|
+
var DATA = <%- JSON.stringify({ modules: props.modules, objects: props.objects, roleEnums: props.roleEnums, stats: props.stats, warnings: props.warnings }) %>;
|
|
300
|
+
|
|
301
|
+
// Populate role filter
|
|
302
|
+
(function() {
|
|
303
|
+
var sel = document.getElementById('roleFilter');
|
|
304
|
+
var roles = {};
|
|
305
|
+
DATA.roleEnums.forEach(function(e) { e.values.forEach(function(v) { roles[v.key] = true; }); });
|
|
306
|
+
DATA.modules.forEach(function(mod) {
|
|
307
|
+
mod.models.forEach(function(model) {
|
|
308
|
+
model.classRestriction.forEach(function(r) { roles[r] = true; });
|
|
309
|
+
model.fields.forEach(function(f) {
|
|
310
|
+
var m = f.roles.match(/`([^`]+)`/g);
|
|
311
|
+
if (m) m.forEach(function(r) { roles[r.replace(/`/g, '')] = true; });
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
mod.controllers.forEach(function(ep) {
|
|
315
|
+
ep.classRoles.forEach(function(r) { roles[r] = true; });
|
|
316
|
+
ep.methods.forEach(function(m) { m.roles.forEach(function(r) { roles[r] = true; }); });
|
|
317
|
+
});
|
|
318
|
+
mod.resolvers.forEach(function(ep) {
|
|
319
|
+
ep.classRoles.forEach(function(r) { roles[r] = true; });
|
|
320
|
+
ep.methods.forEach(function(m) { m.roles.forEach(function(r) { roles[r] = true; }); });
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
Object.keys(roles).sort().forEach(function(r) {
|
|
324
|
+
var o = document.createElement('option');
|
|
325
|
+
o.value = r; o.textContent = r;
|
|
326
|
+
sel.appendChild(o);
|
|
327
|
+
});
|
|
328
|
+
})();
|
|
329
|
+
|
|
330
|
+
function toggle(el) {
|
|
331
|
+
el.classList.toggle('open');
|
|
332
|
+
var content = el.nextElementSibling;
|
|
333
|
+
if (content) content.classList.toggle('open');
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function filterAll() {
|
|
337
|
+
var q = document.getElementById('search').value.toLowerCase();
|
|
338
|
+
var role = document.getElementById('roleFilter').value;
|
|
339
|
+
var warnOnly = document.getElementById('warnOnly').checked;
|
|
340
|
+
|
|
341
|
+
document.querySelectorAll('.module-section').forEach(function(section) {
|
|
342
|
+
var hasWarnings = section.dataset.hasWarnings === 'true';
|
|
343
|
+
var text = section.textContent.toLowerCase();
|
|
344
|
+
var show = true;
|
|
345
|
+
if (q && !text.includes(q)) show = false;
|
|
346
|
+
if (warnOnly && !hasWarnings) show = false;
|
|
347
|
+
if (role && !text.includes(role.toLowerCase())) show = false;
|
|
348
|
+
section.style.display = show ? '' : 'none';
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
var warningsSection = document.getElementById('warnings');
|
|
352
|
+
if (warningsSection) {
|
|
353
|
+
warningsSection.querySelectorAll('tbody tr').forEach(function(row) {
|
|
354
|
+
var text = row.textContent.toLowerCase();
|
|
355
|
+
var show = true;
|
|
356
|
+
if (q && !text.includes(q)) show = false;
|
|
357
|
+
row.style.display = show ? '' : 'none';
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
document.querySelectorAll('th').forEach(function(th) {
|
|
363
|
+
th.addEventListener('click', function() {
|
|
364
|
+
var table = this.closest('table');
|
|
365
|
+
var tbody = table.querySelector('tbody');
|
|
366
|
+
if (!tbody) return;
|
|
367
|
+
var idx = Array.from(this.parentNode.children).indexOf(this);
|
|
368
|
+
var rows = Array.from(tbody.querySelectorAll('tr'));
|
|
369
|
+
var asc = this.dataset.sort !== 'asc';
|
|
370
|
+
rows.sort(function(a, b) {
|
|
371
|
+
var at = (a.children[idx] || {}).textContent || '';
|
|
372
|
+
var bt = (b.children[idx] || {}).textContent || '';
|
|
373
|
+
return asc ? at.localeCompare(bt) : bt.localeCompare(at);
|
|
374
|
+
});
|
|
375
|
+
this.dataset.sort = asc ? 'asc' : 'desc';
|
|
376
|
+
rows.forEach(function(r) { tbody.appendChild(r); });
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
function exportAs(fmt) {
|
|
381
|
+
var blob = new Blob([JSON.stringify(DATA, null, 2)], { type: fmt === 'json' ? 'application/json' : 'text/plain' });
|
|
382
|
+
var a = document.createElement('a');
|
|
383
|
+
a.href = URL.createObjectURL(blob);
|
|
384
|
+
a.download = 'permissions.' + fmt;
|
|
385
|
+
a.click();
|
|
386
|
+
}
|
|
387
|
+
</script>
|
|
388
|
+
</body>
|
|
389
|
+
</html>
|
|
390
|
+
<%
|
|
391
|
+
function getBadgeClass(role) {
|
|
392
|
+
if (!role) return 'badge-custom';
|
|
393
|
+
var r = role.toUpperCase();
|
|
394
|
+
if (r === 'S_EVERYONE') return 'badge-everyone';
|
|
395
|
+
if (r === 'S_NO_ONE') return 'badge-noone';
|
|
396
|
+
if (r === 'ADMIN') return 'badge-admin';
|
|
397
|
+
if (r === 'S_USER') return 'badge-user';
|
|
398
|
+
if (r === 'S_SELF') return 'badge-self';
|
|
399
|
+
if (r === 'S_CREATOR') return 'badge-creator';
|
|
400
|
+
return 'badge-custom';
|
|
401
|
+
}
|
|
402
|
+
%>
|
package/docs/commands.md
CHANGED
|
@@ -123,6 +123,30 @@ lt server object [options]
|
|
|
123
123
|
|
|
124
124
|
---
|
|
125
125
|
|
|
126
|
+
### `lt server permissions`
|
|
127
|
+
|
|
128
|
+
Scans all server modules and generates a permissions report showing roles, restrictions, and security gaps.
|
|
129
|
+
|
|
130
|
+
**Usage:**
|
|
131
|
+
```bash
|
|
132
|
+
lt server permissions [options]
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**Options:**
|
|
136
|
+
| Option | Description |
|
|
137
|
+
|--------|-------------|
|
|
138
|
+
| `--path <dir>` | Path to NestJS project (default: auto-detect) |
|
|
139
|
+
| `--output <file>` | Output file (default: `permissions.<format>`) |
|
|
140
|
+
| `--format <md\|json\|html>` | Output format (default: `html` for TTY, `json` for CI) |
|
|
141
|
+
| `--open` / `--no-open` | Open report in browser (default: `true` for TTY) |
|
|
142
|
+
| `--console` | Print summary to console |
|
|
143
|
+
| `--fail-on-warnings` | Exit code 1 on warnings (for CI/CD) |
|
|
144
|
+
| `--noConfirm` | Skip confirmation prompts |
|
|
145
|
+
|
|
146
|
+
**Configuration:** `commands.server.permissions.*`, `defaults.noConfirm`
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
126
150
|
### `lt server addProp`
|
|
127
151
|
|
|
128
152
|
Adds a property to an existing module or object.
|
|
@@ -436,10 +460,12 @@ lt fullstack init [options]
|
|
|
436
460
|
| `--frontend-branch <branch>` | Branch of frontend starter to use (ng-base-starter or nuxt-base-starter) |
|
|
437
461
|
| `--frontend-copy <path>` | Copy frontend from local template directory |
|
|
438
462
|
| `--frontend-link <path>` | Symlink frontend to local template (fastest, changes affect original) |
|
|
439
|
-
| `--git` |
|
|
440
|
-
| `--git-link <url>` | Git repository URL |
|
|
463
|
+
| `--git` | Push initial commit to remote repository (git is always initialized) |
|
|
464
|
+
| `--git-link <url>` | Git remote repository URL (required when `--git` is true) |
|
|
441
465
|
| `--noConfirm` | Skip confirmation prompts |
|
|
442
466
|
|
|
467
|
+
**Note:** Git is always initialized with the `dev` branch. The `--git` flag only controls whether the initial commit is pushed to a remote repository.
|
|
468
|
+
|
|
443
469
|
**Note:** For Nuxt frontends with `--frontend-copy` or `--frontend-link`, specify the path to the `nuxt-base-template/` subdirectory, not the repository root.
|
|
444
470
|
|
|
445
471
|
**Configuration:** `commands.fullstack.*`, `defaults.noConfirm`
|
package/docs/lt.config.md
CHANGED
|
@@ -101,6 +101,7 @@ The `defaults` section contains settings that apply across multiple commands. Th
|
|
|
101
101
|
| `defaults.controller` | `'Rest'` \| `'GraphQL'` \| `'Both'` \| `'auto'` | `'Both'` | server/module, server/create |
|
|
102
102
|
| `defaults.domain` | `string` | - | deployment/create (use `{name}` as placeholder) |
|
|
103
103
|
| `defaults.noConfirm` | `boolean` | `false` | blocks/add, components/add, config/init, git/*, server/create, server/module, npm/reinit, cli/create, typescript/create, fullstack/init, deployment/create, frontend/angular |
|
|
104
|
+
| `defaults.packageManager` | `'npm'` \| `'pnpm'` \| `'yarn'` | `'npm'` | Fallback when no lockfile is found. Auto-detection from lockfiles takes precedence. Used by: all commands that run package manager operations |
|
|
104
105
|
| `defaults.skipInstall` | `boolean` | `false` | git/update |
|
|
105
106
|
| `defaults.skipLint` | `boolean` | `false` | server/module, server/object, server/addProp |
|
|
106
107
|
|
|
@@ -114,6 +115,7 @@ The `defaults` section contains settings that apply across multiple commands. Th
|
|
|
114
115
|
"controller": "Both",
|
|
115
116
|
"domain": "{name}.lenne.tech",
|
|
116
117
|
"noConfirm": false,
|
|
118
|
+
"packageManager": "npm",
|
|
117
119
|
"skipInstall": false,
|
|
118
120
|
"skipLint": false
|
|
119
121
|
}
|
|
@@ -129,10 +131,23 @@ defaults:
|
|
|
129
131
|
controller: Both
|
|
130
132
|
domain: "{name}.lenne.tech"
|
|
131
133
|
noConfirm: false
|
|
134
|
+
packageManager: npm
|
|
132
135
|
skipInstall: false
|
|
133
136
|
skipLint: false
|
|
134
137
|
```
|
|
135
138
|
|
|
139
|
+
### Package Manager Detection
|
|
140
|
+
|
|
141
|
+
The CLI automatically detects the package manager for your project. The detection order is:
|
|
142
|
+
|
|
143
|
+
1. **Lockfile in current directory**: `pnpm-lock.yaml` -> pnpm, `yarn.lock` -> yarn, `package-lock.json` -> npm
|
|
144
|
+
2. **`packageManager` field in package.json** (Corepack standard): e.g., `"packageManager": "pnpm@8.15.0"`
|
|
145
|
+
3. **Lockfile in parent directories** (monorepo support)
|
|
146
|
+
4. **Config fallback**: `defaults.packageManager` from lt.config
|
|
147
|
+
5. **Default**: `npm`
|
|
148
|
+
|
|
149
|
+
This means all CLI commands (`lt server create`, `lt fullstack init`, `lt npm reinit`, etc.) will use the correct package manager automatically without any configuration needed.
|
|
150
|
+
|
|
136
151
|
### Global vs Command-Specific Settings
|
|
137
152
|
|
|
138
153
|
Global defaults provide a convenient way to set organization-wide preferences. Command-specific settings override these when you need different behavior for a particular command.
|
|
@@ -333,6 +348,37 @@ Creates a new server object (embedded document).
|
|
|
333
348
|
|
|
334
349
|
---
|
|
335
350
|
|
|
351
|
+
#### `lt server permissions`
|
|
352
|
+
|
|
353
|
+
Scans server modules and generates a permissions report.
|
|
354
|
+
|
|
355
|
+
| Field | Type | Default | Description |
|
|
356
|
+
|-------|------|---------|-------------|
|
|
357
|
+
| `commands.server.permissions.format` | `'md'` \| `'json'` \| `'html'` | `'html'` (TTY) / `'json'` (CI) | Output format |
|
|
358
|
+
| `commands.server.permissions.output` | `string` | `permissions.<format>` | Output file path |
|
|
359
|
+
| `commands.server.permissions.path` | `string` | auto-detect | Path to NestJS project |
|
|
360
|
+
| `commands.server.permissions.open` | `boolean` | `true` (TTY) / `false` (CI) | Open report in browser |
|
|
361
|
+
| `commands.server.permissions.console` | `boolean` | `false` | Print summary to console |
|
|
362
|
+
| `commands.server.permissions.failOnWarnings` | `boolean` | `false` | Exit code 1 on warnings |
|
|
363
|
+
| `commands.server.permissions.noConfirm` | `boolean` | `false` | Skip confirmation prompts |
|
|
364
|
+
|
|
365
|
+
**Example:**
|
|
366
|
+
```json
|
|
367
|
+
{
|
|
368
|
+
"commands": {
|
|
369
|
+
"server": {
|
|
370
|
+
"permissions": {
|
|
371
|
+
"format": "html",
|
|
372
|
+
"open": true,
|
|
373
|
+
"failOnWarnings": true
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
336
382
|
#### `lt server addProp`
|
|
337
383
|
|
|
338
384
|
Adds a property to an existing module or object.
|
|
@@ -525,8 +571,8 @@ Creates a new fullstack workspace with API and frontend.
|
|
|
525
571
|
| `commands.fullstack.frontendBranch` | `string` | - | Branch of frontend starter to use (ng-base-starter or nuxt-base-starter) |
|
|
526
572
|
| `commands.fullstack.frontendCopy` | `string` | - | Path to local frontend template directory to copy instead of cloning |
|
|
527
573
|
| `commands.fullstack.frontendLink` | `string` | - | Path to local frontend template directory to symlink (fastest, changes affect original) |
|
|
528
|
-
| `commands.fullstack.git` | `boolean` | - |
|
|
529
|
-
| `commands.fullstack.gitLink` | `string` | - | Git repository URL |
|
|
574
|
+
| `commands.fullstack.git` | `boolean` | - | Push initial commit to remote repository (git is always initialized with `dev` branch) |
|
|
575
|
+
| `commands.fullstack.gitLink` | `string` | - | Git remote repository URL (required when `git` is true) |
|
|
530
576
|
|
|
531
577
|
**Example:**
|
|
532
578
|
```json
|
|
@@ -912,6 +958,7 @@ The `meta` section stores project information.
|
|
|
912
958
|
"controller": "Both",
|
|
913
959
|
"domain": "{name}.lenne.tech",
|
|
914
960
|
"noConfirm": false,
|
|
961
|
+
"packageManager": "npm",
|
|
915
962
|
"skipInstall": false,
|
|
916
963
|
"skipLint": false
|
|
917
964
|
},
|
|
@@ -924,7 +971,8 @@ The `meta` section stores project information.
|
|
|
924
971
|
},
|
|
925
972
|
"fullstack": {
|
|
926
973
|
"frontend": "nuxt",
|
|
927
|
-
"git": false
|
|
974
|
+
"git": false,
|
|
975
|
+
"gitLink": "https://github.com/myorg/myproject.git"
|
|
928
976
|
},
|
|
929
977
|
"git": {
|
|
930
978
|
"defaultBranch": "develop",
|
|
@@ -959,6 +1007,7 @@ defaults:
|
|
|
959
1007
|
controller: Both
|
|
960
1008
|
domain: "{name}.lenne.tech"
|
|
961
1009
|
noConfirm: false
|
|
1010
|
+
packageManager: npm
|
|
962
1011
|
skipInstall: false
|
|
963
1012
|
skipLint: false
|
|
964
1013
|
|
|
@@ -972,6 +1021,7 @@ commands:
|
|
|
972
1021
|
fullstack:
|
|
973
1022
|
frontend: nuxt
|
|
974
1023
|
git: false
|
|
1024
|
+
gitLink: "https://github.com/myorg/myproject.git"
|
|
975
1025
|
|
|
976
1026
|
git:
|
|
977
1027
|
defaultBranch: develop
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lenne.tech/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "lenne.Tech CLI: lt",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"lenne.Tech",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"lt": "bin/lt"
|
|
19
19
|
},
|
|
20
20
|
"scripts": {
|
|
21
|
+
"check": "npm install && npm run format && npm run build && npm run start",
|
|
21
22
|
"postinstall": "node bin/postinstall.js 2>/dev/null || true",
|
|
22
23
|
"build": "npm run lint && npm run test && npm run clean-build && npm run compile && npm run copy-templates",
|
|
23
24
|
"clean-build": "npx rimraf ./build",
|
|
@@ -50,7 +51,7 @@
|
|
|
50
51
|
"bin"
|
|
51
52
|
],
|
|
52
53
|
"dependencies": {
|
|
53
|
-
"@aws-sdk/client-s3": "3.
|
|
54
|
+
"@aws-sdk/client-s3": "3.986.0",
|
|
54
55
|
"@lenne.tech/cli-plugin-helper": "0.0.14",
|
|
55
56
|
"@types/lodash": "4.17.23",
|
|
56
57
|
"bcrypt": "6.0.0",
|
|
@@ -72,8 +73,8 @@
|
|
|
72
73
|
"@types/jest": "30.0.0",
|
|
73
74
|
"@types/js-yaml": "4.0.9",
|
|
74
75
|
"@types/node": "25.2.2",
|
|
75
|
-
"@typescript-eslint/eslint-plugin": "8.
|
|
76
|
-
"@typescript-eslint/parser": "8.
|
|
76
|
+
"@typescript-eslint/eslint-plugin": "8.55.0",
|
|
77
|
+
"@typescript-eslint/parser": "8.55.0",
|
|
77
78
|
"eslint": "9.39.2",
|
|
78
79
|
"eslint-config-prettier": "10.1.8",
|
|
79
80
|
"husky": "9.1.7",
|