node ace add @adonia/core installs the package and runs its configure hook.
One command, nine steps, all idempotent — running it twice changes nothing.
node ace add @adonia/coreAdonia installed. Summary:
Created:
config/adonia.ts
app/adonia/panels/admin_panel.ts
.adonisjs/adonia/resources.ts
.adonisjs/adonia/descriptor.d.ts
.adonisjs/adonia/manifest.json
inertia/pages/adonia/resource_index.tsx
inertia/pages/adonia/resource_form.tsx
inertia/pages/adonia/resource_detail.tsx
inertia/pages/adonia/dashboard.tsx
inertia/pages/adonia/login.tsx
inertia/pages/adonia/error.tsx
inertia/adonia.ts
app/middleware/adonia_panel_access_middleware.ts
database/migrations/<timestamp>_create_adonia_action_events_table.ts
Modified:
adonisrc.ts
package.json
inertia/app.tsx
inertia/css/app.css
start/kernel.tsWhat each piece is for
adonisrc.ts gains three entries: the provider
@adonia/core/providers/adonia_provider, the command loader
@adonia/devtools/commands (this is what gives you node ace adonia:*), and
the codegen hook indexAdoniaResources() inserted immediately after
indexEntities() in hooks.init.
// adonisrc.ts — examples/blog-admin/adonisrc.ts
import { indexAdoniaResources } from '@adonia/devtools/hooks'
export default defineConfig({
commands: [
// …
() => import('@adonia/devtools/commands'),
],
providers: [
// …
() => import('@adonia/core/providers/adonia_provider'),
],
hooks: {
init: [indexEntities({ /* … */ }), indexAdoniaResources(), indexPages({ framework: 'react' })],
},
})config/adonia.ts is the framework defaults, written out so you can read
them: 25 rows per page, offset pagination, redirectAfterCreate: 'edit',
authorization.fallback: 'deny' (with devFallback: 'allow' outside
production), the database search driver, and a 10 MB upload ceiling. Leave it
alone for now; chapter 6 changes one line of it.
app/adonia/panels/admin_panel.ts is your panel. The stub mounts it at
/admin behind the web guard:
import { Panel } from '@adonia/core'
// The GENERATED registry — `.adonisjs/adonia/resources.ts`, never edited.
// import registry from '../../../.adonisjs/adonia/resources.js'
export default Panel.make('admin')
.path('/admin')
.guard('web')
.login({ model: () => import('#models/user') })
.brand({ name: 'Blog Admin' })
.resources(registry)examples/blog-admin/app/adonia/panels/admin_panel.ts
Two calls the stub does not make, both worth adding now. login({ model })
names the model behind the guard’s user provider — an AdonisJS guard signs a
user in but cannot verify a password, so the packaged login page needs the
model carrying withAuthFinder’s verifyCredentials. brand() is the name and
accent colour the chrome renders; chapter 8 comes back to it.
.adonisjs/adonia/ holds three generated artifacts — never edit them:
| File | Contents |
|---|---|
resources.ts |
The registry the panel imports: slug → () => import(module) |
descriptor.d.ts |
Ambient AdoniaGen.<Resource>.Row / .FormState types |
manifest.json |
The §14 manifest: resources, their columns and fields, panels |
They are regenerated by the init hook whenever the dev server boots and
whenever a file under app/adonia/resources or app/adonia/panels changes
under node ace serve --hmr.
inertia/pages/adonia/*.tsx are six thin re-exports — the packaged pages,
imported into your Vite build so they are scanned, bundled and hot-reloaded
like your own pages:
// inertia/pages/adonia/resource_index.tsx
export { ResourceIndexPage as default } from '@adonia/ui/pages'To take one over, node ace adonia:eject resource_index replaces the file with
the full source. inertia/adonia.ts is the client entry (adonia({}))
where custom components and theme overrides are registered, imported for side
effects from inertia/app.tsx.
inertia/css/app.css gains one mandatory line. Tailwind v4 only scans files
it can see, and the packaged components live in node_modules:
@source '../../node_modules/@adonia/ui';Without it the panel renders unstyled. If your app had no Tailwind, the
installer also adds tailwindcss + @tailwindcss/vite to package.json and
registers the Vite plugin — run your package manager afterwards.
start/kernel.ts gains the panel gate as the first named middleware:
export const middleware = router.named({
'adonia.panel-access': () => import('#middleware/adonia_panel_access_middleware'),
// …
})Every panel route except the login page goes through it, so an unauthenticated GET is redirected to the panel’s own login route rather than the app’s.
Boot it
node ace migration:run
node ace serve --hmrVisit http://localhost:3333/admin. You are redirected to /admin/login, sign
in with a seeded user, and land on an empty dashboard: the panel works, it just
has no resources yet.
Run node ace adonia:doctor if anything looks wrong — it checks the init hook,
the generated files, cookie/domain settings, Vite host config and the security
defaults, and tells you which one is off.
Next: the Post resource.