@thinhnguyencth1204/nextcli 0.4.2 → 0.5.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/README.md CHANGED
@@ -27,7 +27,7 @@ This command is fully interactive:
27
27
 
28
28
  - enter project name
29
29
  - select package manager in CLI UI (`npm`, `pnpm`, `yarn`, `bun`)
30
- - multi-select optional modules (`chat`, `supabase`, `supabase-realtime`, `seo`, `resend`)
30
+ - multi-select optional modules (`chat`, `supabase-realtime`, `seo`, `resend`)
31
31
  - confirm install step
32
32
  - normalizes project directory name into a safe project slug for generated `package.json` and env placeholders
33
33
  - ships `SETUP.md` and `PROJECT_STRUCTURE.md` in the generated project root
@@ -95,7 +95,6 @@ Module copy is non-destructive: existing files are kept and reported as skipped
95
95
  Interactive multiselect shows available module catalog:
96
96
 
97
97
  - `chat`
98
- - `supabase`
99
98
  - `supabase-realtime`
100
99
  - `seo`
101
100
  - `resend`
@@ -103,9 +102,11 @@ Interactive multiselect shows available module catalog:
103
102
  Non-interactive example:
104
103
 
105
104
  ```bash
106
- node ../dist/cli.js add module --module seo --module supabase --yes
105
+ node ../dist/cli.js add module --module seo --module supabase-realtime --yes
107
106
  ```
108
107
 
108
+ `supabase` is no longer an optional module; Supabase client, Storage helpers, and Supabase Postgres env placeholders are part of the base template.
109
+
109
110
  ## Command: add auth-provider
110
111
 
111
112
  Enable social auth providers after project creation (not created by default):
@@ -128,7 +129,7 @@ node ../dist/cli.js add auth-provider --provider google --provider facebook --ye
128
129
  This command:
129
130
 
130
131
  - updates `src/lib/auth.ts` provider block
131
- - merges provider env keys into `.env` and `.env.example`
132
+ - merges provider env keys into `.env`, `.env.example`, and `.env.development`
132
133
  - runs Better Auth schema generation helper (with install prompt if CLI is missing in interactive mode)
133
134
 
134
135
  ## Command: migrate
@@ -154,6 +155,7 @@ node ../dist/cli.js migrate --name init_auth --skip-generate
154
155
 
155
156
  - Next.js App Router + TypeScript
156
157
  - Better Auth + Prisma adapter
158
+ - Supabase as the default Postgres/Storage stack
157
159
  - Prisma ORM (`prisma/schema.prisma`, `prisma/migrations`)
158
160
  - Axios fetch wrapper
159
161
  - TanStack React Query
@@ -164,7 +166,7 @@ node ../dist/cli.js migrate --name init_auth --skip-generate
164
166
  - i18n base with `next-intl`
165
167
  - Sonner notifications
166
168
  - date-fns utility library
167
- - Optional modules: chat, supabase, supabase-realtime, seo, resend
169
+ - Optional modules: chat, supabase-realtime, seo, resend
168
170
 
169
171
  ## Template structure
170
172
 
@@ -175,7 +177,6 @@ Base template lives in:
175
177
  Optional module templates:
176
178
 
177
179
  - `templates/features/chat`
178
- - `templates/features/supabase`
179
180
  - `templates/features/supabase-realtime`
180
181
  - `templates/features/seo`
181
182
  - `templates/features/resend`
package/dist/cli.js CHANGED
@@ -137,7 +137,7 @@ async function replaceTokensInDirectory(directoryPath, replacements) {
137
137
  }
138
138
  }
139
139
  }
140
- async function mergeEnvFile(envFilePath, entries) {
140
+ async function mergeEnvFile(envFilePath, entries, options = {}) {
141
141
  const envExists = await pathExists(envFilePath);
142
142
  const currentContent = envExists ? await readFile(envFilePath, "utf8") : "";
143
143
  const lines = currentContent ? currentContent.split("\n") : [];
@@ -161,6 +161,18 @@ async function mergeEnvFile(envFilePath, entries) {
161
161
  if (additions.length === 0) {
162
162
  return;
163
163
  }
164
+ if (options.header && currentContent.includes(options.header)) {
165
+ const nextContent2 = currentContent.replace(
166
+ options.header,
167
+ `${options.header}
168
+ ${additions.join("\n")}`
169
+ );
170
+ await writeFile(envFilePath, nextContent2, "utf8");
171
+ return;
172
+ }
173
+ if (options.header) {
174
+ additions.unshift(options.header);
175
+ }
164
176
  const separator = currentContent.endsWith("\n") || currentContent.length === 0 ? "" : "\n";
165
177
  const nextContent = `${currentContent}${separator}${additions.join("\n")}
166
178
  `;
@@ -202,7 +214,6 @@ var rootDir = path2.resolve(currentDir, "../");
202
214
  var templatePaths = {
203
215
  base: path2.join(rootDir, "templates/next-base"),
204
216
  chat: path2.join(rootDir, "templates/features/chat"),
205
- supabase: path2.join(rootDir, "templates/features/supabase"),
206
217
  supabaseRealtime: path2.join(rootDir, "templates/features/supabase-realtime"),
207
218
  seo: path2.join(rootDir, "templates/features/seo"),
208
219
  resend: path2.join(rootDir, "templates/features/resend")
@@ -223,25 +234,6 @@ var optionalModules = [
223
234
  | \`NEXT_PUBLIC_ENABLE_CHAT\` | Set \`true\` when chat module is enabled (auto on add) |
224
235
 
225
236
  Requires \`supabase-realtime\` (auto-added). Run \`db:migrate\` after add \u2014 chat Prisma models are appended.`
226
- },
227
- {
228
- id: "supabase",
229
- label: "Supabase",
230
- description: "Adds Supabase client and storage upload helpers",
231
- templatePath: templatePaths.supabase,
232
- env: {
233
- NEXT_PUBLIC_SUPABASE_URL: "",
234
- NEXT_PUBLIC_SUPABASE_ANON_KEY: "",
235
- NEXT_PUBLIC_SUPABASE_STORAGE_BUCKET: "public"
236
- },
237
- dependencies: {
238
- "@supabase/supabase-js": "^2.44.2"
239
- },
240
- setupSection: `| Variable | Where to get |
241
- | -------- | ------------ |
242
- | \`NEXT_PUBLIC_SUPABASE_URL\` | Supabase Dashboard \u2192 Project Settings \u2192 API \u2192 Project URL |
243
- | \`NEXT_PUBLIC_SUPABASE_ANON_KEY\` | Same page \u2192 Project API keys \u2192 \`anon\` \`public\` |
244
- | \`NEXT_PUBLIC_SUPABASE_STORAGE_BUCKET\` | Storage \u2192 create bucket \u2192 use bucket name (default scaffold: \`public\`) |`
245
237
  },
246
238
  {
247
239
  id: "supabase-realtime",
@@ -255,7 +247,7 @@ Requires \`supabase-realtime\` (auto-added). Run \`db:migrate\` after add \u2014
255
247
  dependencies: {
256
248
  "@supabase/supabase-js": "^2.44.2"
257
249
  },
258
- setupSection: `Uses same Supabase URL/anon key as \`supabase\` module. Enable Realtime on tables in Supabase Dashboard \u2192 Database \u2192 Replication.`
250
+ setupSection: `Uses the base Supabase URL/anon key. Enable Realtime on tables in Supabase Dashboard \u2192 Database \u2192 Replication.`
259
251
  },
260
252
  {
261
253
  id: "seo",
@@ -560,7 +552,7 @@ import { readdir as readdir3, readFile as readFile4, writeFile as writeFile4 } f
560
552
  import path4 from "path";
561
553
  import { readdir as readdir2, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
562
554
  var defaultManifest = {
563
- cli: "0.4.2",
555
+ cli: "0.5.0",
564
556
  defaultLocale: "vi",
565
557
  locales: ["vi"],
566
558
  namespaces: ["common", "auth", "example"],
@@ -625,7 +617,9 @@ async function reconcileManifest(projectDir) {
625
617
  ]
626
618
  };
627
619
  merged.defaultLocale = merged.locales.includes(merged.defaultLocale) ? merged.defaultLocale : merged.locales[0] ?? "vi";
628
- merged.modules = [...new Set(merged.modules)];
620
+ merged.modules = [...new Set(merged.modules)].filter(
621
+ (moduleId) => moduleId !== "supabase"
622
+ );
629
623
  merged.features = [...new Set(merged.features)];
630
624
  await writeManifest(projectDir, merged);
631
625
  return merged;
@@ -1307,24 +1301,6 @@ function normalizeModuleSelection(moduleIds) {
1307
1301
  autoAddedModules: autoAdded
1308
1302
  };
1309
1303
  }
1310
- async function upsertEnvValue(envFilePath, key, value) {
1311
- if (!await pathExists(envFilePath)) {
1312
- return;
1313
- }
1314
- const content = await readFile6(envFilePath, "utf8");
1315
- const entry = `${key}=${value}`;
1316
- const pattern = new RegExp(`^${key}=.*$`, "m");
1317
- if (pattern.test(content)) {
1318
- const next = content.replace(pattern, entry);
1319
- if (next !== content) {
1320
- await writeFile6(envFilePath, next, "utf8");
1321
- }
1322
- return;
1323
- }
1324
- const separator = content.endsWith("\n") || content.length === 0 ? "" : "\n";
1325
- await writeFile6(envFilePath, `${content}${separator}${entry}
1326
- `, "utf8");
1327
- }
1328
1304
  function registerAddCommand(program2) {
1329
1305
  const add = program2.command("add").description("Add modules to an existing app");
1330
1306
  add.command("feature").description("Scaffold a feature folder under src/features").argument("<feature-name>", "Feature name").action(async (featureName) => {
@@ -1452,9 +1428,22 @@ function registerAddCommand(program2) {
1452
1428
  process.exitCode = 1;
1453
1429
  return;
1454
1430
  }
1455
- const validIds = new Set(optionalModules.map((module) => module.id));
1456
- const requestedIds = options.module ? options.module.flatMap((value) => value.split(",")).map((value) => value.trim()).filter(Boolean) : [];
1457
- for (const moduleId of requestedIds) {
1431
+ const validIds = new Set(
1432
+ optionalModules.map((module) => module.id)
1433
+ );
1434
+ const requestedValues = options.module ? options.module.flatMap((value) => value.split(",")).map((value) => value.trim()).filter(Boolean) : [];
1435
+ const requestedIds = [];
1436
+ for (const moduleId of requestedValues) {
1437
+ if (validIds.has(moduleId)) {
1438
+ requestedIds.push(moduleId);
1439
+ continue;
1440
+ }
1441
+ if (moduleId === "supabase") {
1442
+ log.info(
1443
+ "Supabase is now part of the base template; only supabase-realtime is optional."
1444
+ );
1445
+ continue;
1446
+ }
1458
1447
  if (!validIds.has(moduleId)) {
1459
1448
  log.error(`Unknown module: ${moduleId}`);
1460
1449
  process.exitCode = 1;
@@ -1493,35 +1482,21 @@ function registerAddCommand(program2) {
1493
1482
  if (selectedModules.includes("chat")) {
1494
1483
  chatSchemaStatus = await ensureChatSchemaInProject(cwd);
1495
1484
  }
1496
- const envEntries = selectedModules.reduce(
1497
- (acc, moduleId) => {
1498
- const module = getModuleById(moduleId);
1499
- return {
1500
- ...acc,
1501
- ...module.env
1502
- };
1503
- },
1504
- {}
1505
- );
1506
- if (Object.keys(envEntries).length > 0) {
1507
- const envTargets = [".env", ".env.example", ".env.development"];
1485
+ const envTargets = [".env", ".env.example", ".env.development"];
1486
+ for (const moduleId of selectedModules) {
1487
+ const module = getModuleById(moduleId);
1488
+ if (Object.keys(module.env).length === 0) {
1489
+ continue;
1490
+ }
1508
1491
  for (const envFile of envTargets) {
1509
1492
  const envPath = path7.join(cwd, envFile);
1510
1493
  if (await pathExists(envPath)) {
1511
- await mergeEnvFile(envPath, envEntries);
1494
+ await mergeEnvFile(envPath, module.env, {
1495
+ header: `# --- module: ${module.id} ---`
1496
+ });
1512
1497
  }
1513
1498
  }
1514
1499
  }
1515
- if (selectedModules.includes("chat")) {
1516
- const envTargets = [".env", ".env.example", ".env.development"];
1517
- for (const envFile of envTargets) {
1518
- await upsertEnvValue(
1519
- path7.join(cwd, envFile),
1520
- "NEXT_PUBLIC_ENABLE_CHAT",
1521
- "true"
1522
- );
1523
- }
1524
- }
1525
1500
  const dependencyEntries = selectedModules.reduce(
1526
1501
  (acc, moduleId) => {
1527
1502
  const module = getModuleById(moduleId);
@@ -1735,7 +1710,9 @@ function registerAddCommand(program2) {
1735
1710
  for (const envFile of envTargets) {
1736
1711
  const envPath = path7.join(cwd, envFile);
1737
1712
  if (await pathExists(envPath)) {
1738
- await mergeEnvFile(envPath, envEntries);
1713
+ await mergeEnvFile(envPath, envEntries, {
1714
+ header: "# --- auth providers ---"
1715
+ });
1739
1716
  }
1740
1717
  }
1741
1718
  finishPrompt(`Enabled providers: ${mergedProviders.join(", ")}`);
@@ -1854,22 +1831,18 @@ function registerCreateCommand(program2) {
1854
1831
  if (selectedModules.includes("chat")) {
1855
1832
  chatSchemaStatus = await ensureChatSchemaInProject(targetPath);
1856
1833
  }
1857
- const moduleEnvEntries = selectedModules.reduce(
1858
- (acc, moduleId) => {
1859
- const module = getModuleById(moduleId);
1860
- return {
1861
- ...acc,
1862
- ...module.env
1863
- };
1864
- },
1865
- {}
1866
- );
1867
- if (Object.keys(moduleEnvEntries).length > 0) {
1868
- const envTargets = [".env", ".env.example", ".env.development"];
1834
+ const envTargets = [".env", ".env.example", ".env.development"];
1835
+ for (const moduleId of selectedModules) {
1836
+ const module = getModuleById(moduleId);
1837
+ if (Object.keys(module.env).length === 0) {
1838
+ continue;
1839
+ }
1869
1840
  for (const envFile of envTargets) {
1870
1841
  const envPath = path8.join(targetPath, envFile);
1871
1842
  if (await pathExists(envPath)) {
1872
- await mergeEnvFile(envPath, moduleEnvEntries);
1843
+ await mergeEnvFile(envPath, module.env, {
1844
+ header: `# --- module: ${module.id} ---`
1845
+ });
1873
1846
  }
1874
1847
  }
1875
1848
  }
@@ -1892,9 +1865,8 @@ function registerCreateCommand(program2) {
1892
1865
  const betterAuthSecret = randomBytes(32).toString("base64url");
1893
1866
  await replaceTokensInDirectory(targetPath, {
1894
1867
  __PROJECT_NAME__: projectSlug,
1895
- __ENABLE_CHAT__: selectedModules.includes("chat") ? "true" : "false",
1896
1868
  __BETTER_AUTH_SECRET__: betterAuthSecret,
1897
- __NEXTCLI_VERSION__: "0.4.2"
1869
+ __NEXTCLI_VERSION__: "0.5.0"
1898
1870
  });
1899
1871
  await mergeModuleSetupSections(
1900
1872
  targetPath,
@@ -1905,7 +1877,7 @@ function registerCreateCommand(program2) {
1905
1877
  if (manifest) {
1906
1878
  await writeManifest(targetPath, {
1907
1879
  ...manifest,
1908
- cli: "0.4.2",
1880
+ cli: "0.5.0",
1909
1881
  modules: selectedModules
1910
1882
  });
1911
1883
  }
@@ -2147,7 +2119,7 @@ var NexTCLICommand = class _NexTCLICommand extends Command {
2147
2119
 
2148
2120
  // src/cli.ts
2149
2121
  var program = new NexTCLICommand();
2150
- program.name("nextcli").description("Scaffold outsource-ready Next.js projects").version("0.4.2");
2122
+ program.name("nextcli").description("Scaffold outsource-ready Next.js projects").version("0.5.0");
2151
2123
  registerCreateCommand(program);
2152
2124
  registerAddCommand(program);
2153
2125
  registerMigrateCommand(program);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thinhnguyencth1204/nextcli",
3
- "version": "0.4.2",
3
+ "version": "0.5.0",
4
4
  "description": "CLI scaffolder for outsourced Next.js projects",
5
5
  "type": "module",
6
6
  "bin": {
@@ -16,6 +16,7 @@
16
16
  "dev": "tsup --watch",
17
17
  "typecheck": "tsc --noEmit",
18
18
  "smoke": "node dist/cli.js --help",
19
+ "smoke:full": "bun run scripts/pre-publish-smoke.ts",
19
20
  "prepublishOnly": "npm run build"
20
21
  },
21
22
  "keywords": [
@@ -1,11 +1,13 @@
1
- DATABASE_URL="postgresql://postgres:postgres@localhost:5432/__PROJECT_NAME__"
1
+ # --- Database (Supabase) ---
2
+ DATABASE_URL="postgresql://postgres:your-supabase-db-password@db.your-project-ref.supabase.co:5432/postgres"
3
+ NEXT_PUBLIC_SUPABASE_URL=""
4
+ NEXT_PUBLIC_SUPABASE_ANON_KEY=""
5
+ NEXT_PUBLIC_SUPABASE_STORAGE_BUCKET="public"
6
+
7
+ # --- Auth ---
2
8
  BETTER_AUTH_SECRET="__BETTER_AUTH_SECRET__"
3
9
  BETTER_AUTH_URL="http://localhost:3000"
4
10
 
11
+ # --- App ---
5
12
  NEXT_PUBLIC_APP_URL="http://localhost:3000"
6
- NEXT_PUBLIC_ENABLE_CHAT=__ENABLE_CHAT__
7
13
  NEXT_PUBLIC_DEFAULT_LOCALE="en"
8
- GOOGLE_CLIENT_ID=""
9
- GOOGLE_CLIENT_SECRET=""
10
- FACEBOOK_CLIENT_ID=""
11
- FACEBOOK_CLIENT_SECRET=""
@@ -1,11 +1,13 @@
1
- DATABASE_URL="postgresql://postgres:postgres@localhost:5432/__PROJECT_NAME___dev"
1
+ # --- Database (Supabase) ---
2
+ DATABASE_URL="postgresql://postgres:your-supabase-db-password@db.your-project-ref.supabase.co:5432/postgres"
3
+ NEXT_PUBLIC_SUPABASE_URL=""
4
+ NEXT_PUBLIC_SUPABASE_ANON_KEY=""
5
+ NEXT_PUBLIC_SUPABASE_STORAGE_BUCKET="public"
6
+
7
+ # --- Auth ---
2
8
  BETTER_AUTH_SECRET="__BETTER_AUTH_SECRET__"
3
9
  BETTER_AUTH_URL="http://localhost:3000"
4
10
 
11
+ # --- App ---
5
12
  NEXT_PUBLIC_APP_URL="http://localhost:3000"
6
- NEXT_PUBLIC_ENABLE_CHAT=__ENABLE_CHAT__
7
13
  NEXT_PUBLIC_DEFAULT_LOCALE="en"
8
- GOOGLE_CLIENT_ID=""
9
- GOOGLE_CLIENT_SECRET=""
10
- FACEBOOK_CLIENT_ID=""
11
- FACEBOOK_CLIENT_SECRET=""
@@ -1,11 +1,13 @@
1
- DATABASE_URL="postgresql://postgres:postgres@localhost:5432/__PROJECT_NAME__"
1
+ # --- Database (Supabase) ---
2
+ DATABASE_URL="postgresql://postgres:your-supabase-db-password@db.your-project-ref.supabase.co:5432/postgres"
3
+ NEXT_PUBLIC_SUPABASE_URL=""
4
+ NEXT_PUBLIC_SUPABASE_ANON_KEY=""
5
+ NEXT_PUBLIC_SUPABASE_STORAGE_BUCKET="public"
6
+
7
+ # --- Auth ---
2
8
  BETTER_AUTH_SECRET="your-32-plus-char-random-secret"
3
9
  BETTER_AUTH_URL="http://localhost:3000"
4
10
 
11
+ # --- App ---
5
12
  NEXT_PUBLIC_APP_URL="http://localhost:3000"
6
- NEXT_PUBLIC_ENABLE_CHAT=__ENABLE_CHAT__
7
13
  NEXT_PUBLIC_DEFAULT_LOCALE="en"
8
- GOOGLE_CLIENT_ID=""
9
- GOOGLE_CLIENT_SECRET=""
10
- FACEBOOK_CLIENT_ID=""
11
- FACEBOOK_CLIENT_SECRET=""
@@ -41,7 +41,7 @@ Generated by NexTCLI. Folders marked **(module)** or **(feature)** appear only w
41
41
  | `rbac.ts` | Role hierarchy guards |
42
42
  | `prisma.ts` | DB client |
43
43
  | `api-response.ts` | `/api/v1/*` envelope helpers |
44
- | `supabase/` | **(module: `supabase`)** client + storage helpers |
44
+ | `supabase/` | Supabase client + Storage helpers |
45
45
  | `resend/` | **(module: `resend`)** email client + send helper |
46
46
 
47
47
  ## `src/features/` vs `src/example/`
@@ -74,7 +74,6 @@ Locale config with `nextcli:locales` / `nextcli:namespaces` markers (patched by
74
74
  | Module | Adds |
75
75
  | ------------------- | ------------------------------------------------------------------- |
76
76
  | `chat` | Chat routes, hooks, Prisma chat models (+ auto `supabase-realtime`) |
77
- | `supabase` | `src/lib/supabase/*`, Storage upload helpers |
78
77
  | `supabase-realtime` | Realtime channel helpers |
79
78
  | `seo` | robots/sitemap/JSON-LD helpers |
80
79
  | `resend` | `src/lib/resend/*`, `src/emails/*` |
@@ -4,7 +4,7 @@ Quick reference for env vars and branding after `nextcli create`.
4
4
 
5
5
  ## First run (required)
6
6
 
7
- 1. Start PostgreSQL and set `DATABASE_URL` in `.env`.
7
+ 1. Create a Supabase project and set `DATABASE_URL` in `.env`.
8
8
  2. `bun run db:migrate` — applies `prisma/migrations` (includes `Role`, `User.username`, `requirePasswordChange`).
9
9
  3. `bun run dev` — bootstrap seeds `admin` / `admin` on first start.
10
10
 
@@ -23,12 +23,15 @@ Quick reference for env vars and branding after `nextcli create`.
23
23
 
24
24
  Set in `.env` / `.env.development` (secrets are generated at create time).
25
25
 
26
- | Variable | Purpose | Where to get |
27
- | --------------------- | ---------------------- | -------------------------------------------------------------- |
28
- | `DATABASE_URL` | PostgreSQL connection | Local Postgres or cloud provider dashboardconnection string |
29
- | `BETTER_AUTH_SECRET` | Auth signing secret | Auto-generated on create; rotate in production |
30
- | `BETTER_AUTH_URL` | Server auth base URL | Your app URL (e.g. `http://localhost:3000`) |
31
- | `NEXT_PUBLIC_APP_URL` | Client-visible app URL | Same as public site URL |
26
+ | Variable | Purpose | Where to get |
27
+ | ------------------------------------- | -------------------------- | ---------------------------------------------------------------------- |
28
+ | `DATABASE_URL` | Supabase Postgres database | Supabase Dashboard Project Settings Database Connection string |
29
+ | `NEXT_PUBLIC_SUPABASE_URL` | Supabase client URL | Supabase Dashboard Project Settings → API → Project URL |
30
+ | `NEXT_PUBLIC_SUPABASE_ANON_KEY` | Supabase browser anon key | Same page Project API keys → `anon` `public` |
31
+ | `NEXT_PUBLIC_SUPABASE_STORAGE_BUCKET` | Supabase Storage bucket | Storage create bucket → use bucket name (default scaffold: `public`) |
32
+ | `BETTER_AUTH_SECRET` | Auth signing secret | Auto-generated on create; rotate in production |
33
+ | `BETTER_AUTH_URL` | Server auth base URL | Your app URL (e.g. `http://localhost:3000`) |
34
+ | `NEXT_PUBLIC_APP_URL` | Client-visible app URL | Same as public site URL |
32
35
 
33
36
  ### Default admin account
34
37
 
@@ -54,17 +57,9 @@ Reference for all optional modules. Matching keys are merged into `.env` when se
54
57
 
55
58
  Requires `supabase-realtime` (auto-added). Run `db:migrate` after add — chat Prisma models are appended.
56
59
 
57
- ### Module: Supabase (`supabase`)
58
-
59
- | Variable | Where to get |
60
- | ------------------------------------- | ---------------------------------------------------------------------- |
61
- | `NEXT_PUBLIC_SUPABASE_URL` | Supabase Dashboard → Project Settings → API → Project URL |
62
- | `NEXT_PUBLIC_SUPABASE_ANON_KEY` | Same page → Project API keys → `anon` `public` |
63
- | `NEXT_PUBLIC_SUPABASE_STORAGE_BUCKET` | Storage → create bucket → use bucket name (default scaffold: `public`) |
64
-
65
60
  ### Module: Supabase Realtime (`supabase-realtime`)
66
61
 
67
- Uses same Supabase URL/anon key as `supabase` module. Enable Realtime on tables in Supabase Dashboard → Database → Replication.
62
+ Uses the base Supabase URL/anon key. Enable Realtime on tables in Supabase Dashboard → Database → Replication.
68
63
 
69
64
  ### Module: SEO pack (`seo`)
70
65
 
@@ -27,6 +27,7 @@
27
27
  "@radix-ui/react-slot": "^1.2.3",
28
28
  "@radix-ui/react-tabs": "^1.1.13",
29
29
  "@radix-ui/react-tooltip": "^1.2.8",
30
+ "@supabase/supabase-js": "^2.44.2",
30
31
  "@tanstack/react-form": "^1.0.3",
31
32
  "@tanstack/react-query": "^5.56.2",
32
33
  "@tanstack/react-query-devtools": "^5.56.2",
@@ -3,7 +3,4 @@ import { createClient } from "@supabase/supabase-js";
3
3
  const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
4
4
  const anonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
5
5
 
6
- export const supabase =
7
- url && anonKey
8
- ? createClient(url, anonKey)
9
- : null;
6
+ export const supabase = url && anonKey ? createClient(url, anonKey) : null;
@@ -86,10 +86,7 @@ function assertCategoryRules(
86
86
 
87
87
  const mime = contentType ?? (file instanceof File ? file.type : "");
88
88
 
89
- if (
90
- !mime ||
91
- !allowedMimePrefixes.some((prefix) => mime.startsWith(prefix))
92
- ) {
89
+ if (!mime || !allowedMimePrefixes.some((prefix) => mime.startsWith(prefix))) {
93
90
  throw new StorageHelperError(
94
91
  `File type "${mime || "unknown"}" is not allowed for category "${category}".`,
95
92
  "VALIDATION",