bsmnt 0.14.0 → 0.15.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 (116) hide show
  1. package/dist/core/create/internal-ids.d.ts +1 -0
  2. package/dist/core/create/internal-ids.d.ts.map +1 -1
  3. package/dist/core/create/internal-ids.js +1 -0
  4. package/dist/core/create/internal-ids.js.map +1 -1
  5. package/dist/domain/skills.d.ts +5 -0
  6. package/dist/domain/skills.d.ts.map +1 -1
  7. package/dist/domain/skills.js +9 -1
  8. package/dist/domain/skills.js.map +1 -1
  9. package/dist/infrastructure/create/format-project.d.ts +9 -0
  10. package/dist/infrastructure/create/format-project.d.ts.map +1 -0
  11. package/dist/infrastructure/create/format-project.js +21 -0
  12. package/dist/infrastructure/create/format-project.js.map +1 -0
  13. package/dist/infrastructure/create/lifecycle-registry.d.ts.map +1 -1
  14. package/dist/infrastructure/create/lifecycle-registry.js +5 -0
  15. package/dist/infrastructure/create/lifecycle-registry.js.map +1 -1
  16. package/dist/modules/features/cms/sanity-pagebuilder/index.d.ts.map +1 -1
  17. package/dist/modules/features/cms/sanity-pagebuilder/index.js +1 -0
  18. package/dist/modules/features/cms/sanity-pagebuilder/index.js.map +1 -1
  19. package/dist/modules/features/cms/sanity-pagebuilder-cache/index.d.ts.map +1 -1
  20. package/dist/modules/features/cms/sanity-pagebuilder-cache/index.js +1 -0
  21. package/dist/modules/features/cms/sanity-pagebuilder-cache/index.js.map +1 -1
  22. package/package.json +1 -1
  23. package/src/agent-skills/launch-ready/SKILL.md +114 -0
  24. package/src/agent-skills/launch-ready/references/a11y.md +74 -0
  25. package/src/agent-skills/launch-ready/references/content.md +65 -0
  26. package/src/agent-skills/launch-ready/references/crawlers.md +89 -0
  27. package/src/agent-skills/launch-ready/references/launch.md +44 -0
  28. package/src/agent-skills/launch-ready/references/metadata.md +83 -0
  29. package/src/agent-skills/launch-ready/references/performance.md +36 -0
  30. package/src/agent-skills/launch-ready/references/routing.md +47 -0
  31. package/src/agent-skills/launch-ready/references/security.md +39 -0
  32. package/src/modules/features/cms/sanity/files/app/layout.tsx +2 -2
  33. package/src/modules/features/cms/sanity/files/lib/utils/metadata.ts +2 -2
  34. package/src/modules/features/cms/sanity/files/lib/utils/url.ts +6 -3
  35. package/src/modules/features/cms/sanity-pagebuilder/files/components/sanity/rich-text.tsx +3 -1
  36. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/redirect-import-dialog.tsx +538 -0
  37. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/csv.ts +88 -0
  38. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/fetchers/breadcrumbs.ts +22 -0
  39. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/page-builder-config.ts +15 -0
  40. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/queries.ts +20 -0
  41. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/redirect-import.ts +651 -0
  42. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/sanity.config.ts +2 -0
  43. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/layout/redirect.ts +9 -6
  44. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/layout/structured-data.ts +8 -0
  45. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/metadata.ts +60 -6
  46. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/page.ts +22 -1
  47. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/richText.ts +0 -1
  48. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/structure.ts +16 -1
  49. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/tags.ts +5 -2
  50. package/src/modules/features/cms/sanity-pagebuilder/files/lib/scripts/sanity-typegen.ts +8 -11
  51. package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/metadata.ts +109 -136
  52. package/src/modules/features/cms/sanity-pagebuilder-cache/files/components/sanity/rich-text.tsx +3 -1
  53. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/redirect-import-dialog.tsx +538 -0
  54. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/csv.ts +88 -0
  55. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/fetchers/breadcrumbs.ts +25 -0
  56. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/page-builder-config.ts +15 -0
  57. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/queries.ts +20 -0
  58. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/redirect-import.ts +651 -0
  59. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/sanity.config.ts +2 -0
  60. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/redirect.ts +9 -6
  61. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/structured-data.ts +8 -0
  62. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/metadata.ts +60 -6
  63. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/page.ts +22 -1
  64. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/richText.ts +0 -1
  65. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/structure.ts +16 -1
  66. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/tags.ts +6 -2
  67. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/scripts/sanity-typegen.ts +8 -11
  68. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/metadata.ts +109 -136
  69. package/src/templates/next-default/.env.example +3 -1
  70. package/src/templates/next-default/app/layout.tsx +2 -1
  71. package/src/templates/next-default/app/robots.ts +9 -3
  72. package/src/templates/next-default/lib/utils/metadata.ts +2 -1
  73. package/src/templates/next-default/lib/utils/url.ts +20 -3
  74. package/src/templates/next-default/next.config.ts +16 -0
  75. package/src/templates/next-experiments/.env.example +3 -1
  76. package/src/templates/next-experiments/app/layout.tsx +2 -1
  77. package/src/templates/next-experiments/app/robots.ts +9 -3
  78. package/src/templates/next-experiments/lib/utils/metadata.ts +2 -1
  79. package/src/templates/next-experiments/lib/utils/url.ts +20 -3
  80. package/src/templates/next-experiments/next.config.ts +16 -0
  81. package/src/templates/next-pagebuilder/.env.example +3 -1
  82. package/src/templates/next-pagebuilder/.github/workflows/ci.yml +32 -8
  83. package/src/templates/next-pagebuilder/AGENTS.md +3 -0
  84. package/src/templates/next-pagebuilder/app/(content)/[[...slug]]/page.tsx +45 -7
  85. package/src/templates/next-pagebuilder/app/layout.tsx +3 -41
  86. package/src/templates/next-pagebuilder/app/robots.ts +9 -3
  87. package/src/templates/next-pagebuilder/biome.json +2 -1
  88. package/src/templates/next-pagebuilder/components/layout/footer/index.tsx +1 -1
  89. package/src/templates/next-pagebuilder/components/page-document/index.tsx +27 -35
  90. package/src/templates/next-pagebuilder/lefthook.yml +1 -1
  91. package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.types.ts +32 -29
  92. package/src/templates/next-pagebuilder/lib/utils/base-url.ts +18 -3
  93. package/src/templates/next-pagebuilder/lib/utils/breadcrumbs.ts +70 -0
  94. package/src/templates/next-pagebuilder/next.config.ts +16 -0
  95. package/src/templates/next-pagebuilder/package.json +1 -1
  96. package/src/templates/next-pagebuilder-cache/.env.example +3 -1
  97. package/src/templates/next-pagebuilder-cache/.github/workflows/ci.yml +32 -8
  98. package/src/templates/next-pagebuilder-cache/AGENTS.md +3 -0
  99. package/src/templates/next-pagebuilder-cache/app/(content)/[[...slug]]/page.tsx +46 -7
  100. package/src/templates/next-pagebuilder-cache/app/layout.tsx +5 -46
  101. package/src/templates/next-pagebuilder-cache/app/robots.ts +9 -3
  102. package/src/templates/next-pagebuilder-cache/biome.json +2 -1
  103. package/src/templates/next-pagebuilder-cache/components/layout/footer/index.tsx +1 -1
  104. package/src/templates/next-pagebuilder-cache/components/page-document/index.tsx +27 -35
  105. package/src/templates/next-pagebuilder-cache/lefthook.yml +1 -1
  106. package/src/templates/next-pagebuilder-cache/lib/integrations/sanity/sanity.types.ts +32 -29
  107. package/src/templates/next-pagebuilder-cache/lib/utils/base-url.ts +18 -3
  108. package/src/templates/next-pagebuilder-cache/lib/utils/breadcrumbs.ts +70 -0
  109. package/src/templates/next-pagebuilder-cache/next.config.ts +16 -0
  110. package/src/templates/next-pagebuilder-cache/package.json +1 -1
  111. package/src/templates/next-webgl/.env.example +3 -1
  112. package/src/templates/next-webgl/app/layout.tsx +2 -1
  113. package/src/templates/next-webgl/app/robots.ts +9 -3
  114. package/src/templates/next-webgl/lib/utils/metadata.ts +2 -1
  115. package/src/templates/next-webgl/lib/utils/url.ts +20 -3
  116. package/src/templates/next-webgl/next.config.ts +16 -0
@@ -3,15 +3,30 @@
3
3
  *
4
4
  * Priority:
5
5
  * 1. NEXT_PUBLIC_BASE_URL (explicit production URL)
6
- * 2. VERCEL_PROJECT_PRODUCTION_URL (Vercel production domain)
7
- * 3. VERCEL_URL (Vercel preview/deployment URL)
6
+ * 2. VERCEL_PROJECT_PRODUCTION_URL, on production deployments only
7
+ * 3. VERCEL_URL (the deployment's own URL, so previews never emit production URLs)
8
8
  * 4. http://localhost:3000 (local development)
9
9
  */
10
10
  export const getBaseUrl = () =>
11
11
  process.env.NEXT_PUBLIC_BASE_URL ||
12
- (process.env.VERCEL_PROJECT_PRODUCTION_URL
12
+ (process.env.VERCEL_ENV === "production" &&
13
+ process.env.VERCEL_PROJECT_PRODUCTION_URL
13
14
  ? `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`
14
15
  : undefined) ||
15
16
  (process.env.VERCEL_URL
16
17
  ? `https://${process.env.VERCEL_URL}`
17
18
  : "http://localhost:3000")
19
+
20
+ // The production build also answers on *.vercel.app; only the launch domain is indexed.
21
+ export const getIndexableHost = () => {
22
+ const baseUrl = process.env.NEXT_PUBLIC_BASE_URL
23
+ // Off Vercel VERCEL_ENV is unset, and the configured host is the launch domain.
24
+ const vercelEnv = process.env.VERCEL_ENV
25
+ if (!baseUrl || (vercelEnv && vercelEnv !== "production")) return null
26
+ return URL.canParse(baseUrl) ? new URL(baseUrl).host : null
27
+ }
28
+
29
+ export const isIndexableHost = (host: string | null | undefined) => {
30
+ const indexable = getIndexableHost()
31
+ return indexable !== null && host?.toLowerCase() === indexable
32
+ }
@@ -0,0 +1,70 @@
1
+ import type { BREADCRUMB_PAGES_QUERY_RESULT } from "@/lib/integrations/sanity/sanity.types"
2
+
3
+ type BreadcrumbPage = BREADCRUMB_PAGES_QUERY_RESULT[number]
4
+
5
+ type Breadcrumb = {
6
+ name: string
7
+ url: string
8
+ }
9
+
10
+ const HOME_NAME = "Home"
11
+
12
+ const trimSlashes = (path: string) => path.replace(/^\/+|\/+$/g, "")
13
+
14
+ const firstNonEmpty = (...values: (string | null | undefined)[]) =>
15
+ values.find((value) => value?.trim())?.trim()
16
+
17
+ const urlFor = (path: string) => (path ? `/${path}` : "/")
18
+
19
+ const labelFor = (page: BreadcrumbPage | undefined) =>
20
+ page && firstNonEmpty(page.shortTitle, page.folderTitle, page.title)
21
+
22
+ // Every parent path, not the path itself: "a/b/c" → ["a", "a/b"].
23
+ const ancestorPaths = (path: string) => {
24
+ const segments = path.split("/").filter(Boolean)
25
+ return segments
26
+ .slice(0, -1)
27
+ .map((_, index) => segments.slice(0, index + 1).join("/"))
28
+ }
29
+
30
+ // The paths a page's crumbs are named from, besides the homepage: its
31
+ // ancestors and itself.
32
+ export const breadcrumbPaths = (path: string | null) => {
33
+ const current = trimSlashes(path ?? "")
34
+ return current ? [...ancestorPaths(current), current] : []
35
+ }
36
+
37
+ // Crumbs are named from the pages at each level, the current one included:
38
+ // Short Title, then the folder name for a folder's index page, then Title. A
39
+ // level with no page is skipped, so a crumb never links to a 404.
40
+ export const buildBreadcrumbs = ({
41
+ path,
42
+ name,
43
+ pages,
44
+ }: {
45
+ path: string | null
46
+ name: string
47
+ pages: readonly BreadcrumbPage[]
48
+ }): Breadcrumb[] => {
49
+ const pageAt = (target: string) =>
50
+ pages.find((page) => trimSlashes(page.path ?? "") === target)
51
+
52
+ const home = pageAt("")
53
+ const crumbs: Breadcrumb[] = [
54
+ {
55
+ name: firstNonEmpty(home?.shortTitle, home?.title) ?? HOME_NAME,
56
+ url: "/",
57
+ },
58
+ ]
59
+
60
+ const current = trimSlashes(path ?? "")
61
+ if (!current) return crumbs
62
+
63
+ for (const ancestor of ancestorPaths(current)) {
64
+ const ancestorName = labelFor(pageAt(ancestor))
65
+ if (ancestorName) crumbs.push({ name: ancestorName, url: urlFor(ancestor) })
66
+ }
67
+
68
+ crumbs.push({ name: labelFor(pageAt(current)) ?? name, url: urlFor(current) })
69
+ return crumbs
70
+ }
@@ -1,6 +1,10 @@
1
1
  import withBundleAnalyzer from "@next/bundle-analyzer"
2
2
  import type { NextConfig } from "next"
3
3
  import { sanity } from "next-sanity/live/cache-life"
4
+ import { getIndexableHost } from "./lib/utils/base-url"
5
+
6
+ // Only the launch domain is indexed; every other host is sent noindex.
7
+ const indexableHost = getIndexableHost()
4
8
 
5
9
  const nextConfig: NextConfig = {
6
10
  cacheComponents: true,
@@ -97,6 +101,18 @@ const nextConfig: NextConfig = {
97
101
  imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
98
102
  },
99
103
  headers: async () => [
104
+ {
105
+ source: "/:path*",
106
+ ...(indexableHost && {
107
+ missing: [
108
+ {
109
+ type: "host" as const,
110
+ value: indexableHost.replaceAll(".", "\\."),
111
+ },
112
+ ],
113
+ }),
114
+ headers: [{ key: "X-Robots-Tag", value: "noindex" }],
115
+ },
100
116
  {
101
117
  // Excludes /studio* — must stay mutually exclusive with the rule below.
102
118
  source: "/((?!studio(?:/|$)).*)",
@@ -17,7 +17,7 @@
17
17
  "knip": "knip",
18
18
  "knip:fix": "knip --fix",
19
19
  "lighthouse": "bunx @unlighthouse/cli --site http://localhost:3000",
20
- "lint": "biome lint --max-diagnostics=200",
20
+ "lint": "biome ci --error-on-warnings --max-diagnostics=200 .",
21
21
  "lint:fix": "biome check --write --unsafe --max-diagnostics=200",
22
22
  "prepare": "lefthook install",
23
23
  "start": "next start",
@@ -33,7 +33,9 @@
33
33
  # Development: http://localhost:3000
34
34
  # Production: https://your-domain.com
35
35
  # Inlined at build time — scope this to the Production environment only in
36
- # Vercel so Preview deployments fall through to VERCEL_PROJECT_PRODUCTION_URL.
36
+ # Vercel so Preview deployments fall through to their own VERCEL_URL. On a
37
+ # production deployment it is also the only host that gets indexed: every other
38
+ # host is sent `X-Robots-Tag: noindex` and a disallow-all robots.txt.
37
39
  NEXT_PUBLIC_BASE_URL=""
38
40
 
39
41
  # Draft mode secret for preview functionality
@@ -11,12 +11,13 @@ import {
11
11
  generateWebSiteJsonLd,
12
12
  JsonLd,
13
13
  } from "@/lib/utils/json-ld"
14
+ import { getBaseUrl } from "@/lib/utils/url"
14
15
 
15
16
  const APP_NAME = AppData.name
16
17
  const APP_DEFAULT_TITLE = "Basement Starter"
17
18
  const APP_TITLE_TEMPLATE = "%s - Basement Starter"
18
19
  const APP_DESCRIPTION = AppData.description
19
- const APP_BASE_URL = process.env.NEXT_PUBLIC_BASE_URL ?? "http://localhost:3000"
20
+ const APP_BASE_URL = getBaseUrl()
20
21
 
21
22
  const geist = Geist({
22
23
  subsets: ["latin"],
@@ -1,12 +1,18 @@
1
1
  import type { MetadataRoute } from "next"
2
- import { getBaseUrl } from "@/lib/utils/url"
2
+ import { headers } from "next/headers"
3
+ import { getBaseUrl, isIndexableHost } from "@/lib/utils/url"
4
+
5
+ export default async function robots(): Promise<MetadataRoute.Robots> {
6
+ const host = (await headers()).get("host")
7
+ if (!isIndexableHost(host)) {
8
+ return { rules: { userAgent: "*", disallow: "/" } }
9
+ }
3
10
 
4
- export default function robots(): MetadataRoute.Robots {
5
11
  return {
6
12
  rules: {
7
13
  userAgent: "*",
8
14
  allow: "/",
9
- disallow: [],
15
+ disallow: ["/api/", "/studio"],
10
16
  },
11
17
  sitemap: `${getBaseUrl()}/sitemap.xml`,
12
18
  }
@@ -1,4 +1,5 @@
1
1
  import type { Metadata } from "next"
2
+ import { getBaseUrl } from "@/lib/utils/url"
2
3
 
3
4
  /**
4
5
  * Metadata Generation Utilities
@@ -26,7 +27,7 @@ interface GenerateMetadataOptions {
26
27
  authors?: string[]
27
28
  }
28
29
 
29
- const APP_BASE_URL = process.env.NEXT_PUBLIC_BASE_URL ?? "http://localhost:3000"
30
+ const APP_BASE_URL = getBaseUrl()
30
31
 
31
32
  /**
32
33
  * Generate complete metadata object for pages
@@ -3,17 +3,34 @@
3
3
  *
4
4
  * Priority:
5
5
  * 1. NEXT_PUBLIC_BASE_URL - explicit override (custom domains)
6
- * 2. VERCEL_PROJECT_PRODUCTION_URL - auto-set by Vercel (production domain)
7
- * 3. VERCEL_URL - auto-set by Vercel (preview/branch deploys)
6
+ * 2. VERCEL_PROJECT_PRODUCTION_URL - auto-set by Vercel, on production deployments only
7
+ * 3. VERCEL_URL - the deployment's own URL, so previews never emit production URLs
8
8
  * 4. localhost fallback
9
9
  */
10
10
  export function getBaseUrl(): string {
11
11
  if (process.env.NEXT_PUBLIC_BASE_URL) {
12
12
  return process.env.NEXT_PUBLIC_BASE_URL
13
13
  }
14
- if (process.env.VERCEL_PROJECT_PRODUCTION_URL) {
14
+ if (
15
+ process.env.VERCEL_ENV === "production" &&
16
+ process.env.VERCEL_PROJECT_PRODUCTION_URL
17
+ ) {
15
18
  return `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`
16
19
  }
17
20
  if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`
18
21
  return "http://localhost:3000"
19
22
  }
23
+
24
+ // The production build also answers on *.vercel.app; only the launch domain is indexed.
25
+ export function getIndexableHost(): string | null {
26
+ const baseUrl = process.env.NEXT_PUBLIC_BASE_URL
27
+ // Off Vercel VERCEL_ENV is unset, and the configured host is the launch domain.
28
+ const vercelEnv = process.env.VERCEL_ENV
29
+ if (!baseUrl || (vercelEnv && vercelEnv !== "production")) return null
30
+ return URL.canParse(baseUrl) ? new URL(baseUrl).host : null
31
+ }
32
+
33
+ export function isIndexableHost(host: string | null | undefined): boolean {
34
+ const indexable = getIndexableHost()
35
+ return indexable !== null && host?.toLowerCase() === indexable
36
+ }
@@ -1,5 +1,9 @@
1
1
  import withBundleAnalyzer from "@next/bundle-analyzer"
2
2
  import type { NextConfig } from "next"
3
+ import { getIndexableHost } from "./lib/utils/url"
4
+
5
+ // Only the launch domain is indexed; every other host is sent noindex.
6
+ const indexableHost = getIndexableHost()
3
7
 
4
8
  const nextConfig: NextConfig = {
5
9
  reactStrictMode: true,
@@ -92,6 +96,18 @@ const nextConfig: NextConfig = {
92
96
  imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
93
97
  },
94
98
  headers: async () => [
99
+ {
100
+ source: "/:path*",
101
+ ...(indexableHost && {
102
+ missing: [
103
+ {
104
+ type: "host" as const,
105
+ value: indexableHost.replaceAll(".", "\\."),
106
+ },
107
+ ],
108
+ }),
109
+ headers: [{ key: "X-Robots-Tag", value: "noindex" }],
110
+ },
95
111
  {
96
112
  // Excludes /studio* — must stay mutually exclusive with the rule below.
97
113
  source: "/((?!studio(?:/|$)).*)",