@plutocms/core 0.44.1 → 0.45.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/CHANGELOG.md CHANGED
@@ -1,6 +1,13 @@
1
1
  # Changelog
2
2
 
3
3
 
4
+ ## [0.45.0](https://github.com/plutocms/pluto/compare/v0.44.1...v0.45.0) (2026-11-01)
5
+
6
+
7
+ ### Features
8
+
9
+ * in production, the admin shows a not-configured page when no auth driver is registered ([#346](https://github.com/plutocms/pluto/issues/346)) ([6f49cb8](https://github.com/plutocms/pluto/commit/6f49cb814d1d3614d8b3b7b92722081ba82549ae))
10
+
4
11
  ## [0.44.1](https://github.com/plutocms/pluto/compare/v0.44.0...v0.44.1) (2026-11-01)
5
12
 
6
13
 
package/FEATURES.md CHANGED
@@ -101,7 +101,7 @@ policies shared by every project here.
101
101
  `defineAsyncComponent`, shows a skeleton per field, widget and settings panel while a chunk
102
102
  loads, and warns in dev about a registered component that is not async.
103
103
  @.claude/skills/extension-registry/SKILL.md
104
- - Admin shell: core owns the `/admin` parent route, the `/admin/login` page, the route guard, the admin banners, the navbar and the user menu. A backend layer registers an auth driver, account menu items, site info and link actions. The admin is for staff only: a signed-in user with no capability goes to `/admin/no-access`. The bar shows on `/admin/**` for staff, and on public pages for staff only. A route hides the bar with the page meta `plutoAdminBar: false`, and `isPlutoPreviewPath` marks the preview path. The bar height is the token `--pluto-admin-bar-height`, and `<html>` gets `data-pluto-admin-bar` while the bar shows. A hint cookie puts the bar frame in the server HTML of a public page for staff (the actions and the user menu load after the mount). Content types with `adminBar` get "New <type>" and "Edit <type>" buttons (`usePlutoEditLink`). With two or more types, one "New" menu lists them, grouped by `navGroup`. A `navGroup` also puts related types under one sidebar item (registry bucket `navGroups`, core group `pages`). @.claude/skills/admin-shell/SKILL.md
104
+ - Admin shell: core owns the `/admin` parent route, the `/admin/login` page, the route guard, the admin banners, the navbar and the user menu. A backend layer registers an auth driver, account menu items, site info and link actions. The admin is for staff only: a signed-in user with no capability goes to `/admin/no-access`. In production, an app with no auth driver (the backend is missing or did not load) sends every admin path to `/admin/not-configured`; in dev the admin opens. The bar shows on `/admin/**` for staff, and on public pages for staff only. A route hides the bar with the page meta `plutoAdminBar: false`, and `isPlutoPreviewPath` marks the preview path. The bar height is the token `--pluto-admin-bar-height`, and `<html>` gets `data-pluto-admin-bar` while the bar shows. A hint cookie puts the bar frame in the server HTML of a public page for staff (the actions and the user menu load after the mount). Content types with `adminBar` get "New <type>" and "Edit <type>" buttons (`usePlutoEditLink`). With two or more types, one "New" menu lists them, grouped by `navGroup`. A `navGroup` also puts related types under one sidebar item (registry bucket `navGroups`, core group `pages`). @.claude/skills/admin-shell/SKILL.md
105
105
  - Local layers helper: `plutoLayer(name)` from `@plutocms/core/layers` gives a Nuxt `extends` entry.
106
106
  With `PLUTO_LOCAL_ROOT`, it uses the local checkouts of the Pluto layers. @.claude/skills/local-layers/SKILL.md
107
107
  - Pluto logo: `PlutoLogo` draws the logo inline. The wordmark takes the text color, so the logo works
@@ -9,6 +9,7 @@ import type {
9
9
  export const ADMIN_LOGIN_PATH = '/admin/login'
10
10
  export const ADMIN_HOME_PATH = '/admin/home'
11
11
  export const ADMIN_NO_ACCESS_PATH = '/admin/no-access'
12
+ export const ADMIN_NOT_CONFIGURED_PATH = '/admin/not-configured'
12
13
 
13
14
  const FAKE_ORIGIN = 'http://pluto.invalid'
14
15
 
@@ -111,6 +112,40 @@ function firstString(value: unknown): unknown {
111
112
  return Array.isArray(value) ? value[0] : value
112
113
  }
113
114
 
115
+ export interface DriverInput {
116
+ path: string
117
+ /** An auth driver is registered. */
118
+ hasDriver: boolean
119
+ /** The app runs in dev (`import.meta.dev`). */
120
+ dev: boolean
121
+ }
122
+
123
+ /**
124
+ * Decides what the route guard does about the auth driver, before the sign-in
125
+ * rules. It returns `undefined` when a driver is registered and the sign-in
126
+ * rules apply. It never touches a path outside `/admin`.
127
+ *
128
+ * - No driver in production: the backend is missing or did not load (both
129
+ * backends register a driver). Every admin path goes to the not-configured
130
+ * page, so the admin pages do not show to everyone.
131
+ * - No driver in dev: allow, so admin pages can be built with no backend.
132
+ * - A driver, on the not-configured page: go to the admin home.
133
+ */
134
+ export function driverDecision(input: DriverInput): GuardDecision | undefined {
135
+ const { path, hasDriver, dev } = input
136
+ if (!isAdminPath(path)) {
137
+ return { action: 'allow' }
138
+ }
139
+ const onPage = trimSlash(path) === ADMIN_NOT_CONFIGURED_PATH
140
+ if (hasDriver) {
141
+ return onPage ? { action: 'redirect', to: ADMIN_HOME_PATH } : undefined
142
+ }
143
+ if (dev || onPage) {
144
+ return { action: 'allow' }
145
+ }
146
+ return { action: 'redirect', to: ADMIN_NOT_CONFIGURED_PATH }
147
+ }
148
+
114
149
  /**
115
150
  * Decides what the route guard does. It never touches a path outside `/admin`.
116
151
  * A signed-out user on a private admin path goes to the login page, with the
@@ -1,16 +1,16 @@
1
1
  import {
2
+ driverDecision,
2
3
  guardDecision,
3
- isAdminPath,
4
4
  matchPublicPath,
5
5
  staffDecision,
6
6
  } from '../composables/internal/admin-shell'
7
7
 
8
8
  /**
9
- * The route guard of the admin. It does nothing when no auth driver is
10
- * registered, so a backend with its own guard (supabase 0.10) is not affected.
11
- * It never touches a path outside `/admin`. A signed-out user on a private
12
- * admin path goes to the login page. A signed-in user on a public auth path
13
- * goes to the admin home. The driver's own `guard` runs first, so a backend
9
+ * The route guard of the admin. It never touches a path outside `/admin`.
10
+ * With no auth driver, `driverDecision` decides: in production every admin
11
+ * path goes to the not-configured page, in dev the admin opens. With a driver,
12
+ * a signed-out user on a private admin path goes to the login page. A
13
+ * signed-in user on a public auth path goes to the admin home. The driver's own `guard` runs first, so a backend
14
14
  * can send the user somewhere else (supabase: the first setup). Then a signed-in
15
15
  * user on a private path must be staff (at least one capability, or no
16
16
  * permissions driver). Other users go to `/admin/no-access`. The public auth
@@ -19,8 +19,15 @@ import {
19
19
  */
20
20
  export default defineNuxtRouteMiddleware(async (to) => {
21
21
  const auth = usePlutoAuth()
22
- if (!auth.hasDriver.value || !isAdminPath(to.path)) {
23
- return
22
+ const driver = driverDecision({
23
+ path: to.path,
24
+ hasDriver: auth.hasDriver.value,
25
+ dev: import.meta.dev,
26
+ })
27
+ if (driver) {
28
+ return driver.action === 'redirect'
29
+ ? navigateTo(driver.to, { replace: true })
30
+ : undefined
24
31
  }
25
32
 
26
33
  try {
@@ -0,0 +1,22 @@
1
+ <script setup lang="ts">
2
+ // The page for an admin with no auth driver in production: the backend is
3
+ // missing or did not load. The route guard sends every admin path here (see
4
+ // `driverDecision`), so the admin pages do not show to everyone. The frame is
5
+ // PlutoAdminAuthCard. See the admin-shell skill.
6
+ definePageMeta({ layout: 'auth' })
7
+
8
+ const { t } = usePlutoI18n()
9
+
10
+ useHead({ title: () => t('pluto.notConfigured.title') })
11
+ </script>
12
+
13
+ <template>
14
+ <PlutoAdminAuthCard :title="t('pluto.notConfigured.title')">
15
+ <UAlert
16
+ :description="t('pluto.notConfigured.description')"
17
+ color="warning"
18
+ icon="lucide:plug-zap"
19
+ variant="subtle"
20
+ />
21
+ </PlutoAdminAuthCard>
22
+ </template>
@@ -597,6 +597,10 @@
597
597
  "goToSite": "Go to the site",
598
598
  "signOut": "Sign out"
599
599
  },
600
+ "notConfigured": {
601
+ "title": "The admin is not set up",
602
+ "description": "This site has no sign-in for the admin. If you run this site, make sure that the server can load its content backend, and check the environment variables of the server."
603
+ },
600
604
  "post": {
601
605
  "previewTitle": "Post preview",
602
606
  "previewBadge": "Preview",
@@ -597,6 +597,10 @@
597
597
  "goToSite": "Ir para o site",
598
598
  "signOut": "Sair"
599
599
  },
600
+ "notConfigured": {
601
+ "title": "O painel não está configurado",
602
+ "description": "Este site não tem login para o painel. Se você administra este site, confira se o servidor consegue carregar o backend de conteúdo e verifique as variáveis de ambiente do servidor."
603
+ },
600
604
  "post": {
601
605
  "previewTitle": "Prévia do post",
602
606
  "previewBadge": "Prévia",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@plutocms/core",
3
3
  "type": "module",
4
- "version": "0.44.1",
4
+ "version": "0.45.0",
5
5
  "description": "The base layer every Pluto app extends: the admin, the content model, and the Git CMS",
6
6
  "author": "Victor Ribeiro",
7
7
  "license": "MIT",