@plutocms/supabase 0.3.0 → 0.4.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,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.4.0](https://github.com/plutocms/supabase/compare/v0.3.0...v0.4.0) (2026-09-11)
4
+
5
+
6
+ ### Features
7
+
8
+ * **migrations:** harden SQL splitter and add a versioned ledger shape ([#45](https://github.com/plutocms/supabase/issues/45)) ([f6e54a6](https://github.com/plutocms/supabase/commit/f6e54a6d87ae12c197e467c6d271793ce0ae4b7f))
9
+ * **migrations:** versioned per-file migration engine ([#46](https://github.com/plutocms/supabase/issues/46)) ([2e39d12](https://github.com/plutocms/supabase/commit/2e39d1255ada616ae55894bc2d363eb710d1a8dd))
10
+
11
+
12
+ ### Bug Fixes
13
+
14
+ * close setup-wizard RCE, signup privilege escalation, and unguarded admin routes ([#43](https://github.com/plutocms/supabase/issues/43)) ([81c3c7e](https://github.com/plutocms/supabase/commit/81c3c7eb7c2dfa80b426d91d0330176298316be4))
15
+ * **deps:** move @nuxt/eslint to dependencies ([#42](https://github.com/plutocms/supabase/issues/42)) ([11e5b1e](https://github.com/plutocms/supabase/commit/11e5b1eac80436af85a70252d25e22bda8bc3be2))
16
+ * **navbar:** hide admin navbar on public auth pages ([f9b2a9e](https://github.com/plutocms/supabase/commit/f9b2a9e8db7c83f89c842565e359f794fb700143))
17
+ * **types:** drop media.product_id from the committed type snapshot ([#44](https://github.com/plutocms/supabase/issues/44)) ([6b21506](https://github.com/plutocms/supabase/commit/6b215064c4f8538af097b71b4b5f1567de1d585e))
18
+
3
19
  ## [0.3.0](https://github.com/plutocms/supabase/compare/v0.2.2...v0.3.0) (2026-09-10)
4
20
 
5
21
 
package/FEATURES.md CHANGED
@@ -6,5 +6,5 @@ with full detail, so this file stays short.
6
6
  See the workspace-level `CLAUDE.md` (one directory up) for the delegation, writing, and git
7
7
  policies shared by every project here.
8
8
 
9
- - Layer migrations: apply pending layer database migrations from the admin UI, after initial
10
- setup. @.claude/skills/layer-migrations/SKILL.md
9
+ - Layer migrations: apply pending, versioned, per-file database migrations from the admin UI,
10
+ after initial setup. @.claude/skills/layer-migrations/SKILL.md
@@ -3,6 +3,10 @@ const route = useRoute()
3
3
  const session = useSupabaseSession()
4
4
 
5
5
  const shouldShowNavbar = computed(() => {
6
+ if (allowedUnauthenticatedPaths.includes(route.path)) {
7
+ return false
8
+ }
9
+
6
10
  return route.path === '/admin'
7
11
  || route.path.startsWith('/admin/')
8
12
  || Boolean(session.value)
@@ -16,6 +16,17 @@ interface PlutoUserMetadata {
16
16
  [key: string]: unknown
17
17
  }
18
18
 
19
+ // Admin paths that a signed-out visitor may load. Keep this list in sync
20
+ // with every public `/admin/*` page.
21
+ export const allowedUnauthenticatedPaths: RouteLocationRaw[] = [
22
+ '/admin/login',
23
+ '/admin/signup',
24
+ '/admin/confirm',
25
+ '/admin/setup',
26
+ '/admin/forgot-password',
27
+ '/admin/update-password',
28
+ ]
29
+
19
30
  export async function useAuth(authOptions?: PlutoSupabaseAuthOptions) {
20
31
  const toast = useToast()
21
32
 
@@ -50,15 +61,6 @@ export async function useAuth(authOptions?: PlutoSupabaseAuthOptions) {
50
61
  const isLoggedIn = computed<boolean>(() => !!supabaseSession.value)
51
62
  const isSubmitting = ref<boolean>(false)
52
63
 
53
- const allowedUnauthenticatedPaths: RouteLocationRaw[] = [
54
- '/admin/login',
55
- '/admin/signup',
56
- '/admin/confirm',
57
- '/admin/setup',
58
- '/admin/forgot-password',
59
- '/admin/update-password',
60
- ]
61
-
62
64
  interface LoginForm {
63
65
  email: string
64
66
  password: string
@@ -1,13 +1,23 @@
1
- export interface LayerMigrationResult {
1
+ export interface MigrationFileResult {
2
2
  layerName: string
3
+ migrationName: string
3
4
  status: 'applied' | 'skipped' | 'failed'
4
5
  error?: string
5
6
  }
6
7
 
8
+ export interface LayerStatus {
9
+ layerName: string
10
+ applied: string[]
11
+ pending: string[]
12
+ }
13
+
7
14
  interface MigrationsStatusResponse {
8
15
  success: boolean
16
+ layers: LayerStatus[]
9
17
  pending: string[]
10
18
  applied: string[]
19
+ discovered: string[]
20
+ pendingFileCount: number
11
21
  hasConnection: boolean
12
22
  needsConnectionString: boolean
13
23
  }
@@ -15,13 +25,14 @@ interface MigrationsStatusResponse {
15
25
  interface RunMigrationsResponse {
16
26
  success: boolean
17
27
  needsConnectionString?: boolean
18
- results?: LayerMigrationResult[]
28
+ results?: MigrationFileResult[]
19
29
  persisted?: boolean
20
30
  message?: string
31
+ error?: string
21
32
  }
22
33
 
23
34
  /**
24
- * Reads and applies pending layer migrations, for admin-only pages and the
35
+ * Reads and applies pending migrations, for admin-only pages and the
25
36
  * admin-shell banner.
26
37
  *
27
38
  * `useFetch` keys the request as `pluto-migrations-status`, so every
@@ -51,6 +62,8 @@ export function useMigrations() {
51
62
  return data.value ?? null
52
63
  })
53
64
 
65
+ // Layer-level count — a layer with any pending file counts once here.
66
+ // See `status.pendingFileCount` for the per-file count.
54
67
  const pendingCount = computed(() => status.value?.pending.length ?? 0)
55
68
 
56
69
  async function runMigrations(connectionString?: string) {
@@ -15,6 +15,15 @@ const { status, fetchStatus, pendingCount, refresh, runMigrations } =
15
15
 
16
16
  const isDev = import.meta.dev
17
17
 
18
+ // Only the layers that actually have a pending, or applied, file — used to
19
+ // render per-file detail under each layer below.
20
+ const pendingLayers = computed(
21
+ () => status.value?.layers.filter((layer) => layer.pending.length > 0) ?? []
22
+ )
23
+ const appliedLayers = computed(
24
+ () => status.value?.layers.filter((layer) => layer.applied.length > 0) ?? []
25
+ )
26
+
18
27
  const connectionForm = ref({
19
28
  connectionString: '',
20
29
  password: '',
@@ -175,9 +184,14 @@ async function applyMigrations(useForm: boolean) {
175
184
  <h2 class="font-semibold">
176
185
  Pending layers ({{ status.pending.length }})
177
186
  </h2>
178
- <ul class="list-inside list-disc text-sm">
179
- <li v-for="layer in status.pending" :key="layer">
180
- {{ layer }}
187
+ <ul class="flex flex-col gap-y-2 text-sm">
188
+ <li v-for="layer in pendingLayers" :key="layer.layerName">
189
+ <span class="font-mono">{{ layer.layerName }}</span>
190
+ <ul class="list-inside list-disc pl-4">
191
+ <li v-for="fileName in layer.pending" :key="fileName">
192
+ {{ fileName }}
193
+ </li>
194
+ </ul>
181
195
  </li>
182
196
  </ul>
183
197
  </div>
@@ -186,9 +200,14 @@ async function applyMigrations(useForm: boolean) {
186
200
  <h2 class="font-semibold">
187
201
  Applied layers ({{ status.applied.length }})
188
202
  </h2>
189
- <ul class="list-inside list-disc text-sm">
190
- <li v-for="layer in status.applied" :key="layer">
191
- {{ layer }}
203
+ <ul class="flex flex-col gap-y-2 text-sm">
204
+ <li v-for="layer in appliedLayers" :key="layer.layerName">
205
+ <span class="font-mono">{{ layer.layerName }}</span>
206
+ <ul class="list-inside list-disc pl-4">
207
+ <li v-for="fileName in layer.applied" :key="fileName">
208
+ {{ fileName }}
209
+ </li>
210
+ </ul>
192
211
  </li>
193
212
  </ul>
194
213
  </div>
@@ -305,8 +324,13 @@ async function applyMigrations(useForm: boolean) {
305
324
  v-if="lastRun.results?.length"
306
325
  class="flex flex-col gap-y-1 text-sm"
307
326
  >
308
- <li v-for="result in lastRun.results" :key="result.layerName">
309
- <span class="font-mono">{{ result.layerName }}</span>
327
+ <li
328
+ v-for="result in lastRun.results"
329
+ :key="`${result.layerName}/${result.migrationName}`"
330
+ >
331
+ <span class="font-mono"
332
+ >{{ result.layerName }}/{{ result.migrationName }}</span
333
+ >
310
334
  — {{ result.status }}
311
335
  <span v-if="result.error" class="text-error">
312
336
  : {{ result.error }}</span
@@ -120,7 +120,6 @@ async function completeDatabaseSetup() {
120
120
  const data = await $fetch<any>('/api/setup/create', {
121
121
  method: 'POST',
122
122
  body: {
123
- baseUrl: window.location.origin,
124
123
  connectionString,
125
124
  },
126
125
  })
@@ -323,11 +322,11 @@ function handleStepChange(step: number) {
323
322
  Supabase database and run the SQL queries from
324
323
  this
325
324
  <ULink
326
- to="https://github.com/plutocms/supabase/blob/feat/setup-wizard/public/schema.sql"
325
+ to="https://github.com/plutocms/supabase/tree/main/db/migrations"
327
326
  target="_blank"
328
327
  class="underline"
329
328
  >
330
- SQL file</ULink
329
+ SQL files</ULink
331
330
  >.
332
331
  </p>
333
332
 
@@ -0,0 +1,107 @@
1
+ -- Admin hardening
2
+ --
3
+ -- Gates the writes that were previously open to any authenticated user
4
+ -- behind a real admin check, and closes a client-controlled admin
5
+ -- self-grant on signup. Standalone and self-sufficient: it does not
6
+ -- depend on running as a diff against 001_baseline.sql, only on that
7
+ -- file's tables (public.profiles, public.settings, public.healthcheck)
8
+ -- already existing.
9
+
10
+ -- Returns true when the calling user's own profile has is_admin = true.
11
+ -- SECURITY DEFINER so this lookup runs with the function owner's
12
+ -- privileges rather than the caller's — RLS policies call this to decide
13
+ -- whether to grant a *wider* read/write than a user's own row would
14
+ -- otherwise get, so the check itself must not be subject to those same
15
+ -- policies.
16
+ create or replace function public.is_admin()
17
+ returns boolean
18
+ language sql
19
+ security definer
20
+ set search_path = ''
21
+ stable
22
+ as $$
23
+ select coalesce(
24
+ (select is_admin from public.profiles where id = auth.uid()),
25
+ false
26
+ );
27
+ $$;
28
+
29
+ -- A user can always read their own profile; only admins can read
30
+ -- everyone's. Previously any authenticated user could read every row
31
+ -- (including every user's email) — this was the actual reason `is_admin`
32
+ -- existed on the table but no policy referenced it.
33
+ drop policy if exists "Enable read access for authenticated users" on public.profiles;
34
+
35
+ create policy "Enable read access for authenticated users"
36
+ on public.profiles
37
+ for select
38
+ to authenticated, dashboard_user
39
+ using (auth.uid() = id or public.is_admin());
40
+
41
+ -- Previously any authenticated user (not just an admin) could insert or
42
+ -- update settings — see server/api/settings/update.post.ts, which also
43
+ -- now requires an admin session.
44
+ drop policy if exists "Enable insert for authenticated users only" on public.settings;
45
+
46
+ create policy "Enable insert for authenticated users only"
47
+ on public.settings
48
+ for insert
49
+ to authenticated, dashboard_user
50
+ with check (public.is_admin());
51
+
52
+ drop policy if exists "Enable update for authenticated users on settings" on public.settings;
53
+
54
+ create policy "Enable update for authenticated users on settings"
55
+ on public.settings
56
+ for update
57
+ to authenticated, dashboard_user
58
+ using (public.is_admin())
59
+ with check (public.is_admin());
60
+
61
+ -- Previously any authenticated user (not just an admin) could flip
62
+ -- first_setup back to 'true', which would re-open the unauthenticated
63
+ -- setup wizard for every visitor.
64
+ drop policy if exists "Enable update for authenticated users on healthcheck" on public.healthcheck;
65
+
66
+ create policy "Enable update for authenticated users on healthcheck"
67
+ on public.healthcheck
68
+ for update
69
+ to authenticated, dashboard_user
70
+ using (public.is_admin())
71
+ with check (public.is_admin());
72
+
73
+ --- inserts a row into public.profiles
74
+ --- automatically grants admin to the first user if no profiles exist yet
75
+ create or replace function public.handle_new_user()
76
+ returns trigger
77
+ language plpgsql
78
+ security definer set search_path = ''
79
+ as $$
80
+ declare
81
+ profile_count int;
82
+ should_be_admin boolean;
83
+ begin
84
+ select count(*) into profile_count from public.profiles;
85
+
86
+ -- Only the very first account is auto-promoted. Never trust
87
+ -- raw_user_meta_data for this: it is fully client-controlled — anyone can
88
+ -- pass an arbitrary `options.data` straight to Supabase's own
89
+ -- /auth/v1/signup endpoint, bypassing this project's own /api/auth/signup
90
+ -- route entirely — so honoring an `is_admin` key there let any new
91
+ -- signup grant itself admin.
92
+ should_be_admin := profile_count = 0;
93
+
94
+ insert into public.profiles (id, email, username, display_name, is_admin)
95
+ values (
96
+ new.id,
97
+ new.email,
98
+ new.raw_user_meta_data ->> 'username',
99
+ new.raw_user_meta_data ->> 'display_name',
100
+ should_be_admin
101
+ );
102
+ return new;
103
+ end;
104
+ $$;
105
+ -- Not re-created here: on_auth_user_created (from 001_baseline.sql)
106
+ -- already points at public.handle_new_user() by name, and
107
+ -- `create or replace function` above is enough to update its behavior.
@@ -1,11 +1,87 @@
1
+ import type { PlutoMigrationFile } from '../shared/types/migrations'
2
+ import { createHash } from 'node:crypto'
1
3
  import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'
2
- import { dirname, join, relative } from 'node:path'
4
+ import { basename, dirname, join, relative } from 'node:path'
3
5
  import { createResolver, defineNuxtModule, getLayerDirectories } from 'nuxt/kit'
4
6
 
7
+ const schemaPattern = /^schema\.(.+)\.sql$/
8
+ const noTransactionPattern = /^--\s*pluto:no-transaction\b/
9
+
10
+ /**
11
+ * Plain, deliberate string comparator for migration file names — ASCII
12
+ * lexicographic order, so `001_...` sorts before `002_...` and so on.
13
+ * Written out instead of relying on `Array.prototype.sort()`'s default
14
+ * (which happens to do the same thing for ASCII names, but isn't a
15
+ * documented guarantee to depend on).
16
+ */
17
+ function compareFileNames(a: string, b: string): number {
18
+ if (a < b) {
19
+ return -1
20
+ }
21
+ if (a > b) {
22
+ return 1
23
+ }
24
+ return 0
25
+ }
26
+
27
+ /**
28
+ * Derives a layer's key from its `package.json` `name` field, stripping
29
+ * any `@scope/` prefix. `@plutocms/supabase` always maps to the literal
30
+ * key `core` — every deployed site's ledger already has rows with
31
+ * `layer_name = 'core'`. Falls back to the layer directory's own name
32
+ * when `package.json` is missing or unreadable.
33
+ */
34
+ function deriveLayerKey(layerRoot: string): string {
35
+ try {
36
+ const pkgRaw = readFileSync(join(layerRoot, 'package.json'), 'utf-8')
37
+ const pkg = JSON.parse(pkgRaw) as { name?: string }
38
+
39
+ if (typeof pkg.name === 'string' && pkg.name.length > 0) {
40
+ if (pkg.name === '@plutocms/supabase') {
41
+ return 'core'
42
+ }
43
+ return pkg.name.replace(/^@[^/]+\//, '')
44
+ }
45
+ } catch {
46
+ // package.json missing or unreadable — fall back to the directory name.
47
+ }
48
+
49
+ return basename(layerRoot)
50
+ }
51
+
52
+ function computeChecksum(content: string): string {
53
+ return createHash('sha256').update(content, 'utf-8').digest('hex').slice(0, 16)
54
+ }
55
+
5
56
  /**
6
- * Discovers schema.[layerName].sql files across all Nuxt layers,
7
- * reads their content at build time, and populates runtimeConfig
8
- * so the migrations plugin can run them at server startup.
57
+ * True when the file's first non-blank line is a `-- pluto:no-transaction`
58
+ * directive.
59
+ */
60
+ function detectNoTransaction(content: string): boolean {
61
+ const firstNonBlankLine = content
62
+ .split('\n')
63
+ .map((line) => line.trim())
64
+ .find((line) => line.length > 0)
65
+
66
+ return firstNonBlankLine ? noTransactionPattern.test(firstNonBlankLine) : false
67
+ }
68
+
69
+ function readMigrationFile(dir: string, fileName: string, name: string): PlutoMigrationFile {
70
+ const content = readFileSync(join(dir, fileName), 'utf-8')
71
+
72
+ return {
73
+ name,
74
+ sql: content,
75
+ checksum: computeChecksum(content),
76
+ noTransaction: detectNoTransaction(content),
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Discovers `db/migrations/*.sql` (or, for a layer not yet converted, the
82
+ * legacy `public/schema.sql` / `public/schema.[name].sql`) across all Nuxt
83
+ * layers, reads their content at build time, and populates runtimeConfig
84
+ * so the migrations engine can apply them at server startup.
9
85
  *
10
86
  * Also seeds the consuming app's shared/types/supabase.ts with a
11
87
  * re-export of this layer's own committed snapshot when the consumer
@@ -59,62 +135,121 @@ export default defineNuxtModule({
59
135
  }
60
136
 
61
137
  const layerDirs = getLayerDirectories()
62
- const layerSchemas: Record<string, string> = {}
63
- const schemaPattern = /^schema\.(.+)\.sql$/
138
+ const layerMigrations: Record<string, PlutoMigrationFile[]> = {}
139
+
140
+ // Keeps the first-discovered layer for a given key (getLayerDirectories
141
+ // orders the user/project layer first), and warns once when a later
142
+ // layer's key collides with one already discovered.
143
+ function assignLayer(layerKey: string, files: PlutoMigrationFile[]): void {
144
+ if (layerMigrations[layerKey]) {
145
+ console.warn(
146
+ `[pluto-migrations] Layer key "${layerKey}" was already discovered from another layer. Keeping the first one, ignoring this one.`
147
+ )
148
+ return
149
+ }
150
+ layerMigrations[layerKey] = files
151
+ }
64
152
 
65
153
  for (const layer of layerDirs) {
66
- // Each layer may have a public/ directory with schema files
154
+ const migrationsDir = join(layer.root, 'db/migrations')
155
+
156
+ if (existsSync(migrationsDir)) {
157
+ let fileNames: string[] = []
158
+ try {
159
+ fileNames = readdirSync(migrationsDir).filter((file) =>
160
+ file.endsWith('.sql')
161
+ )
162
+ } catch {
163
+ console.error(
164
+ `[pluto-migrations] Failed to read migrations directory: ${migrationsDir}`
165
+ )
166
+ continue
167
+ }
168
+
169
+ fileNames.sort(compareFileNames)
170
+
171
+ const files: PlutoMigrationFile[] = []
172
+ for (const fileName of fileNames) {
173
+ try {
174
+ files.push(readMigrationFile(migrationsDir, fileName, fileName))
175
+ } catch {
176
+ console.error(
177
+ `[pluto-migrations] Failed to read migration file: ${fileName}`
178
+ )
179
+ }
180
+ }
181
+
182
+ assignLayer(deriveLayerKey(layer.root), files)
183
+ continue
184
+ }
185
+
186
+ // Legacy fallback for a layer not yet converted to db/migrations/,
187
+ // exactly as before: a single public/schema.sql (always key `core`)
188
+ // and/or public/schema.[name].sql (key comes from the file name, not
189
+ // package.json — this keeps working for an unconverted layer without
190
+ // requiring it to match its package name).
67
191
  const publicDir = join(layer.root, 'public')
68
192
 
69
193
  if (!existsSync(publicDir)) {
70
194
  continue
71
195
  }
72
196
 
73
- let files: string[] = []
197
+ let publicFiles: string[] = []
74
198
  try {
75
- files = readdirSync(publicDir, 'utf-8')
199
+ publicFiles = readdirSync(publicDir)
76
200
  } catch {
77
201
  continue
78
202
  }
79
203
 
80
- for (const file of files) {
81
- // Skip the core schema
82
- if (file === 'schema.sql') {
204
+ for (const fileName of publicFiles) {
205
+ if (fileName === 'schema.sql') {
206
+ try {
207
+ assignLayer('core', [
208
+ readMigrationFile(publicDir, fileName, '001_baseline.sql'),
209
+ ])
210
+ } catch {
211
+ console.error(
212
+ `[pluto-migrations] Failed to read schema file: ${fileName}`
213
+ )
214
+ }
83
215
  continue
84
216
  }
85
217
 
86
- const match = file.match(schemaPattern)
218
+ const match = fileName.match(schemaPattern)
87
219
  if (match?.[1]) {
88
- const layerName = match[1]
89
- // Read SQL content at build time
90
220
  try {
91
- layerSchemas[layerName] = readFileSync(
92
- join(publicDir, file),
93
- 'utf-8'
94
- )
221
+ assignLayer(match[1], [
222
+ readMigrationFile(publicDir, fileName, '001_baseline.sql'),
223
+ ])
95
224
  } catch {
96
225
  console.error(
97
- `[pluto-migrations] Failed to read schema file: ${file}`
226
+ `[pluto-migrations] Failed to read schema file: ${fileName}`
98
227
  )
99
228
  }
100
229
  }
101
230
  }
102
231
  }
103
232
 
104
- const layerNames = Object.keys(layerSchemas)
105
-
106
- // Populate runtimeConfig with layer names and their SQL content.
233
+ // Populate runtimeConfig with every layer's ordered migration files.
107
234
  // Nuxt's schema inference narrows this to a literal shape based on
108
- // whatever layer keys were last observed in this project (see the
109
- // `RuntimeConfig` augmentation in shared/types/runtime-config.d.ts,
110
- // which isn't visible from this module-context tsconfig), so the
111
- // assignment is cast to the intended general shape.
112
- ;(nuxt.options.runtimeConfig as { plutoLayerSchemas: Record<string, string> }).plutoLayerSchemas =
113
- layerSchemas
114
-
115
- if (layerNames.length > 0) {
235
+ // whatever layer keys and file shapes happen to be discovered in a
236
+ // given project (see the `RuntimeConfig` augmentation in
237
+ // shared/types/runtime-config.d.ts, which isn't visible from this
238
+ // module-context tsconfig), so the assignment is cast to the intended
239
+ // general shape. That inferred shape's array elements don't carry
240
+ // enough structure for a direct cast, hence the `unknown` step.
241
+ ;(
242
+ nuxt.options.runtimeConfig as unknown as {
243
+ plutoLayerMigrations: Record<string, PlutoMigrationFile[]>
244
+ }
245
+ ).plutoLayerMigrations = layerMigrations
246
+
247
+ const layerKeys = Object.keys(layerMigrations)
248
+ if (layerKeys.length > 0) {
116
249
  console.warn(
117
- `[pluto-migrations] Discovered layer schemas: ${layerNames.join(', ')}`
250
+ `[pluto-migrations] Discovered layer migrations: ${layerKeys
251
+ .map((key) => `${key} (${layerMigrations[key]!.length} file(s))`)
252
+ .join(', ')}`
118
253
  )
119
254
  }
120
255
  },
package/nuxt.config.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import type { PlutoMigrationFile } from './shared/types/migrations'
2
+
1
3
  // Point these at a local checkout (e.g. `../pluto`, `../utils`) to test
2
4
  // unpublished changes; unset, they resolve to the published npm packages.
3
5
  const plutoLayer = process.env.PLUTO_PLUTO_PATH || '@plutocms/pluto'
@@ -18,7 +20,7 @@ export default defineNuxtConfig({
18
20
  supabaseUrl: process.env.SUPABASE_URL,
19
21
  supabaseKey: process.env.SUPABASE_KEY,
20
22
  plutoRootDir: '',
21
- plutoLayerSchemas: {} as Record<string, string>,
23
+ plutoLayerMigrations: {} as Record<string, PlutoMigrationFile[]>,
22
24
  },
23
25
 
24
26
  alias: {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@plutocms/supabase",
3
3
  "type": "module",
4
- "version": "0.3.0",
4
+ "version": "0.4.0",
5
5
  "trustedDependencies": [
6
6
  "@parcel/watcher",
7
7
  "@plutocms/pluto",
@@ -36,6 +36,7 @@
36
36
  "supabase-types": "bun ./scripts/supabase-typegen.ts"
37
37
  },
38
38
  "dependencies": {
39
+ "@nuxt/eslint": "^1.16.0",
39
40
  "@nuxt/ui": "^4.9.0",
40
41
  "@nuxtjs/supabase": "^2.0.9",
41
42
  "@plutocms/pluto": "^0.3.2",
@@ -46,7 +47,6 @@
46
47
  },
47
48
  "devDependencies": {
48
49
  "@antfu/eslint-config": "^6.2.0",
49
- "@nuxt/eslint": "^1.16.0",
50
50
  "@types/bun": "^1.3.14",
51
51
  "@vueuse/nuxt": "^14.3.0",
52
52
  "eslint": "^10.6.0",