@plutocms/supabase 0.0.1-alpha.8 → 0.1.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.
Files changed (103) hide show
  1. package/CHANGELOG.md +98 -0
  2. package/README.md +2 -4
  3. package/app/assets/css/tailwind.css +5 -0
  4. package/app/components/navbar/NavbarAdmin.vue +6 -5
  5. package/app/composables/auth.ts +124 -2
  6. package/app/middleware/auth.ts +13 -6
  7. package/app/middleware/setup-check.ts +25 -21
  8. package/app/pages/admin/(auth)/confirm.vue +38 -0
  9. package/app/pages/admin/(auth)/forgot-password.vue +88 -0
  10. package/app/pages/admin/(auth)/login.vue +5 -1
  11. package/app/pages/admin/(auth)/signup.vue +76 -16
  12. package/app/pages/admin/(auth)/update-password.vue +134 -0
  13. package/app/pages/admin/setup.vue +31 -28
  14. package/app/pages/admin.vue +3 -4
  15. package/modules/pluto-migrations.ts +121 -0
  16. package/nuxt.config.ts +14 -1
  17. package/package.json +40 -24
  18. package/public/schema.sql +66 -8
  19. package/scripts/supabase-typegen.ts +36 -21
  20. package/scripts/tsconfig.json +18 -0
  21. package/server/api/auth/signup.post.ts +46 -0
  22. package/server/api/setup/create.post.ts +38 -88
  23. package/server/plugins/migrations.ts +60 -0
  24. package/server/utils/migrations.ts +88 -0
  25. package/server/utils/sql.ts +81 -0
  26. package/server/utils/typegen.ts +68 -0
  27. package/shared/types/runtime-config.d.ts +16 -0
  28. package/shared/types/supabase.ts +90 -117
  29. package/.agents/skills/nuxt/GENERATION.md +0 -5
  30. package/.agents/skills/nuxt/SKILL.md +0 -55
  31. package/.agents/skills/nuxt/references/advanced-hooks.md +0 -289
  32. package/.agents/skills/nuxt/references/advanced-layers.md +0 -299
  33. package/.agents/skills/nuxt/references/advanced-module-authoring.md +0 -554
  34. package/.agents/skills/nuxt/references/best-practices-data-fetching.md +0 -357
  35. package/.agents/skills/nuxt/references/best-practices-ssr.md +0 -355
  36. package/.agents/skills/nuxt/references/core-cli.md +0 -263
  37. package/.agents/skills/nuxt/references/core-config.md +0 -162
  38. package/.agents/skills/nuxt/references/core-data-fetching.md +0 -236
  39. package/.agents/skills/nuxt/references/core-deployment.md +0 -224
  40. package/.agents/skills/nuxt/references/core-directory-structure.md +0 -269
  41. package/.agents/skills/nuxt/references/core-modules.md +0 -292
  42. package/.agents/skills/nuxt/references/core-routing.md +0 -226
  43. package/.agents/skills/nuxt/references/features-components-autoimport.md +0 -328
  44. package/.agents/skills/nuxt/references/features-components.md +0 -264
  45. package/.agents/skills/nuxt/references/features-composables.md +0 -276
  46. package/.agents/skills/nuxt/references/features-server.md +0 -265
  47. package/.agents/skills/nuxt/references/features-state.md +0 -194
  48. package/.agents/skills/nuxt/references/rendering-modes.md +0 -237
  49. package/.agents/skills/nuxt-ui/SKILL.md +0 -334
  50. package/.agents/skills/nuxt-ui/references/components.md +0 -377
  51. package/.agents/skills/nuxt-ui/references/composables.md +0 -127
  52. package/.agents/skills/nuxt-ui/references/layouts/chat.md +0 -195
  53. package/.agents/skills/nuxt-ui/references/layouts/dashboard.md +0 -220
  54. package/.agents/skills/nuxt-ui/references/layouts/docs.md +0 -141
  55. package/.agents/skills/nuxt-ui/references/layouts/editor.md +0 -167
  56. package/.agents/skills/nuxt-ui/references/layouts/page.md +0 -260
  57. package/.agents/skills/nuxt-ui/references/theming.md +0 -427
  58. package/.agents/skills/supabase-postgres-best-practices/AGENTS.md +0 -68
  59. package/.agents/skills/supabase-postgres-best-practices/CLAUDE.md +0 -1
  60. package/.agents/skills/supabase-postgres-best-practices/README.md +0 -116
  61. package/.agents/skills/supabase-postgres-best-practices/SKILL.md +0 -64
  62. package/.agents/skills/supabase-postgres-best-practices/references/advanced-full-text-search.md +0 -55
  63. package/.agents/skills/supabase-postgres-best-practices/references/advanced-jsonb-indexing.md +0 -49
  64. package/.agents/skills/supabase-postgres-best-practices/references/conn-idle-timeout.md +0 -46
  65. package/.agents/skills/supabase-postgres-best-practices/references/conn-limits.md +0 -44
  66. package/.agents/skills/supabase-postgres-best-practices/references/conn-pooling.md +0 -41
  67. package/.agents/skills/supabase-postgres-best-practices/references/conn-prepared-statements.md +0 -46
  68. package/.agents/skills/supabase-postgres-best-practices/references/data-batch-inserts.md +0 -54
  69. package/.agents/skills/supabase-postgres-best-practices/references/data-n-plus-one.md +0 -53
  70. package/.agents/skills/supabase-postgres-best-practices/references/data-pagination.md +0 -50
  71. package/.agents/skills/supabase-postgres-best-practices/references/data-upsert.md +0 -50
  72. package/.agents/skills/supabase-postgres-best-practices/references/lock-advisory.md +0 -56
  73. package/.agents/skills/supabase-postgres-best-practices/references/lock-deadlock-prevention.md +0 -68
  74. package/.agents/skills/supabase-postgres-best-practices/references/lock-short-transactions.md +0 -50
  75. package/.agents/skills/supabase-postgres-best-practices/references/lock-skip-locked.md +0 -54
  76. package/.agents/skills/supabase-postgres-best-practices/references/monitor-explain-analyze.md +0 -45
  77. package/.agents/skills/supabase-postgres-best-practices/references/monitor-pg-stat-statements.md +0 -55
  78. package/.agents/skills/supabase-postgres-best-practices/references/monitor-vacuum-analyze.md +0 -55
  79. package/.agents/skills/supabase-postgres-best-practices/references/query-composite-indexes.md +0 -44
  80. package/.agents/skills/supabase-postgres-best-practices/references/query-covering-indexes.md +0 -40
  81. package/.agents/skills/supabase-postgres-best-practices/references/query-index-types.md +0 -48
  82. package/.agents/skills/supabase-postgres-best-practices/references/query-missing-indexes.md +0 -43
  83. package/.agents/skills/supabase-postgres-best-practices/references/query-partial-indexes.md +0 -45
  84. package/.agents/skills/supabase-postgres-best-practices/references/schema-constraints.md +0 -80
  85. package/.agents/skills/supabase-postgres-best-practices/references/schema-data-types.md +0 -46
  86. package/.agents/skills/supabase-postgres-best-practices/references/schema-foreign-key-indexes.md +0 -59
  87. package/.agents/skills/supabase-postgres-best-practices/references/schema-lowercase-identifiers.md +0 -55
  88. package/.agents/skills/supabase-postgres-best-practices/references/schema-partitioning.md +0 -55
  89. package/.agents/skills/supabase-postgres-best-practices/references/schema-primary-keys.md +0 -61
  90. package/.agents/skills/supabase-postgres-best-practices/references/security-privileges.md +0 -54
  91. package/.agents/skills/supabase-postgres-best-practices/references/security-rls-basics.md +0 -50
  92. package/.agents/skills/supabase-postgres-best-practices/references/security-rls-performance.md +0 -57
  93. package/.editorconfig +0 -12
  94. package/.eslintignore +0 -1
  95. package/.prettierrc +0 -9
  96. package/.vscode/extensions.json +0 -10
  97. package/.vscode/settings.json +0 -61
  98. package/bun.lock +0 -2837
  99. package/eslint.config.mjs +0 -88
  100. package/server/api/category/list.get.ts +0 -9
  101. package/server/api/category/new.post.ts +0 -31
  102. package/server/api/signup.post.ts +0 -28
  103. package/skills-lock.json +0 -20
@@ -7,10 +7,12 @@ useHead({
7
7
  title: 'Sign Up',
8
8
  })
9
9
 
10
+ const toast = useToast()
11
+
10
12
  const form = ref({
11
13
  email: '',
12
- first_name: '',
13
- last_name: '',
14
+ username: '',
15
+ display_name: '',
14
16
  password: '',
15
17
  confirm_password: '',
16
18
  })
@@ -21,26 +23,44 @@ const passwordMatch = computed(() => {
21
23
  return form.value.password === form.value.confirm_password
22
24
  })
23
25
 
26
+ const isEmailVerificationMessageVisible = ref<boolean>(false)
27
+
24
28
  async function submitForm() {
25
29
  if (!passwordMatch.value) {
26
30
  return
27
31
  }
28
32
 
29
33
  isSubmitting.value = true
34
+
30
35
  try {
31
- await $fetch('/api/signup', {
36
+ await $fetch('/api/auth/signup', {
32
37
  method: 'POST',
33
38
  body: {
34
39
  email: form.value.email,
35
- first_name: form.value.first_name,
36
- last_name: form.value.last_name,
40
+ username: form.value.username,
41
+ display_name: form.value.display_name,
37
42
  password: form.value.password,
38
43
  },
39
44
  })
40
45
 
41
- navigateTo('/admin/login?rel=new')
46
+ toast.add({
47
+ title: 'Account created successfully',
48
+ description: 'You still need to verify your email before you can log in.',
49
+ icon: 'lucide:check',
50
+ color: 'success',
51
+ })
52
+
53
+ isEmailVerificationMessageVisible.value = true
42
54
  } catch (error) {
43
55
  console.error('Error signing up:', error)
56
+
57
+ toast.add({
58
+ title: 'Could not create account',
59
+ description:
60
+ error instanceof Error ? error.message : 'An unknown error occurred.',
61
+ icon: 'lucide:circle-x',
62
+ color: 'error',
63
+ })
44
64
  } finally {
45
65
  isSubmitting.value = false
46
66
  }
@@ -50,52 +70,92 @@ async function submitForm() {
50
70
  <template>
51
71
  <div class="grid h-full place-items-center">
52
72
  <UCard class="w-100">
53
- <form @submit.prevent="submitForm">
73
+ <div v-if="isEmailVerificationMessageVisible" class="space-y-6 py-10">
74
+ <div class="text-center space-y-4">
75
+ <hgroup class="flex flex-col gap-y-1">
76
+ <span class="text-5xl">
77
+ <UIcon name="lucide:mail-warning" class="inline-block" />
78
+ </span>
79
+
80
+ <h1 class="text-3xl font-bold">Almost there!</h1>
81
+ </hgroup>
82
+
83
+ <p>
84
+ A verification link has been sent to your email. Please check your
85
+ inbox and follow the instructions to verify your account.
86
+ </p>
87
+
88
+ <UAlert icon="lucide:check" color="success" variant="outline">
89
+ <template #description>
90
+ You can close this page. You will be logged in automatically once
91
+ you verify your email.
92
+ </template>
93
+ </UAlert>
94
+
95
+ <UAlert
96
+ icon="lucide:alert-triangle"
97
+ color="warning"
98
+ variant="outline"
99
+ >
100
+ <template #description>
101
+ If you don't see the email, please check your spam folder.
102
+ </template>
103
+ </UAlert>
104
+ </div>
105
+ </div>
106
+
107
+ <form v-else @submit.prevent="submitForm">
54
108
  <div class="flex flex-col gap-y-6">
55
109
  <h1 class="text-3xl font-bold">Create Account</h1>
56
110
 
57
- <UFormField label="Email">
111
+ <UFormField label="Email" required>
58
112
  <UInput
59
113
  v-model="form.email"
60
114
  type="email"
61
115
  placeholder="victor@example.com"
62
116
  class="w-full"
117
+ required
63
118
  />
64
119
  </UFormField>
65
120
 
66
121
  <div class="flex gap-x-4">
67
- <UFormField label="First Name">
122
+ <UFormField label="Username" required>
68
123
  <UInput
69
- v-model="form.first_name"
70
- placeholder="e.g: John"
124
+ v-model="form.username"
125
+ placeholder="e.g: johndoe"
71
126
  class="w-full"
127
+ minlength="3"
128
+ required
72
129
  />
73
130
  </UFormField>
74
131
 
75
- <UFormField label="Last Name">
132
+ <UFormField label="Display Name" required>
76
133
  <UInput
77
- v-model="form.last_name"
78
- placeholder="e.g: Doe"
134
+ v-model="form.display_name"
135
+ placeholder="e.g: John Doe"
79
136
  class="w-full"
137
+ required
80
138
  />
81
139
  </UFormField>
82
140
  </div>
83
141
 
84
- <UFormField :error="!passwordMatch" label="Password">
142
+ <UFormField :error="!passwordMatch" label="Password" required>
85
143
  <UInput
86
144
  v-model="form.password"
87
145
  placeholder="••••"
88
146
  class="w-full"
89
147
  type="password"
148
+ required
90
149
  />
91
150
  </UFormField>
92
151
 
93
- <UFormField :error="!passwordMatch" label="Confirm Password">
152
+ <UFormField :error="!passwordMatch" label="Confirm Password" required>
94
153
  <UInput
95
154
  v-model="form.confirm_password"
96
155
  placeholder="••••"
97
156
  class="w-full"
98
157
  type="password"
158
+ required
99
159
  />
100
160
  </UFormField>
101
161
 
@@ -0,0 +1,134 @@
1
+ <script setup lang="ts">
2
+ definePageMeta({
3
+ layout: 'auth',
4
+ })
5
+
6
+ useHead({
7
+ title: 'Update Password',
8
+ })
9
+
10
+ const route = useRoute()
11
+ const supabase = useSupabaseClient()
12
+
13
+ // Exchange the PKCE code for a session when arriving from a password reset email
14
+ onMounted(async () => {
15
+ const code = route.query.code as string | undefined
16
+
17
+ if (code) {
18
+ await supabase.auth.exchangeCodeForSession(code)
19
+ }
20
+ })
21
+
22
+ const { isSubmitting, updatePassword } = await useAuth()
23
+
24
+ const form = ref({
25
+ password: '',
26
+ confirm_password: '',
27
+ })
28
+
29
+ const passwordMatch = computed(() => {
30
+ return form.value.password === form.value.confirm_password
31
+ })
32
+
33
+ const isPasswordUpdated = ref(false)
34
+
35
+ async function submitForm() {
36
+ if (!passwordMatch.value) {
37
+ return
38
+ }
39
+
40
+ const result = await updatePassword(form.value.password)
41
+
42
+ if (result.success) {
43
+ isPasswordUpdated.value = true
44
+
45
+ await supabase.auth.signOut()
46
+ }
47
+ }
48
+ </script>
49
+
50
+ <template>
51
+ <div class="grid h-full place-items-center">
52
+ <UCard class="w-100">
53
+ <div v-if="isPasswordUpdated" class="space-y-6 py-10">
54
+ <div class="text-center space-y-4">
55
+ <hgroup class="flex flex-col gap-y-1">
56
+ <span class="text-5xl">
57
+ <UIcon name="lucide:lock-keyhole" class="inline-block" />
58
+ </span>
59
+
60
+ <h1 class="text-3xl font-bold">Password Updated!</h1>
61
+ </hgroup>
62
+
63
+ <p>
64
+ Your password has been changed. You can now log in with your new
65
+ password.
66
+ </p>
67
+
68
+ <div class="flex items-center justify-center">
69
+ <UButton to="/admin/login" color="primary" icon="lucide:log-in">
70
+ Go to Login
71
+ </UButton>
72
+ </div>
73
+ </div>
74
+ </div>
75
+
76
+ <form v-else @submit.prevent="submitForm">
77
+ <div class="flex flex-col gap-y-6">
78
+ <hgroup class="flex flex-col gap-y-1">
79
+ <h1 class="text-3xl font-bold">Update Password</h1>
80
+ <p class="text-sm text-muted">Enter your new password below.</p>
81
+ </hgroup>
82
+
83
+ <UFormField label="New Password">
84
+ <UInput
85
+ v-model="form.password"
86
+ placeholder="••••"
87
+ type="password"
88
+ class="w-full"
89
+ autocomplete="new-password"
90
+ required
91
+ />
92
+ </UFormField>
93
+
94
+ <UFormField
95
+ :error="
96
+ form.confirm_password && !passwordMatch
97
+ ? 'Passwords do not match.'
98
+ : undefined
99
+ "
100
+ label="Confirm New Password"
101
+ >
102
+ <UInput
103
+ v-model="form.confirm_password"
104
+ placeholder="••••"
105
+ type="password"
106
+ class="w-full"
107
+ autocomplete="new-password"
108
+ required
109
+ />
110
+ </UFormField>
111
+
112
+ <div class="flex items-center justify-between gap-x-4">
113
+ <UFormField>
114
+ <UButton
115
+ :loading="isSubmitting"
116
+ :disabled="!passwordMatch"
117
+ type="submit"
118
+ icon="lucide:lock-keyhole"
119
+ >
120
+ Update Password
121
+ </UButton>
122
+ </UFormField>
123
+
124
+ <UFormField>
125
+ <UButton variant="link" to="/admin/login" class="px-0">
126
+ Back to Login
127
+ </UButton>
128
+ </UFormField>
129
+ </div>
130
+ </div>
131
+ </form>
132
+ </UCard>
133
+ </div>
134
+ </template>
@@ -23,11 +23,6 @@ const items = ref<StepperItem[]>([
23
23
  icon: 'i-lucide-database',
24
24
  },
25
25
 
26
- /* {
27
- title: 'Admin Account',
28
- icon: 'i-lucide-user',
29
- }, */
30
-
31
26
  {
32
27
  title: 'Complete Setup',
33
28
  icon: 'i-lucide-check',
@@ -61,7 +56,15 @@ const currentStepId = computed(() => {
61
56
  return undefined
62
57
  })
63
58
 
64
- async function completeSetup() {
59
+ const currentStepSubmitLabel = computed(() => {
60
+ if (currentStep.value === 0) {
61
+ return 'Set up database'
62
+ }
63
+
64
+ return 'Submit'
65
+ })
66
+
67
+ async function completeDatabaseSetup() {
65
68
  try {
66
69
  isSettingUpDatabase.value = true
67
70
 
@@ -112,8 +115,8 @@ async function completeSetup() {
112
115
  }
113
116
 
114
117
  function handleStepChange(step: number) {
115
- if (step === 1) {
116
- completeSetup()
118
+ if (step === 0) {
119
+ completeDatabaseSetup()
117
120
  }
118
121
  }
119
122
  </script>
@@ -121,22 +124,16 @@ function handleStepChange(step: number) {
121
124
  <template>
122
125
  <UApp>
123
126
  <div class="flex justify-center items-center h-screen">
124
- <UCard class="w-[500px]">
127
+ <UCard class="w-125">
125
128
  <div class="flex flex-col gap-y-8">
126
- <UStepper
127
- ref="stepper"
128
- v-model="currentStep"
129
- :items="items"
130
- disabled
131
- linear
132
- />
129
+ <UStepper ref="stepper" v-model="currentStep" :items="items" linear />
133
130
 
134
131
  <div>
135
132
  <section v-if="currentStep === 0">
136
133
  <UForm
137
134
  id="database-setup-form"
138
135
  class="flex flex-col gap-y-4"
139
- @submit="handleStepChange(currentStep + 1)"
136
+ @submit="handleStepChange(currentStep)"
140
137
  >
141
138
  <UFormField label="Supabase connection string" required>
142
139
  <UInput
@@ -259,21 +256,28 @@ function handleStepChange(step: number) {
259
256
  </UForm>
260
257
  </section>
261
258
 
262
- <section v-else-if="currentStep === 1">
259
+ <section v-if="currentStep === 1">
263
260
  <div class="flex flex-col gap-y-4">
264
- <h1 class="text-2xl font-bold text-center">
265
- Setup Complete 🎉
266
- </h1>
261
+ <hgroup class="text-center">
262
+ <span class="text-3xl">🎉</span>
263
+
264
+ <h1 class="font-bold text-2xl">Setup Complete</h1>
265
+ </hgroup>
267
266
 
268
- <p>
269
- Your database has been set up successfully! 🎉 You can now log
270
- in to the admin panel using your Supabase credentials.
267
+ <p class="text-balance text-center text-lg">
268
+ Your database is ready. Create your admin account by signing
269
+ up.
271
270
  </p>
272
271
 
273
272
  <p class="text-center">
274
- <ULink to="/admin/login" class="underline">
275
- Go to login page</ULink
273
+ <UButton
274
+ icon="lucide:arrow-right"
275
+ to="/admin/signup"
276
+ color="primary"
277
+ trailing
276
278
  >
279
+ Go to sign up
280
+ </UButton>
277
281
  </p>
278
282
  </div>
279
283
  </section>
@@ -286,10 +290,9 @@ function handleStepChange(step: number) {
286
290
  :form="currentStepId"
287
291
  :loading="currentLoading"
288
292
  leading-icon="lucide:check"
289
- trailing-icon="lucide:arrow-right"
290
293
  type="submit"
291
294
  >
292
- Submit and continue
295
+ {{ currentStepSubmitLabel }}
293
296
  </UButton>
294
297
  </div>
295
298
  </div>
@@ -5,7 +5,7 @@ definePageMeta({
5
5
  })
6
6
 
7
7
  const route = useRoute()
8
- const { isLoggedIn, logout } = await useAuth()
8
+ const { isLoggedIn, logout, allowedUnauthenticatedPaths } = await useAuth()
9
9
  const toast = useToast()
10
10
 
11
11
  const visibility = useDocumentVisibility()
@@ -14,9 +14,8 @@ watch(visibility, async (current, previous) => {
14
14
  if (current === 'visible' && previous === 'hidden') {
15
15
  if (
16
16
  !isLoggedIn.value &&
17
- route.path.startsWith('/admin/') &&
18
- route.path !== '/admin/login' &&
19
- route.path !== '/admin/signup'
17
+ !route.path.startsWith('/admin/setup') &&
18
+ !allowedUnauthenticatedPaths.includes(route.path)
20
19
  ) {
21
20
  await logout({ redirectTo: route.path })
22
21
 
@@ -0,0 +1,121 @@
1
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'
2
+ import { dirname, join, relative } from 'node:path'
3
+ import { createResolver, defineNuxtModule, getLayerDirectories } from 'nuxt/kit'
4
+
5
+ /**
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.
9
+ *
10
+ * Also seeds the consuming app's shared/types/supabase.ts with a
11
+ * re-export of this layer's own committed snapshot when the consumer
12
+ * has none yet, so `Database` resolves to real types instead of
13
+ * `unknown` on first run. A re-export (rather than a full copy) keeps
14
+ * a single canonical `Database` declaration so Nuxt's shared-imports
15
+ * scanning across layers doesn't see two conflicting definitions. The
16
+ * runtime migration plugin overwrites this with a fully generated file
17
+ * once real migrations run (see server/plugins/migrations.ts).
18
+ */
19
+ export default defineNuxtModule({
20
+ meta: {
21
+ name: 'pluto-migrations',
22
+ configKey: 'plutoMigrations',
23
+ },
24
+
25
+ setup(_options, nuxt) {
26
+ // Let the runtime migration plugin know where the consumer's
27
+ // project root is, so it can write the generated types file there.
28
+ nuxt.options.runtimeConfig.plutoRootDir = nuxt.options.rootDir
29
+
30
+ const consumerTypesPath = join(
31
+ nuxt.options.rootDir,
32
+ 'shared/types/supabase.ts'
33
+ )
34
+ if (!existsSync(consumerTypesPath)) {
35
+ // Resolve *this exact* @plutocms/supabase instance's own snapshot
36
+ // (local-linked or npm-installed, whichever is actually running)
37
+ // rather than a bare package specifier, which would re-resolve
38
+ // through the consumer's own node_modules and could pick up a
39
+ // different, possibly stale, copy of the package.
40
+ const { resolve } = createResolver(import.meta.url)
41
+ const ownTypesPath = resolve('../shared/types/supabase.ts')
42
+ const consumerTypesDir = dirname(consumerTypesPath)
43
+ let importSpecifier = relative(consumerTypesDir, ownTypesPath)
44
+ .replace(/\\/g, '/')
45
+ .replace(/\.ts$/, '')
46
+ if (!importSpecifier.startsWith('.')) {
47
+ importSpecifier = `./${importSpecifier}`
48
+ }
49
+
50
+ mkdirSync(consumerTypesDir, { recursive: true })
51
+ writeFileSync(
52
+ consumerTypesPath,
53
+ `export * from '${importSpecifier}'\n`,
54
+ 'utf-8'
55
+ )
56
+ console.warn(
57
+ '[pluto-migrations] Seeded shared/types/supabase.ts (re-exporting @plutocms/supabase types; no generated types found yet).'
58
+ )
59
+ }
60
+
61
+ const layerDirs = getLayerDirectories()
62
+ const layerSchemas: Record<string, string> = {}
63
+ const schemaPattern = /^schema\.(.+)\.sql$/
64
+
65
+ for (const layer of layerDirs) {
66
+ // Each layer may have a public/ directory with schema files
67
+ const publicDir = join(layer.root, 'public')
68
+
69
+ if (!existsSync(publicDir)) {
70
+ continue
71
+ }
72
+
73
+ let files: string[] = []
74
+ try {
75
+ files = readdirSync(publicDir, 'utf-8')
76
+ } catch {
77
+ continue
78
+ }
79
+
80
+ for (const file of files) {
81
+ // Skip the core schema
82
+ if (file === 'schema.sql') {
83
+ continue
84
+ }
85
+
86
+ const match = file.match(schemaPattern)
87
+ if (match?.[1]) {
88
+ const layerName = match[1]
89
+ // Read SQL content at build time
90
+ try {
91
+ layerSchemas[layerName] = readFileSync(
92
+ join(publicDir, file),
93
+ 'utf-8'
94
+ )
95
+ } catch {
96
+ console.error(
97
+ `[pluto-migrations] Failed to read schema file: ${file}`
98
+ )
99
+ }
100
+ }
101
+ }
102
+ }
103
+
104
+ const layerNames = Object.keys(layerSchemas)
105
+
106
+ // Populate runtimeConfig with layer names and their SQL content.
107
+ // 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) {
116
+ console.warn(
117
+ `[pluto-migrations] Discovered layer schemas: ${layerNames.join(', ')}`
118
+ )
119
+ }
120
+ },
121
+ })
package/nuxt.config.ts CHANGED
@@ -1,5 +1,10 @@
1
+ // Point these at a local checkout (e.g. `../pluto`, `../utils`) to test
2
+ // unpublished changes; unset, they resolve to the published npm packages.
3
+ const plutoLayer = process.env.PLUTO_PLUTO_PATH || '@plutocms/pluto'
4
+ const utilsLayer = process.env.PLUTO_UTILS_PATH || '@plutocms/utils'
5
+
1
6
  export default defineNuxtConfig({
2
- extends: [['@plutocms/pluto', { install: true }], '@plutocms/utils'],
7
+ extends: [[plutoLayer, { install: true }], utilsLayer],
3
8
 
4
9
  modules: ['@nuxt/eslint', '@nuxtjs/supabase'],
5
10
 
@@ -10,6 +15,8 @@ export default defineNuxtConfig({
10
15
  runtimeConfig: {
11
16
  supabaseUrl: process.env.SUPABASE_URL,
12
17
  supabaseKey: process.env.SUPABASE_KEY,
18
+ plutoRootDir: '',
19
+ plutoLayerSchemas: {} as Record<string, string>,
13
20
  },
14
21
 
15
22
  alias: {
@@ -24,6 +31,12 @@ export default defineNuxtConfig({
24
31
  },
25
32
  },
26
33
 
34
+ vite: {
35
+ optimizeDeps: {
36
+ include: ['yup'],
37
+ },
38
+ },
39
+
27
40
  eslint: {
28
41
  config: {
29
42
  nuxt: {
package/package.json CHANGED
@@ -1,7 +1,26 @@
1
1
  {
2
2
  "name": "@plutocms/supabase",
3
3
  "type": "module",
4
- "version": "0.0.1-alpha.8",
4
+ "version": "0.1.0",
5
+ "trustedDependencies": [
6
+ "@parcel/watcher",
7
+ "@plutocms/pluto",
8
+ "@plutocms/utils",
9
+ "esbuild",
10
+ "supabase",
11
+ "unrs-resolver",
12
+ "vue-demi"
13
+ ],
14
+ "description": "Supabase backend layer for Pluto CMS",
15
+ "author": "Victor Ribeiro",
16
+ "homepage": "https://github.com/plutocms/supabase#readme",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/plutocms/supabase.git"
20
+ },
21
+ "bugs": {
22
+ "url": "https://github.com/plutocms/supabase/issues"
23
+ },
5
24
  "main": "./nuxt.config.ts",
6
25
  "scripts": {
7
26
  "build": "nuxt build",
@@ -12,35 +31,32 @@
12
31
  "prettier": "prettier --write .",
13
32
  "lint": "eslint .",
14
33
  "lint:fix": "eslint . --fix && prettier --write .",
34
+ "typecheck": "nuxi typecheck",
15
35
  "supabase-types": "bun ./scripts/supabase-typegen.ts"
16
36
  },
17
37
  "dependencies": {
18
- "@nuxtjs/supabase": "2.0.3",
19
- "@plutocms/pluto": "^0.0.1-alpha.4",
20
- "@plutocms/utils": "^0.0.1-alpha.5",
21
- "postgres": "^3.4.8",
22
- "supabase": "^2.76.15",
38
+ "@nuxt/ui": "^4.9.0",
39
+ "@nuxtjs/supabase": "^2.0.9",
40
+ "@plutocms/pluto": "^0.1.0",
41
+ "@plutocms/utils": "^0.1.0",
42
+ "postgres": "^3.4.9",
43
+ "supabase": "^2.109.1",
23
44
  "yup": "1.7.1"
24
45
  },
25
46
  "devDependencies": {
26
47
  "@antfu/eslint-config": "^6.2.0",
27
- "@nuxt/eslint": "^1.15.1",
28
- "@nuxt/ui": "^4.4.0",
29
- "@types/bun": "^1.3.9",
30
- "@vueuse/nuxt": "^14.2.1",
31
- "eslint": "^9.39.1",
32
- "nuxt": "^4.3.1",
33
- "prettier": "^3.8.1",
34
- "vue": "^3.5.28",
35
- "vue-router": "^5.0.2"
48
+ "@nuxt/eslint": "^1.16.0",
49
+ "@types/bun": "^1.3.14",
50
+ "@vueuse/nuxt": "^14.3.0",
51
+ "eslint": "^10.6.0",
52
+ "nuxt": "^4.4.2",
53
+ "prettier": "^3.9.4",
54
+ "tailwindcss": "^4.3.2",
55
+ "vue": "^3.5.39",
56
+ "vue-router": "^5.1.0",
57
+ "vue-tsc": "^3.3.7"
36
58
  },
37
- "trustedDependencies": [
38
- "@parcel/watcher",
39
- "@plutocms/pluto",
40
- "@plutocms/utils",
41
- "esbuild",
42
- "supabase",
43
- "unrs-resolver",
44
- "vue-demi"
45
- ]
59
+ "publishConfig": {
60
+ "access": "public"
61
+ }
46
62
  }