create-nextblock 0.9.0 → 0.9.5

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 (39) hide show
  1. package/bin/create-nextblock.js +40 -60
  2. package/docker-template/.env.docker.example +5 -4
  3. package/docker-template/Dockerfile +20 -3
  4. package/docker-template/docker-compose.yml +5 -1
  5. package/docker-template/scripts/docker-setup.mjs +19 -4
  6. package/package.json +1 -1
  7. package/templates/nextblock-template/Dockerfile +20 -3
  8. package/templates/nextblock-template/app/[slug]/page.tsx +5 -0
  9. package/templates/nextblock-template/app/actions.ts +58 -8
  10. package/templates/nextblock-template/app/api/cron/reset-sandbox/sandboxResetSql.ts +83 -0
  11. package/templates/nextblock-template/app/article/[slug]/page.tsx +5 -0
  12. package/templates/nextblock-template/app/cms/settings/security/actions.ts +30 -0
  13. package/templates/nextblock-template/app/cms/settings/security/components/SecurityPanel.tsx +69 -0
  14. package/templates/nextblock-template/app/layout.tsx +57 -3
  15. package/templates/nextblock-template/app/lib/site-settings.ts +22 -7
  16. package/templates/nextblock-template/app/page.tsx +6 -0
  17. package/templates/nextblock-template/app/product/[slug]/page.tsx +5 -0
  18. package/templates/nextblock-template/app/setup/SetupWizard.tsx +771 -0
  19. package/templates/nextblock-template/app/setup/layout.tsx +13 -0
  20. package/templates/nextblock-template/app/setup/page.tsx +103 -0
  21. package/templates/nextblock-template/components/AppShell.tsx +12 -0
  22. package/templates/nextblock-template/components/header-auth.tsx +24 -62
  23. package/templates/nextblock-template/docker-compose.yml +5 -1
  24. package/templates/nextblock-template/docs/11-SELF-HOSTED-DOCKER.md +173 -0
  25. package/templates/nextblock-template/docs/12-VERCEL-DEPLOYMENT.md +67 -0
  26. package/templates/nextblock-template/docs/README.md +2 -0
  27. package/templates/nextblock-template/lib/blocks/FeaturedProductBlock.tsx +1 -1
  28. package/templates/nextblock-template/lib/blocks/ProductGridBlock.tsx +1 -1
  29. package/templates/nextblock-template/lib/setup/actions.ts +370 -0
  30. package/templates/nextblock-template/lib/setup/env-status.ts +86 -0
  31. package/templates/nextblock-template/lib/setup/env-write.ts +111 -0
  32. package/templates/nextblock-template/lib/setup/provisioning.ts +59 -0
  33. package/templates/nextblock-template/lib/setup/schema-apply.ts +379 -0
  34. package/templates/nextblock-template/lib/setup/system-config.ts +105 -0
  35. package/templates/nextblock-template/lib/setup/types.ts +18 -0
  36. package/templates/nextblock-template/package.json +1 -1
  37. package/templates/nextblock-template/proxy.ts +143 -49
  38. package/templates/nextblock-template/scripts/docker-setup.mjs +19 -4
  39. package/templates/nextblock-template/tsconfig.tsbuildinfo +1 -1
@@ -25,6 +25,7 @@ import { verifyPackageOnline } from '@nextblock-cms/db/server';
25
25
  import { unstable_cache } from 'next/cache';
26
26
  import { createStaticSupabaseClient, getSiteSettings } from './lib/site-settings';
27
27
  import { DEFAULT_OG_IMAGE } from './lib/seo';
28
+ import { isSupabaseConfigured } from '../lib/setup/env-status';
28
29
 
29
30
  const defaultUrl = process.env.NEXT_PUBLIC_URL || 'http://localhost:3000';
30
31
 
@@ -230,11 +231,40 @@ const getCachedActiveLogo = unstable_cache(
230
231
  );
231
232
 
232
233
  async function loadLayoutData() {
233
- const supabase = createSupabaseServerClient();
234
-
235
234
  const headerList = await headers();
236
- const cookieStore = await cookies();
237
235
  const nonce = headerList.get('x-nonce') || '';
236
+ const requestPath = headerList.get('x-nextblock-path') || '';
237
+
238
+ // Skip the public-chrome data loading when there's nothing to render it on: an
239
+ // unconfigured instance, OR the standalone /setup wizard. On /setup, AppShell shows no
240
+ // header/footer anyway, and the DB schema may not exist yet (configured-but-pre-migrate)
241
+ // — querying it just produces noisy "table not found" errors for data nobody displays.
242
+ if (!isSupabaseConfigured() || requestPath.startsWith('/setup')) {
243
+ return {
244
+ user: null,
245
+ profile: null,
246
+ serverDeterminedLocale: DEFAULT_LOCALE_FOR_LAYOUT,
247
+ availableCurrencies: [] as StoreCurrency[],
248
+ serverCurrencyCode: null,
249
+ availableLanguages: [] as Language[],
250
+ defaultLanguage: null,
251
+ translations: [] as Awaited<ReturnType<typeof getCachedTranslations>>,
252
+ copyrightText: '',
253
+ nonce,
254
+ hasSupabaseEnv: false,
255
+ headerNavItems: [] as NavigationItem[],
256
+ footerNavItems: [] as NavigationItem[],
257
+ logo: null as HeaderLogo | null,
258
+ canAccessCms: false,
259
+ siteTitle: 'NextBlock',
260
+ isEcommerceActive: false,
261
+ globalCss: '',
262
+ privacySettings: DEFAULT_PRIVACY_SETTINGS,
263
+ };
264
+ }
265
+
266
+ const supabase = createSupabaseServerClient();
267
+ const cookieStore = await cookies();
238
268
 
239
269
  const xUserLocaleHeader = headerList.get('x-user-locale');
240
270
  const nextUserLocaleCookie = cookieStore.get('NEXT_USER_LOCALE')?.value;
@@ -417,6 +447,22 @@ export default async function RootLayout({
417
447
  ? (await import('@vercel/toolbar/next')).VercelToolbar
418
448
  : null;
419
449
 
450
+ // Expose the PUBLIC Supabase values (url + anon key — both safe to ship to the
451
+ // browser) at runtime. In production the client uses the build-time-inlined
452
+ // NEXT_PUBLIC_* and ignores this; it only matters in local dev, where the wizard
453
+ // writes those vars at runtime and the already-loaded browser bundle would otherwise
454
+ // hold stale empties until a dev-server restart. Read from server process.env here,
455
+ // so it's always fresh.
456
+ const publicEnvBootstrap = (() => {
457
+ const url = process.env.NEXT_PUBLIC_SUPABASE_URL || '';
458
+ const anonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || '';
459
+ if (!url || !anonKey) return '';
460
+ return `window.__NEXTBLOCK_PUBLIC_ENV__=${JSON.stringify({ url, anonKey }).replace(
461
+ /</g,
462
+ '\\u003c',
463
+ )};`;
464
+ })();
465
+
420
466
  return (
421
467
  <html lang={serverDeterminedLocale} suppressHydrationWarning>
422
468
  <head>
@@ -424,6 +470,14 @@ export default async function RootLayout({
424
470
  {globalCss && <style dangerouslySetInnerHTML={{ __html: globalCss }} />}
425
471
  </head>
426
472
  <body className="min-h-screen">
473
+ {publicEnvBootstrap && (
474
+ <Script
475
+ id="nextblock-public-env"
476
+ strategy={TRUSTED_TYPES_SCRIPT_STRATEGY}
477
+ nonce={nonce}
478
+ dangerouslySetInnerHTML={{ __html: publicEnvBootstrap }}
479
+ />
480
+ )}
427
481
  {/* In development this loads after hydration to avoid browser-hidden nonce comparisons. */}
428
482
  <Script
429
483
  id="trusted-types-bootstrap"
@@ -23,13 +23,16 @@ export interface SiteSettings {
23
23
  * route `generateMetadata` functions.
24
24
  */
25
25
  export function createStaticSupabaseClient() {
26
- const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
26
+ // Fall back to a dummy host when unconfigured (fresh clone, pre-/setup) so the
27
+ // root layout can still render rather than crashing the whole app on boot. Callers
28
+ // (getSiteSettings + the getCached* helpers) already swallow failures and use
29
+ // fallbacks, and loadLayoutData short-circuits before reaching here when there is
30
+ // no Supabase env at all.
31
+ const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL || 'https://dummy.supabase.co';
27
32
  const supabaseKey =
28
- process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
29
-
30
- if (!supabaseUrl || !supabaseKey) {
31
- throw new Error('Missing Supabase environment variables for public layout data');
32
- }
33
+ process.env.SUPABASE_SERVICE_ROLE_KEY ||
34
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY ||
35
+ 'dummy-anon-key';
33
36
 
34
37
  return createSupabaseJsClient<Database>(supabaseUrl, supabaseKey, {
35
38
  auth: {
@@ -53,6 +56,13 @@ export const getSiteSettings = unstable_cache(
53
56
  siteKeywords: DEFAULT_SITE_KEYWORDS,
54
57
  };
55
58
 
59
+ // Unconfigured instance (pre-/setup): the static client would point at the dummy
60
+ // host, so the fetch fails with a noisy DNS error before falling back. Skip it and
61
+ // return SEO defaults directly. (generateMetadata calls this even on /setup.)
62
+ if (!process.env.NEXT_PUBLIC_SUPABASE_URL) {
63
+ return fallback;
64
+ }
65
+
56
66
  try {
57
67
  const supabase = createStaticSupabaseClient();
58
68
  const { data, error } = await supabase
@@ -61,7 +71,12 @@ export const getSiteSettings = unstable_cache(
61
71
  .in('key', ['site_title', 'site_description', 'site_keywords']);
62
72
 
63
73
  if (error || !data) {
64
- console.error('Error fetching cached site settings:', error);
74
+ // PGRST205 = table not found: the schema isn't migrated yet (e.g. mid-setup,
75
+ // right after the connection is saved but before `npm run db:migrate`). That's an
76
+ // expected transient state — don't shout about it. Real errors still log.
77
+ if (error && error.code !== 'PGRST205') {
78
+ console.error('Error fetching cached site settings:', error);
79
+ }
65
80
  return fallback;
66
81
  }
67
82
 
@@ -63,6 +63,12 @@ async function getPreferredLocale() {
63
63
  }
64
64
 
65
65
  export async function generateMetadata(): Promise<Metadata> {
66
+ // Unconfigured instance (pre-/setup): skip all DB work so metadata generation
67
+ // can't crash the boot. The proxy redirects unconfigured traffic to /setup anyway.
68
+ if (!process.env.NEXT_PUBLIC_SUPABASE_URL || !process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY) {
69
+ return { title: 'NextBlock' };
70
+ }
71
+
66
72
  const preferredLocale = await getPreferredLocale();
67
73
  const homepageSlug = await getHomepageSlugForLocale(preferredLocale);
68
74
  const pageData = await getPageDataBySlug(homepageSlug, preferredLocale);
@@ -74,6 +74,11 @@ function resolveCategoryName(category: any, languageCode?: string | null) {
74
74
  }
75
75
 
76
76
  export async function generateStaticParams() {
77
+ // Unconfigured instance (pre-/setup): no DB to read product slugs from.
78
+ if (!process.env.NEXT_PUBLIC_SUPABASE_URL || !process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY) {
79
+ return [];
80
+ }
81
+
77
82
  const supabase = getSsgSupabaseClient();
78
83
  const { data: products } = await getProducts(supabase);
79
84
  const productRows = ((products || []) as any[]).filter(