adminforth 2.50.0-next.1 → 2.50.0-next.10

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 (48) hide show
  1. package/commands/createApp/templates/.agents/skills/adminforth/SKILL.md.hbs +42 -0
  2. package/commands/createApp/templates/.agents/skills/adminforth-custom-vue/SKILL.md.hbs +340 -0
  3. package/commands/createApp/templates/.agents/skills/adminforth-hooks/SKILL.md.hbs +70 -0
  4. package/commands/createApp/templates/.agents/skills/adminforth-permissions/SKILL.md.hbs +59 -0
  5. package/commands/createApp/templates/AGENTS.md.hbs +71 -0
  6. package/commands/createApp/templates/CLAUDE.md.hbs +5 -0
  7. package/commands/createApp/templates/Dockerfile.hbs +5 -16
  8. package/commands/createApp/templates/package.json.hbs +6 -18
  9. package/commands/createApp/templates/readme.md.hbs +5 -24
  10. package/commands/createApp/utils.js +52 -5
  11. package/dist/index.d.ts +1 -0
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +2 -3
  14. package/dist/index.js.map +1 -1
  15. package/dist/modules/configValidator.d.ts.map +1 -1
  16. package/dist/modules/configValidator.js +3 -2
  17. package/dist/modules/configValidator.js.map +1 -1
  18. package/dist/modules/restApi.d.ts.map +1 -1
  19. package/dist/modules/restApi.js +9 -1
  20. package/dist/modules/restApi.js.map +1 -1
  21. package/dist/servers/express.d.ts +2 -0
  22. package/dist/servers/express.d.ts.map +1 -1
  23. package/dist/servers/express.js +34 -7
  24. package/dist/servers/express.js.map +1 -1
  25. package/dist/servers/openapi.d.ts.map +1 -1
  26. package/dist/servers/openapi.js +2 -0
  27. package/dist/servers/openapi.js.map +1 -1
  28. package/dist/spa/src/afcl/Link.vue +24 -4
  29. package/dist/spa/src/components/ResourceListTable.vue +1 -0
  30. package/dist/spa/src/components/Sidebar.vue +1 -1
  31. package/dist/spa/src/types/Back.ts +10 -1
  32. package/dist/spa/src/types/Common.ts +1 -1
  33. package/dist/spa/src/types/adapters/AudioAdapter.ts +73 -0
  34. package/dist/spa/src/types/adapters/index.ts +10 -0
  35. package/dist/spa/src/utils/listUtils.ts +3 -0
  36. package/dist/spa/src/views/ShowView.vue +1 -0
  37. package/dist/types/Back.d.ts +14 -1
  38. package/dist/types/Back.d.ts.map +1 -1
  39. package/dist/types/Back.js.map +1 -1
  40. package/dist/types/Common.d.ts +1 -1
  41. package/dist/types/Common.d.ts.map +1 -1
  42. package/dist/types/adapters/AudioAdapter.d.ts +52 -0
  43. package/dist/types/adapters/AudioAdapter.d.ts.map +1 -0
  44. package/dist/types/adapters/AudioAdapter.js +2 -0
  45. package/dist/types/adapters/AudioAdapter.js.map +1 -0
  46. package/dist/types/adapters/index.d.ts +1 -0
  47. package/dist/types/adapters/index.d.ts.map +1 -1
  48. package/package.json +2 -1
@@ -0,0 +1,42 @@
1
+ ---
2
+ name: adminforth
3
+ description: "Use for general AdminForth app work: resources, index.ts, api.ts, schema.prisma, custom UI, Data API, adapters, plugins, and overall project structure. Use specialized skills for permissions, hooks, and custom Vue work."
4
+ user-invocable: true
5
+ ---
6
+
7
+ # AdminForth App Workflow
8
+
9
+ ## When to Use
10
+
11
+ - Editing `index.ts`, `api.ts`, `resources/*.ts`, `custom/**`, environment files, or deployment files.
12
+ - Adding a resource, menu item, custom page, component, custom API, plugin, or adapter.
13
+ - Deciding whether logic belongs in resource config, the Data API, a custom Express route, or frontend customization.
14
+ - Use `adminforth-permissions` when the task is mainly about access control.
15
+ - Use `adminforth-hooks` when the task is mainly about lifecycle hooks.
16
+ - Use `adminforth-custom-vue` when the task is mainly about components under `custom/`, field renderers, or page/login/global injections.
17
+
18
+ ## Project Map
19
+
20
+ - `index.ts`: main AdminForth config, resource registration, menu, and server startup.
21
+ - `api.ts`: custom Express endpoints. Prefer schema-aware handlers so request and response shapes stay explicit.
22
+ - `resources/*.ts`: table or collection definitions, columns, labels, filters, and resource-level behavior.
23
+ - `custom/`: Vue components, custom pages, injections, and static assets.
24
+ {{#if prismaDbUrl}}- `schema.prisma`: optional schema and migration source when this app uses Prisma.{{/if}}
25
+ - `.env.local` and `.env`: local config and secrets.
26
+
27
+ ## Recommended Workflow
28
+
29
+ 1. Change the database schema with Prisma or your own migration tool. AdminForth itself does not change the database schema for you.
30
+ 2. Add or update the matching resource in `resources/*.ts`.
31
+ 3. Register the resource and menu entry in `index.ts`.
32
+ 4. Prefer resource config for labels, field visibility, defaults, and validation before reaching for custom code.
33
+ 5. Use AdminForth Data API for simple CRUD, filtering, sorting, and counts. Use your own ORM or query builder for joins, aggregations, or more complex queries.
34
+ 6. Put custom business endpoints in `api.ts`.
35
+ 7. Put custom UI in `custom/`.
36
+
37
+ ## Commands
38
+
39
+ - Install: `{{packageManager}} install`
40
+ - Start local development: `{{packageManagerRun}} dev`
41
+ {{#if prismaDbUrl}}- Apply local migrations: `{{packageManagerRun}} migrate:local`
42
+ - Create a migration: `{{packageManagerRun}} makemigration{{packageManagerScriptArgSeparator}}--name <change-name>`{{/if}}
@@ -0,0 +1,340 @@
1
+ ---
2
+ name: adminforth-custom-vue
3
+ description: "Use when implementing AdminForth custom Vue UI: field components, page injections, login or global injections, meta-driven component declarations, and frontend packages inside custom/."
4
+ user-invocable: true
5
+ ---
6
+
7
+ # AdminForth Custom Vue Workflow
8
+
9
+ ## When to Use
10
+
11
+ - Editing files under `custom/`.
12
+ - Adding custom field renderers or editors via `column.components`.
13
+ - Adding resource page injections, login injections, or global layout injections.
14
+ - Passing `meta` into reusable Vue components.
15
+ - Installing frontend packages used only by custom AdminForth Vue code.
16
+
17
+ ## `custom/` Directory and `@@/`
18
+
19
+ - `custom/` is the frontend workspace for AdminForth custom Vue code.
20
+ - By default, `@@/Something.vue` means “resolve this path from `customComponentsDir` on the backend”, and `customComponentsDir` defaults to `./custom`.
21
+ - Example: `@@/reports/OrdersHeader.vue` maps to `./custom/reports/OrdersHeader.vue`.
22
+ - The same `@@/` prefix also works for assets and helper files that are bundled into the SPA.
23
+
24
+ ## Frontend Packages in `custom/`
25
+
26
+ - Install frontend-only dependencies inside `custom/`, not in the app root.
27
+
28
+ ```bash
29
+ cd custom
30
+ {{packageManager}} install apexcharts vue3-apexcharts
31
+ ```
32
+
33
+ - If `custom/package.json` does not exist yet, initialize the folder first.
34
+
35
+ ```bash
36
+ cd custom
37
+ {{packageManager}} init
38
+ ```
39
+
40
+ ## Organizing Components
41
+
42
+ - You can freely create subfolders and split components into smaller children.
43
+ - Example structure:
44
+
45
+ ```text
46
+ custom/
47
+ reports/
48
+ OrdersTopPanel.vue
49
+ parts/
50
+ OrdersTotals.vue
51
+ OrdersFilters.vue
52
+ ```
53
+
54
+ - Example parent component using subcomponents:
55
+
56
+ ```vue
57
+ <template>
58
+ <section class="grid gap-4 md:grid-cols-2">
59
+ <OrdersTotals :record="record" />
60
+ <OrdersFilters :meta="meta" />
61
+ </section>
62
+ </template>
63
+
64
+ <script setup lang="ts">
65
+ import OrdersTotals from './parts/OrdersTotals.vue';
66
+ import OrdersFilters from './parts/OrdersFilters.vue';
67
+
68
+ defineProps<{
69
+ record?: any;
70
+ meta?: any;
71
+ }>();
72
+ </script>
73
+ ```
74
+
75
+ - In AdminForth config you still reference only the entry component:
76
+
77
+ ```ts
78
+ beforeBreadcrumbs: '@@/reports/OrdersTopPanel.vue'
79
+ ```
80
+
81
+ ## Short vs Full Component Declaration
82
+
83
+ - Short declaration is just a file string:
84
+
85
+ ```ts
86
+ show: '@@/RoomsCell.vue'
87
+ ```
88
+
89
+ - Full declaration lets you pass `meta`:
90
+
91
+ ```ts
92
+ show: {
93
+ file: '@@/RoomsCell.vue',
94
+ meta: {
95
+ filler: '🟨',
96
+ },
97
+ }
98
+ ```
99
+
100
+ - Use full declaration when the same component should behave differently in different places.
101
+ - `meta` is passed into the Vue component as a prop.
102
+ - Common `meta` use cases are reusable display options, plugin settings, `thinEnoughToShrinkTable`, `afOrder`, or custom page layout flags.
103
+
104
+ ## Field Component Spots
105
+
106
+ - `components.show`: custom value renderer on show page.
107
+ - `components.showRow`: replaces the full show-table row.
108
+ - `components.create`: custom editor on create page.
109
+ - `components.edit`: custom editor on edit page.
110
+ - `components.list`: custom value renderer in list cells.
111
+ - `components.filter`: custom filter input.
112
+
113
+ ## Field Component Props and Emits
114
+
115
+ - `show` and `list` components should expect `column`, `record`, `resource`, `adminUser`, and optional `meta`.
116
+ - In practice, custom `edit` and `create` components receive `column`, `value`, `record`, `resource`, `adminUser`, `readonly`, and optional `meta`.
117
+ - Custom `edit` and `create` components can emit:
118
+ - `update:value` to change the current field value.
119
+ - `update:recordFieldValue` to change another field in the same record.
120
+ - `update:inValidity` to report custom validation state.
121
+ - `update:emptiness` to report custom emptiness rules.
122
+ - If you update hidden technical fields through `update:recordFieldValue`, the target column may need `allowModifyWhenNotShowInCreate` or `allowModifyWhenNotShowInEdit`.
123
+
124
+ ## Custom Edit Component Example
125
+
126
+ ```vue
127
+ <template>
128
+ <div class="grid gap-2">
129
+ <Input
130
+ :model-value="localValue"
131
+ :readonly="readonly"
132
+ :placeholder="meta?.placeholder || column.label"
133
+ @update:model-value="onInput"
134
+ />
135
+
136
+ <p v-if="errorMessage" class="text-sm text-red-600">
137
+ {{ errorMessage }}
138
+ </p>
139
+
140
+ <p v-else-if="isEmpty" class="text-sm text-amber-600">
141
+ Value is currently empty
142
+ </p>
143
+ </div>
144
+ </template>
145
+
146
+ <script setup lang="ts">
147
+ import { computed, onMounted, ref } from 'vue';
148
+ import Input from '@/afcl/Input.vue';
149
+ import type {
150
+ AdminForthResourceColumnCommon,
151
+ AdminForthResourceCommon,
152
+ AdminUser,
153
+ } from '@/types/Common';
154
+
155
+ const props = defineProps<{
156
+ column: AdminForthResourceColumnCommon;
157
+ value: string | null | undefined;
158
+ record: Record<string, any>;
159
+ meta?: { minLength?: number; placeholder?: string };
160
+ resource: AdminForthResourceCommon;
161
+ adminUser: AdminUser;
162
+ readonly: boolean;
163
+ }>();
164
+
165
+ const emit = defineEmits<{
166
+ (e: 'update:value', value: string): void;
167
+ (e: 'update:recordFieldValue', payload: { fieldName: string; fieldValue: any }): void;
168
+ (e: 'update:inValidity', value: string | false): void;
169
+ (e: 'update:emptiness', value: boolean): void;
170
+ }>();
171
+
172
+ const localValue = ref(props.value ?? '');
173
+ const WHITESPACE_RE = /\s+/g;
174
+
175
+ const isEmpty = computed(() => localValue.value.trim() === '');
176
+ const errorMessage = computed(() => {
177
+ const minLength = props.meta?.minLength ?? 3;
178
+ if (isEmpty.value) {
179
+ return false;
180
+ }
181
+ return localValue.value.trim().length < minLength
182
+ ? `Minimum length is ${minLength}`
183
+ : false;
184
+ });
185
+
186
+ onMounted(syncState);
187
+
188
+ function onInput(nextValue: string) {
189
+ localValue.value = nextValue;
190
+ emit('update:value', nextValue);
191
+ emit('update:recordFieldValue', {
192
+ fieldName: 'slug',
193
+ fieldValue: nextValue.toLowerCase().trim().replace(WHITESPACE_RE, '-'),
194
+ });
195
+ syncState();
196
+ }
197
+
198
+ function syncState() {
199
+ // for edit/create components where emptiness is not obvious (e.g. image was not uploaded), always emit emptiness when it change
200
+ emit('update:emptiness', isEmpty.value);
201
+
202
+ // for custom validity (e.g. upload still in progress), emit error message or false for valid
203
+ emit('update:inValidity', errorMessage.value || false);
204
+ }
205
+ </script>
206
+ ```
207
+
208
+ ## Custom List and Show Example with `meta`
209
+
210
+ ```ts
211
+ {
212
+ name: 'number_of_rooms',
213
+ components: {
214
+ show: {
215
+ file: '@@/RoomsCell.vue',
216
+ meta: { filler: '🟨', suffix: 'rooms' },
217
+ },
218
+ list: {
219
+ file: '@@/RoomsCell.vue',
220
+ meta: { filler: '🟦', suffix: 'r' },
221
+ },
222
+ },
223
+ }
224
+ ```
225
+
226
+ ```vue
227
+ <template>
228
+ <div class="flex items-center gap-2">
229
+ <span>
230
+ {{ meta?.filler?.repeat(record.number_of_rooms || 0) }}
231
+ </span>
232
+ <span>{{ record.number_of_rooms }} {{ meta?.suffix }}</span>
233
+ </div>
234
+ </template>
235
+
236
+ <script setup lang="ts">
237
+ import type {
238
+ AdminForthResourceColumnCommon,
239
+ AdminForthResourceCommon,
240
+ AdminUser,
241
+ } from '@/types/Common';
242
+
243
+ defineProps<{
244
+ column: AdminForthResourceColumnCommon;
245
+ record: Record<string, any>;
246
+ meta?: { filler?: string; suffix?: string };
247
+ resource: AdminForthResourceCommon;
248
+ adminUser: AdminUser;
249
+ }>();
250
+ </script>
251
+ ```
252
+
253
+ ## Resource Page Injection Spots
254
+
255
+ - `pageInjections.list.beforeBreadcrumbs`
256
+ - `pageInjections.list.afterBreadcrumbs`
257
+ - `pageInjections.list.beforeActionButtons`
258
+ - `pageInjections.list.bottom`
259
+ - `pageInjections.list.threeDotsDropdownItems`
260
+ - `pageInjections.list.customActionIcons`
261
+ - `pageInjections.list.customActionIconsThreeDotsMenuItems`
262
+ - `pageInjections.list.tableBodyStart`
263
+ - `pageInjections.list.tableRowReplace`
264
+ - `pageInjections.show.beforeBreadcrumbs`
265
+ - `pageInjections.show.afterBreadcrumbs`
266
+ - `pageInjections.show.bottom`
267
+ - `pageInjections.show.threeDotsDropdownItems`
268
+ - `pageInjections.edit.beforeBreadcrumbs`
269
+ - `pageInjections.edit.afterBreadcrumbs`
270
+ - `pageInjections.edit.bottom`
271
+ - `pageInjections.edit.threeDotsDropdownItems`
272
+ - `pageInjections.create.beforeBreadcrumbs`
273
+ - `pageInjections.create.afterBreadcrumbs`
274
+ - `pageInjections.create.bottom`
275
+ - `pageInjections.create.threeDotsDropdownItems`
276
+
277
+ ## Login and Global Injection Spots
278
+
279
+ - `customization.loginPageInjections.panelHeader`
280
+ - `customization.loginPageInjections.underInputs`
281
+ - `customization.loginPageInjections.underLoginButton`
282
+ - `customization.globalInjections.userMenu`
283
+ - `customization.globalInjections.header`
284
+ - `customization.globalInjections.sidebar`
285
+ - `customization.globalInjections.sidebarTop`
286
+ - `customization.globalInjections.everyPageBottom`
287
+
288
+ ## Top Injection Example
289
+
290
+ ```ts
291
+ options: {
292
+ pageInjections: {
293
+ list: {
294
+ beforeBreadcrumbs: {
295
+ file: '@@/reports/OrdersTopPanel.vue',
296
+ meta: {
297
+ title: 'Orders overview',
298
+ },
299
+ },
300
+ },
301
+ },
302
+ }
303
+ ```
304
+
305
+ ## List Injection Shrink Behavior
306
+
307
+ - On list pages, AdminForth tries to keep the table itself scrollable when there are no large top or bottom injections.
308
+ - That shrink behavior is affected by four list injection spots: `beforeBreadcrumbs`, `afterBreadcrumbs`, `beforeActionButtons`, and `bottom`.
309
+ - If none of those four spots are used, the table tries to shrink into the viewport and vertical scrolling happens inside the table area.
310
+ - If any of those spots are used, AdminForth assumes the page may now need extra vertical space, so the table stops shrinking and page scrolling moves to the document body instead.
311
+ - If your injected panel is actually compact, set `meta.thinEnoughToShrinkTable: true` on that injection.
312
+ - Important: every injection in those four spots must set `thinEnoughToShrinkTable: true`, otherwise the table will still stay in non-shrinking mode.
313
+
314
+ ```ts
315
+ options: {
316
+ pageInjections: {
317
+ list: {
318
+ bottom: {
319
+ file: '@@/reports/OrdersSummary.vue',
320
+ meta: {
321
+ thinEnoughToShrinkTable: true,
322
+ },
323
+ },
324
+ },
325
+ },
326
+ }
327
+ ```
328
+
329
+ ## Row-Level List Injection Notes
330
+
331
+ - `tableBodyStart` renders extra `<tr>` rows near the start of the table body and receives `resource`, `adminUser`, and `meta`.
332
+ - `tableRowReplace` replaces the default `<tr>` for each record and receives `record`, `resource`, `adminUser`, and `meta`.
333
+ - `customActionIcons` and `customActionIconsThreeDotsMenuItems` are per-record spots and receive `record`, `resource`, `adminUser`, `meta`, and `updateRecords`.
334
+
335
+ ## Practical Rules
336
+
337
+ - Prefer simple string declarations until you actually need `meta`.
338
+ - Reuse one component with multiple full declarations instead of cloning similar files.
339
+ - Keep page injections small unless the layout intentionally becomes page-scrolling.
340
+ - Keep custom edit and create components explicit about validity and emptiness if the default input heuristics are not enough.
@@ -0,0 +1,70 @@
1
+ ---
2
+ name: adminforth-hooks
3
+ description: "Use when implementing or changing AdminForth hooks: beforeDatasourceRequest, afterDatasourceResponse, beforeSave, afterSave, and source-aware show or edit flows."
4
+ user-invocable: true
5
+ ---
6
+
7
+ # AdminForth Hooks Workflow
8
+
9
+ ## When to Use
10
+
11
+ - Editing `resource.hooks` in `resources/*.ts`.
12
+ - Deciding which hook stage should own filtering, normalization, enrichment, validation, or side effects.
13
+ - Handling the difference between show-page requests and edit-page initial load.
14
+
15
+ ## Hook Rules
16
+
17
+ - Hooks execute on the backend during AdminForth request handling.
18
+ - Keep hooks fast because every awaited operation delays the current request.
19
+ - Return `{ ok: true }` to continue or `{ ok: false, error: 'message' }` to stop the flow.
20
+ - A hook can be a single async function or an array of async functions.
21
+
22
+ ## Available Hooks
23
+
24
+ - `show.beforeDatasourceRequest`
25
+ - `show.afterDatasourceResponse`
26
+ - `list.beforeDatasourceRequest`
27
+ - `list.afterDatasourceResponse`
28
+ - `create.beforeSave`
29
+ - `create.afterSave`
30
+ - `edit.beforeSave`
31
+ - `edit.afterSave`
32
+ - `delete.beforeSave`
33
+ - `delete.afterSave`
34
+
35
+ ## Stage Selection
36
+
37
+ - Use `beforeDatasourceRequest` for read-time scoping, filter changes, or blocking access before the datasource runs.
38
+ - Use `afterDatasourceResponse` for reshaping or enriching records after the datasource returns them.
39
+ - Use `beforeSave` for checks or data mutations that must happen before create, edit, or delete reaches the database.
40
+ - Use `afterSave` only for post-write side effects such as logging, notifications, or sync work. Errors there do not roll back data that already reached the database.
41
+
42
+ ## Show and Edit Behavior
43
+
44
+ - `show` hooks are also used when AdminForth loads initial data for the edit page.
45
+ - Inside `show` hooks, inspect `query.source` on the hook params, not `extra.query`.
46
+ - `query.source === 'show'` means the real show page.
47
+ - `query.source === 'edit'` means the edit form initial-load flow.
48
+
49
+ ## Example
50
+
51
+ ```ts
52
+ hooks: {
53
+ show: {
54
+ afterDatasourceResponse: async ({ query, response }) => {
55
+ if (query.source === 'edit') {
56
+ // runs only when AdminForth loads initial values for the edit page
57
+ } else if (query.source === 'show') {
58
+ // runs only on the real show page
59
+ }
60
+
61
+ return { ok: true };
62
+ },
63
+ },
64
+ }
65
+ ```
66
+
67
+ ## Choosing Hooks vs Permissions
68
+
69
+ - Prefer `adminforth-permissions` for straightforward role-based access control.
70
+ - Use hooks when the rule depends on specific record values, incoming filters, mutations, or request lifecycle timing.
@@ -0,0 +1,59 @@
1
+ ---
2
+ name: adminforth-permissions
3
+ description: "Use when implementing or changing AdminForth access control: allowedActions, showIn callbacks, menu visibility, per-record restrictions, and permission-aware resource behavior."
4
+ user-invocable: true
5
+ ---
6
+
7
+ # AdminForth Permissions Workflow
8
+
9
+ ## When to Use
10
+
11
+ - Editing `options.allowedActions`, column `showIn.*` callbacks, menu visibility, or other permission-sensitive resource behavior.
12
+ - Deciding whether a rule belongs in `allowedActions`, `showIn[x]`, or a hook that blocks access for specific records.
13
+ - Explaining why hiding a menu item is not the same as real authorization.
14
+
15
+ ## Permission Model
16
+
17
+ - `allowedActions` is the real per-resource permission layer for `list`, `show`, `create`, `edit`, `delete`, and `filter`.
18
+ - `showIn[x]` is the real per-column and per-page-flow control for whether a field is accessible in a given UI flow.
19
+ - Menu visibility is UX only. Hiding a resource from the menu does not block direct URL access or API access.
20
+ - For per-record restrictions, use hooks that run before datasource access or before save and return `{ ok: false, error: 'message' }`.
21
+
22
+ ## Allowed Actions Rules
23
+
24
+ - `allowedActions` can be static booleans or async functions.
25
+ - The callback receives `adminUser`, `resource`, `meta`, `source`, and `adminforth`.
26
+ - `source` tells you why the permission is being checked. Available values are `displayButtons`, `listRequest`, `showRequest`, `editLoadRequest`, `editRequest`, `createRequest`, `deleteRequest`, `bulkActionRequest`, and `customActionRequest`.
27
+ - `allowedActions` is evaluated for both UI rendering and real requests, so keep it fast and avoid unnecessary database work.
28
+ - Use `source === ActionCheckSource.DisplayButtons` only when you intentionally want different UI visibility from actual request authorization.
29
+
30
+ ## Example
31
+
32
+ ```ts
33
+ import { ActionCheckSource } from 'adminforth';
34
+
35
+ {
36
+ resourceId: 'orders',
37
+ options: {
38
+ allowedActions: {
39
+ list: ({ adminUser }) => adminUser.dbUser.role !== 'suspended',
40
+ delete: ({ adminUser, source }) => {
41
+ // `source` tells you why AdminForth is checking permission.
42
+ // This keeps the delete button visible, but blocks the real delete
43
+ // unless the request is made by a superadmin.
44
+ if (source === ActionCheckSource.DisplayButtons) {
45
+ return true;
46
+ }
47
+
48
+ return adminUser.dbUser.role === 'superadmin';
49
+ },
50
+ },
51
+ },
52
+ }
53
+ ```
54
+
55
+ ## Choosing the Right Layer
56
+
57
+ - Use `allowedActions` for coarse resource-level permissions such as role-based create, edit, delete, or list access.
58
+ - Use `showIn[x]` when a field should be visible or editable only in specific flows or for specific admins.
59
+ - Use hooks when access depends on actual record values, request filters, or lifecycle context.
@@ -0,0 +1,71 @@
1
+ # AdminForth App Guide
2
+
3
+ ## Package manager
4
+
5
+ This scaffold was generated with `{{packageManager}}`. Use `{{packageManager}}` commands in this project unless you intentionally migrate the tooling.
6
+
7
+ ## Project map
8
+
9
+ - `index.ts` is the main AdminForth configuration and server entrypoint.
10
+ - `api.ts` is for custom Express APIs and request/response schemas.
11
+ - `resources/*.ts` defines resources, columns, labels, permissions, hooks, and display behavior.
12
+ - `custom/` is for custom Vue components, pages, injections, and static assets.
13
+ {{#if prismaDbUrl}}- `schema.prisma` is the schema and migration source if you use Prisma for this app.{{/if}}
14
+ - `.env.local` stores local non-sensitive defaults. `.env` stores secrets. In production prefer real environment variables or a secret manager.
15
+
16
+ ## Docs sources for agents
17
+
18
+ - Use `https://adminforth.dev/llms.txt` for quick navigation across the AdminForth docs corpus.
19
+ - Use `https://adminforth.dev/llms-full.txt` (`llms full`) for the full single-file AdminForth reference.
20
+ - Prefer these sources before broad web search when the task is mainly about AdminForth APIs, plugins, adapters, or customization.
21
+
22
+ ## Common commands
23
+
24
+ - Install dependencies: `{{packageManager}} install`
25
+ - Start the app locally: `{{packageManagerRun}} dev`
26
+ {{#if prismaDbUrl}}- Apply local migrations: `{{packageManagerRun}} migrate:local`
27
+ - Create a new migration and apply it locally: `{{packageManagerRun}} makemigration{{packageManagerScriptArgSeparator}}--name <change-name>`{{/if}}
28
+
29
+ ## AdminForth workflow
30
+
31
+ - AdminForth connects to existing database and gives a back-office over the data (CRUD, filtering, sorting, etc).
32
+ - One table or collection maps to one resource.
33
+ - Resource config database type agnostic (same for all db types).
34
+ - Add new resources in `resources/`, register them in `index.ts`, and add menu entries if you want them visible in the sidebar.
35
+ - Resource config should mandatory list all column names which AdminFramework should be aware of, column name should match the name in the database table/collection.
36
+ - For relational schema-based dbs types of columns in resource are optional and used for overriding where possible, however AdminForth by default fetches physycal types and manual specification is not required.
37
+ - For NoSQL or schemaless dbs types of columns in resource are required for AdminForth to work.
38
+ - AdminForth does not manage database schema. By default project ships Prisma but developer can use own migration tool, and update only through `resources/*.ts` and `index.ts`.
39
+ - Always create `recordLabel` to change on UI how one record is represented across the app. Try to include the most identifying information but human-readable in the label but keep it short for readability (avoid IDs if possible).
40
+ - Use `fillOnCreate` to fill with default values on record creation on backend. Avoid showIn.create=true + fillOnCreate for the same field.
41
+ - Use `suggestOnCreate` to prefill input with a suggested value on the frontend.
42
+ - Use `enum`, `minLength`, `maxLength` in column config to enforce validation rules.
43
+
44
+ - Use AdminForth Data API for simple CRUD, filtering, sorting, and counts. For joins, aggregations, or heavier queries, use custom ORM or query builder.
45
+ - Use `api.ts` for custom business endpoints that do not fit standard resource CRUD.
46
+ - Use `custom/` for custom pages, field rendering, injections, and other Vue-side customization.
47
+
48
+ ### Access control
49
+
50
+ - Keep access control explicit and backend-enforced. Menu visibility is only UX and is not a permission boundary.
51
+ - For permission-specific implementation details, examples, and routing between `allowedActions`, `showIn[x]`, and hooks, use the `adminforth-permissions` skill.
52
+
53
+ ### Hooks
54
+
55
+ - Hooks run on the backend as part of AdminForth request flows, so keep them focused and fast.
56
+ - For hook stage selection, show-vs-edit behavior, and implementation examples, use the `adminforth-hooks` skill.
57
+
58
+ ## Engineering conventions
59
+
60
+ - Trust typed internal contracts. Do not add duplicate validation for values already guaranteed by schema, types, or backend responses.
61
+ - Validate once at the boundary, not repeatedly in downstream consumers.
62
+ - Prefer small explicit helpers and named constants over speculative fallback logic.
63
+ - Extract reusable or non-trivial regexes into named constants instead of scattering inline regexes through business logic.
64
+ - Keep changes minimal, DRY, and YAGNI.
65
+
66
+ ## Before you change the app
67
+
68
+ - If you add a new resource, register it in `index.ts` and add it to the menu if needed.
69
+ {{#if prismaDbUrl}}- If you change `schema.prisma`, create and apply a migration locally before testing the UI.{{/if}}
70
+ - If you add custom frontend code under `custom/`, install any extra frontend packages in `custom/package.json`.
71
+ - Default local login is `adminforth` / `adminforth` unless you changed the seeded admin user.
@@ -0,0 +1,5 @@
1
+ @AGENTS.md
2
+
3
+ # Claude-specific notes
4
+
5
+ When working with this project, follow the AdminForth conventions from AGENTS.md.
@@ -1,19 +1,8 @@
1
1
 
2
- {{#unless useNpm}}
3
- FROM devforth/node20-pnpm:latest
2
+ FROM {{dockerBaseImage}}
4
3
  WORKDIR /code/
5
- ADD package.json pnpm-lock.yaml pnpm-workspace.yaml /code/
6
- RUN pnpm i
4
+ ADD package.json {{dockerAdditionalManifestFiles}} /code/
5
+ RUN {{packageManager}} {{dockerPackageInstallSubcommand}}
7
6
  ADD . /code/
8
- RUN pnpm exec adminforth bundle
9
- CMD ["sh", "-c", "pnpm migrate:prod && pnpm prod"]
10
- {{/unless}}
11
- {{#if useNpm}}
12
- FROM node:{{nodeMajor}}-slim
13
- WORKDIR /code/
14
- ADD package.json package-lock.json /code/
15
- RUN npm ci
16
- ADD . /code/
17
- RUN npx adminforth bundle
18
- CMD ["sh", "-c", "npm run migrate:prod && npm run prod"]
19
- {{/if}}
7
+ RUN {{packageManagerExec}} adminforth bundle
8
+ CMD ["sh", "-c", "{{packageManagerRun}} migrate:prod && {{packageManagerRun}} prod"]
@@ -8,26 +8,14 @@
8
8
  "license": "ISC",
9
9
  "description": "",
10
10
  "scripts": {
11
- {{#unless useNpm}}
12
- "dev": "pnpm _env:dev tsx watch index.ts",
13
- "prod": "pnpm _env:prod tsx index.ts",
14
- "start": "pnpm dev",
15
- "makemigration": "pnpm _env:dev npx --yes prisma migrate dev --create-only",
16
- "migrate:local": "pnpm _env:dev npx --yes prisma migrate deploy",
17
- "migrate:prod": "pnpm _env:prod npx --yes prisma migrate deploy",
11
+ "dev": "{{packageManagerEnvDev}} tsx watch index.ts",
12
+ "prod": "{{packageManagerEnvProd}} tsx index.ts",
13
+ "start": "{{packageManagerRun}} dev",
14
+ "makemigration": "{{packageManagerEnvDev}} npx --yes prisma migrate dev --create-only",
15
+ "migrate:local": "{{packageManagerEnvDev}} npx --yes prisma migrate deploy",
16
+ "migrate:prod": "{{packageManagerEnvProd}} npx --yes prisma migrate deploy",
18
17
  "_env:dev": "dotenvx run -f .env -f .env.local --",
19
18
  "_env:prod": "dotenvx run -f .env.prod --"
20
- {{/unless}}
21
- {{#if useNpm}}
22
- "dev": "npm run _env:dev -- tsx watch index.ts",
23
- "prod": "npm run _env:prod -- tsx index.ts",
24
- "start": "npm run dev",
25
- "makemigration": "npm run _env:dev -- npx --yes prisma migrate dev --create-only",
26
- "migrate:local": "npm run _env:dev -- npx --yes prisma migrate deploy",
27
- "migrate:prod": "npm run _env:prod -- npx --yes prisma migrate deploy",
28
- "_env:dev": "dotenvx run -f .env -f .env.local --",
29
- "_env:prod": "dotenvx run -f .env.prod --"
30
- {{/if}}
31
19
  },
32
20
  "engines": {
33
21
  "node": ">=20"