bsmnt 0.7.1 → 0.10.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/dist/application/add-hooks/index.d.ts.map +1 -1
- package/dist/application/add-hooks/index.js +1 -0
- package/dist/application/add-hooks/index.js.map +1 -1
- package/dist/application/add-integration/index.d.ts.map +1 -1
- package/dist/application/add-integration/index.js +20 -2
- package/dist/application/add-integration/index.js.map +1 -1
- package/dist/core/create/execute-plan.d.ts +2 -0
- package/dist/core/create/execute-plan.d.ts.map +1 -1
- package/dist/core/create/execute-plan.js +3 -0
- package/dist/core/create/execute-plan.js.map +1 -1
- package/dist/domain/version.d.ts +14 -0
- package/dist/domain/version.d.ts.map +1 -0
- package/dist/domain/version.js +81 -0
- package/dist/domain/version.js.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/create/executor.d.ts.map +1 -1
- package/dist/infrastructure/create/executor.js +2 -0
- package/dist/infrastructure/create/executor.js.map +1 -1
- package/dist/infrastructure/create/render-generated-docs.d.ts +18 -0
- package/dist/infrastructure/create/render-generated-docs.d.ts.map +1 -0
- package/dist/infrastructure/create/render-generated-docs.js +72 -0
- package/dist/infrastructure/create/render-generated-docs.js.map +1 -0
- package/dist/infrastructure/create/setup-dotenvx.d.ts.map +1 -1
- package/dist/infrastructure/create/setup-dotenvx.js +2 -1
- package/dist/infrastructure/create/setup-dotenvx.js.map +1 -1
- package/dist/infrastructure/create/setup-remote.d.ts +6 -1
- package/dist/infrastructure/create/setup-remote.d.ts.map +1 -1
- package/dist/infrastructure/create/setup-remote.js +7 -2
- package/dist/infrastructure/create/setup-remote.js.map +1 -1
- package/dist/infrastructure/integrations/ensure-studio-headers.d.ts +6 -0
- package/dist/infrastructure/integrations/ensure-studio-headers.d.ts.map +1 -0
- package/dist/infrastructure/integrations/ensure-studio-headers.js +153 -0
- package/dist/infrastructure/integrations/ensure-studio-headers.js.map +1 -0
- package/dist/infrastructure/update/cache.d.ts +14 -0
- package/dist/infrastructure/update/cache.d.ts.map +1 -0
- package/dist/infrastructure/update/cache.js +41 -0
- package/dist/infrastructure/update/cache.js.map +1 -0
- package/dist/infrastructure/update/check-version.d.ts +2 -0
- package/dist/infrastructure/update/check-version.d.ts.map +1 -0
- package/dist/infrastructure/update/check-version.js +33 -0
- package/dist/infrastructure/update/check-version.js.map +1 -0
- package/dist/infrastructure/update/notifier.d.ts +8 -0
- package/dist/infrastructure/update/notifier.d.ts.map +1 -0
- package/dist/infrastructure/update/notifier.js +141 -0
- package/dist/infrastructure/update/notifier.js.map +1 -0
- package/dist/modules/features/cms/sanity/config.d.ts.map +1 -1
- package/dist/modules/features/cms/sanity/config.js +2 -1
- package/dist/modules/features/cms/sanity/config.js.map +1 -1
- package/dist/modules/features/cms/sanity-pagebuilder/config.d.ts.map +1 -1
- package/dist/modules/features/cms/sanity-pagebuilder/config.js +4 -1
- package/dist/modules/features/cms/sanity-pagebuilder/config.js.map +1 -1
- package/package.json +2 -2
- package/src/modules/features/cms/sanity/files/app/api/blog/[slug]/route.ts +4 -1
- package/src/modules/features/cms/sanity/files/app/sitemap.md/route.ts +2 -1
- package/src/modules/features/cms/sanity/files/components/ui/sanity-image/index.tsx +19 -5
- package/src/modules/features/cms/sanity/files/lib/integrations/sanity/README.md +31 -0
- package/src/modules/features/cms/sanity/files/proxy.ts +9 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/app/api/[[...slug]]/route.ts +8 -3
- package/src/modules/features/cms/sanity-pagebuilder/files/app/sitemap.md/route.ts +7 -1
- package/src/modules/features/cms/sanity-pagebuilder/files/components/ui/sanity-image/index.tsx +15 -5
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/block-actions-item.tsx +81 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/slug-field.tsx +13 -7
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/slug-notices.tsx +135 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/icons.ts +2 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/live/index.tsx +1 -1
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/markdown-proxy.config.ts +1 -1
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/page-builder-config.ts +7 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/queries.ts +72 -21
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/index.ts +1 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/reusable/gated-content.ts +178 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/index.ts +9 -1
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/layout/redirect.ts +35 -4
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/metadata.ts +54 -1
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/page-builder.ts +4 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/page.ts +11 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/structure.ts +4 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/utils/page-builder-markdown.ts +20 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/metadata.ts +27 -5
- package/src/modules/features/cms/sanity-pagebuilder/files/proxy.ts +9 -0
- package/src/modules/features/env/dotenvx/files/setup-remote.ts +3 -1
- package/src/templates/next-default/.env.example +10 -0
- package/src/templates/next-default/.github/workflows/ci.yml +7 -0
- package/src/templates/next-default/README.md +14 -0
- package/src/templates/next-default/app/robots.ts +2 -3
- package/src/templates/next-default/knip.json +8 -6
- package/src/templates/next-default/lefthook.yml +0 -5
- package/src/templates/next-default/next.config.ts +36 -2
- package/src/templates/next-default/package.json +9 -9
- package/src/templates/next-experiments/.env.example +10 -0
- package/src/templates/next-experiments/.github/workflows/ci.yml +7 -0
- package/src/templates/next-experiments/README.md +14 -0
- package/src/templates/next-experiments/app/robots.ts +2 -3
- package/src/templates/next-experiments/knip.json +8 -6
- package/src/templates/next-experiments/lefthook.yml +0 -5
- package/src/templates/next-experiments/next.config.ts +36 -2
- package/src/templates/next-experiments/package.json +9 -9
- package/src/templates/next-pagebuilder/.env.example +10 -0
- package/src/templates/next-pagebuilder/.github/PULL_REQUEST_TEMPLATE.md +13 -0
- package/src/templates/next-pagebuilder/.github/workflows/ci.yml +15 -0
- package/src/templates/next-pagebuilder/README.md +45 -0
- package/src/templates/next-pagebuilder/app/(content)/[[...slug]]/page.tsx +26 -11
- package/src/templates/next-pagebuilder/app/actions/unlock-gated-content.ts +39 -0
- package/src/templates/next-pagebuilder/app/robots.ts +2 -3
- package/src/templates/next-pagebuilder/app/sitemap.xml/route.ts +6 -3
- package/src/templates/next-pagebuilder/components/page-builder/components/content-collection/skeleton.tsx +53 -0
- package/src/templates/next-pagebuilder/components/page-builder/components/gated-content/index.tsx +169 -0
- package/src/templates/next-pagebuilder/components/page-builder/renderer.tsx +10 -1
- package/src/templates/next-pagebuilder/knip.json +8 -6
- package/src/templates/next-pagebuilder/lefthook.yml +0 -5
- package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.types.ts +81 -0
- package/src/templates/next-pagebuilder/next.config.ts +33 -2
- package/src/templates/next-pagebuilder/package.json +4 -4
- package/src/templates/next-webgl/.env.example +10 -0
- package/src/templates/next-webgl/.github/workflows/ci.yml +7 -0
- package/src/templates/next-webgl/README.md +14 -0
- package/src/templates/next-webgl/app/robots.ts +2 -3
- package/src/templates/next-webgl/knip.json +8 -6
- package/src/templates/next-webgl/lefthook.yml +0 -5
- package/src/templates/next-webgl/next.config.ts +36 -2
- package/src/templates/next-webgl/package.json +9 -9
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# ============================================
|
|
2
2
|
# Basement Next Starter - Environment Variables
|
|
3
3
|
# ============================================
|
|
4
|
+
# bsmnt:if dotenvx
|
|
4
5
|
# This project uses dotenvx (https://dotenvx.com): the committed `.env` holds
|
|
5
6
|
# shared values, with secrets encrypted there. The private keys live in
|
|
6
7
|
# `.env.keys` (gitignored) — restore them with `bun run env:setup`.
|
|
@@ -15,6 +16,13 @@
|
|
|
15
16
|
#
|
|
16
17
|
# This file just documents the available variables; real values live in `.env`.
|
|
17
18
|
# ============================================
|
|
19
|
+
# bsmnt:endif
|
|
20
|
+
# bsmnt:if !dotenvx
|
|
21
|
+
# Copy this file to `.env.local` and fill in the values you need.
|
|
22
|
+
# `.env.local` is gitignored, so real values stay off version control.
|
|
23
|
+
# Only NEXT_PUBLIC_* variables are exposed to the browser.
|
|
24
|
+
# ============================================
|
|
25
|
+
# bsmnt:endif
|
|
18
26
|
|
|
19
27
|
|
|
20
28
|
# ============================================
|
|
@@ -24,6 +32,8 @@
|
|
|
24
32
|
# Base URL for your site (used for SEO, canonical URLs, etc.)
|
|
25
33
|
# Development: http://localhost:3000
|
|
26
34
|
# Production: https://your-domain.com
|
|
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.
|
|
27
37
|
NEXT_PUBLIC_BASE_URL=""
|
|
28
38
|
|
|
29
39
|
# Draft mode secret for preview functionality
|
|
@@ -46,6 +46,7 @@ jobs:
|
|
|
46
46
|
- name: Lint
|
|
47
47
|
run: bun run lint
|
|
48
48
|
|
|
49
|
+
# bsmnt:if dotenvx
|
|
49
50
|
# dotenvx needs the private key to decrypt secrets at build time. It's
|
|
50
51
|
# pulled from 1Password via an org-wide service-account token + the op-ref
|
|
51
52
|
# committed in .env. Optional: builds pass without it (only plaintext /
|
|
@@ -70,6 +71,12 @@ jobs:
|
|
|
70
71
|
fi
|
|
71
72
|
bun run build
|
|
72
73
|
|
|
74
|
+
# bsmnt:endif
|
|
75
|
+
# bsmnt:if !dotenvx
|
|
76
|
+
- name: Build
|
|
77
|
+
run: bun run build
|
|
78
|
+
|
|
79
|
+
# bsmnt:endif
|
|
73
80
|
# Runs your tests if any exist; a fresh project with none passes cleanly.
|
|
74
81
|
- name: Test
|
|
75
82
|
run: |
|
|
@@ -22,6 +22,7 @@ bun dev
|
|
|
22
22
|
| `bun format` | Format the codebase |
|
|
23
23
|
| `bun typecheck` | Run TypeScript |
|
|
24
24
|
| `bun analyze` | Analyze the Next.js bundle |
|
|
25
|
+
<!-- bsmnt:if dotenvx -->
|
|
25
26
|
| `bun env:set KEY value` | Encrypt one value into `.env` (`--plain`, `--preview`/`--prod`) |
|
|
26
27
|
| `bun env:encrypt-all` | Bulk-encrypt every plaintext value in a file (`--preview`/`--prod`) |
|
|
27
28
|
| `bun env:setup` | Restore `.env.keys` from 1Password |
|
|
@@ -29,7 +30,9 @@ bun dev
|
|
|
29
30
|
| `bun env:setup-remote` | Push private keys to GitHub / Vercel (`github`/`vercel`, or pick) |
|
|
30
31
|
| `bun setup-remote` | Create the GitHub repo, link a Vercel project, and push the key to Vercel env |
|
|
31
32
|
| `bun env:save` | Save the private key to 1Password (Development vault) |
|
|
33
|
+
<!-- bsmnt:endif -->
|
|
32
34
|
|
|
35
|
+
<!-- bsmnt:if dotenvx -->
|
|
33
36
|
## Environment variables
|
|
34
37
|
|
|
35
38
|
Secrets are encrypted at rest with [dotenvx](https://dotenvx.com): the committed `.env` holds encrypted values, the private keys live in `.env.keys` (gitignored), and dev/build run through `dotenvx run` so values decrypt automatically.
|
|
@@ -53,6 +56,15 @@ Secrets are encrypted at rest with [dotenvx](https://dotenvx.com): the committed
|
|
|
53
56
|
|
|
54
57
|
A gitleaks pre-commit hook + CI scan block committed plaintext secrets (install: `brew install gitleaks`).
|
|
55
58
|
|
|
59
|
+
<!-- bsmnt:endif -->
|
|
60
|
+
<!-- bsmnt:if !dotenvx -->
|
|
61
|
+
## Environment variables
|
|
62
|
+
|
|
63
|
+
Copy `.env.example` to `.env.local` and fill in the values you need. `.env.local` is gitignored, so it stays off version control. Only `NEXT_PUBLIC_*` variables are exposed to the browser — everything else is server-only.
|
|
64
|
+
|
|
65
|
+
For deploys, set the same variables in your host's dashboard (e.g. Vercel → Settings → Environment Variables).
|
|
66
|
+
|
|
67
|
+
<!-- bsmnt:endif -->
|
|
56
68
|
## Project Structure
|
|
57
69
|
|
|
58
70
|
```txt
|
|
@@ -90,8 +102,10 @@ The workflow takes its runtime versions from your manifests, so there's a single
|
|
|
90
102
|
|
|
91
103
|
It also caches Bun's install cache between runs, and the test step is a no-op until you add real test files.
|
|
92
104
|
|
|
105
|
+
<!-- bsmnt:if dotenvx -->
|
|
93
106
|
The build decrypts the committed `.env` with the dotenvx private key, pulled from 1Password: set an **org-level** `OP_SERVICE_ACCOUNT_TOKEN` (a read-only [service account](https://developer.1password.com/docs/service-accounts/) scoped to the `Development` vault) — one secret for every repo — and CI reads the key via the op-ref committed in `.env`. A per-repo `DOTENV_PRIVATE_KEY` secret (`bun run env:setup-remote github`) works as a fallback; builds pass without either (only plaintext / `NEXT_PUBLIC_*` is read).
|
|
94
107
|
|
|
108
|
+
<!-- bsmnt:endif -->
|
|
95
109
|
### Branch protection
|
|
96
110
|
|
|
97
111
|
To block merges when CI fails, enable branch protection on `main`:
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { MetadataRoute } from "next"
|
|
2
|
-
|
|
3
|
-
const APP_BASE_URL = process.env.NEXT_PUBLIC_BASE_URL ?? "http://localhost:3000"
|
|
2
|
+
import { getBaseUrl } from "@/lib/utils/url"
|
|
4
3
|
|
|
5
4
|
export default function robots(): MetadataRoute.Robots {
|
|
6
5
|
return {
|
|
@@ -9,6 +8,6 @@ export default function robots(): MetadataRoute.Robots {
|
|
|
9
8
|
allow: "/",
|
|
10
9
|
disallow: [],
|
|
11
10
|
},
|
|
12
|
-
sitemap: `${
|
|
11
|
+
sitemap: `${getBaseUrl()}/sitemap.xml`,
|
|
13
12
|
}
|
|
14
13
|
}
|
|
@@ -12,11 +12,13 @@
|
|
|
12
12
|
"@svgr/webpack",
|
|
13
13
|
"gsap",
|
|
14
14
|
"@gsap/react",
|
|
15
|
-
"motion"
|
|
15
|
+
"motion",
|
|
16
|
+
"detect-gpu",
|
|
17
|
+
"lodash-es",
|
|
18
|
+
"@types/lodash-es",
|
|
19
|
+
"react-device-detect",
|
|
20
|
+
"zustand"
|
|
16
21
|
],
|
|
17
|
-
"ignoreBinaries": [
|
|
18
|
-
|
|
19
|
-
"gh",
|
|
20
|
-
"vercel"
|
|
21
|
-
]
|
|
22
|
+
"ignoreBinaries": ["op", "gh", "vercel"],
|
|
23
|
+
"ignoreFiles": [".agents/**", ".claude/**", "hooks/**"]
|
|
22
24
|
}
|
|
@@ -64,8 +64,10 @@ const nextConfig: NextConfig = {
|
|
|
64
64
|
},
|
|
65
65
|
},
|
|
66
66
|
},
|
|
67
|
+
logging: {
|
|
68
|
+
browserToTerminal: true,
|
|
69
|
+
},
|
|
67
70
|
experimental: {
|
|
68
|
-
browserDebugInfoInTerminal: true,
|
|
69
71
|
optimizePackageImports: [
|
|
70
72
|
"@react-three/drei",
|
|
71
73
|
"@react-three/fiber",
|
|
@@ -86,7 +88,8 @@ const nextConfig: NextConfig = {
|
|
|
86
88
|
},
|
|
87
89
|
headers: async () => [
|
|
88
90
|
{
|
|
89
|
-
|
|
91
|
+
// Excludes /studio* — must stay mutually exclusive with the rule below.
|
|
92
|
+
source: "/((?!studio(?:/|$)).*)",
|
|
90
93
|
headers: [
|
|
91
94
|
{
|
|
92
95
|
key: "X-Content-Type-Options",
|
|
@@ -118,6 +121,37 @@ const nextConfig: NextConfig = {
|
|
|
118
121
|
},
|
|
119
122
|
],
|
|
120
123
|
},
|
|
124
|
+
{
|
|
125
|
+
// X-Frame-Options can't allow a third-party origin, and Next can't unset
|
|
126
|
+
// it from another rule — the iframe needs it absent, not overridden.
|
|
127
|
+
source: "/studio/:path*",
|
|
128
|
+
headers: [
|
|
129
|
+
{
|
|
130
|
+
key: "X-Content-Type-Options",
|
|
131
|
+
value: "nosniff",
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
key: "Content-Security-Policy",
|
|
135
|
+
value: "frame-ancestors 'self' https://*.sanity.io;",
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
key: "X-XSS-Protection",
|
|
139
|
+
value: "1; mode=block",
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
key: "X-DNS-Prefetch-Control",
|
|
143
|
+
value: "on",
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
key: "Strict-Transport-Security",
|
|
147
|
+
value: "max-age=63072000; includeSubDomains; preload",
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
key: "Permissions-Policy",
|
|
151
|
+
value: "camera=(), microphone=(), geolocation=()",
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
},
|
|
121
155
|
],
|
|
122
156
|
redirects: async () => [
|
|
123
157
|
{
|
|
@@ -30,30 +30,30 @@
|
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"class-variance-authority": "^0.7.1",
|
|
32
32
|
"clsx": "^2.1.1",
|
|
33
|
-
"next": "^16",
|
|
34
|
-
"react": "^19",
|
|
35
|
-
"react-dom": "^19",
|
|
33
|
+
"next": "^16.3.0",
|
|
34
|
+
"react": "^19.2.8",
|
|
35
|
+
"react-dom": "^19.2.8",
|
|
36
36
|
"tailwind-merge": "^3.4.0",
|
|
37
37
|
"zod": "^4.3.6"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@biomejs/biome": "^2.4.14",
|
|
41
|
-
"@next/bundle-analyzer": "^16",
|
|
41
|
+
"@next/bundle-analyzer": "^16.3.0",
|
|
42
42
|
"@svgr/webpack": "^8.1.0",
|
|
43
|
-
"@tailwindcss/postcss": "^4",
|
|
43
|
+
"@tailwindcss/postcss": "^4.2.2",
|
|
44
44
|
"@types/bun": "^1.3.6",
|
|
45
45
|
"@types/node": "^24.13.1",
|
|
46
|
-
"@types/react": "^19.2.
|
|
46
|
+
"@types/react": "^19.2.14",
|
|
47
47
|
"@types/react-dom": "^19.2.3",
|
|
48
|
-
"@typescript/native-preview": "^7.0.0-dev.
|
|
48
|
+
"@typescript/native-preview": "^7.0.0-dev.20260323.1",
|
|
49
49
|
"babel-plugin-react-compiler": "^1.0.0",
|
|
50
50
|
"cross-env": "^10.1.0",
|
|
51
51
|
"knip": "^6.16.1",
|
|
52
52
|
"lefthook": "^1.13.0",
|
|
53
53
|
"postcss-preset-env": "^10.6.1",
|
|
54
54
|
"schema-dts": "^2.0.0",
|
|
55
|
-
"tailwindcss": "^4",
|
|
56
|
-
"typescript": "^5"
|
|
55
|
+
"tailwindcss": "^4.2.2",
|
|
56
|
+
"typescript": "^5.9.3"
|
|
57
57
|
},
|
|
58
58
|
"trustedDependencies": [
|
|
59
59
|
"@biomejs/biome",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# ============================================
|
|
2
2
|
# Basement Next Starter - Environment Variables
|
|
3
3
|
# ============================================
|
|
4
|
+
# bsmnt:if dotenvx
|
|
4
5
|
# This project uses dotenvx (https://dotenvx.com): the committed `.env` holds
|
|
5
6
|
# shared values, with secrets encrypted there. The private keys live in
|
|
6
7
|
# `.env.keys` (gitignored) — restore them with `bun run env:setup`.
|
|
@@ -15,6 +16,13 @@
|
|
|
15
16
|
#
|
|
16
17
|
# This file just documents the available variables; real values live in `.env`.
|
|
17
18
|
# ============================================
|
|
19
|
+
# bsmnt:endif
|
|
20
|
+
# bsmnt:if !dotenvx
|
|
21
|
+
# Copy this file to `.env.local` and fill in the values you need.
|
|
22
|
+
# `.env.local` is gitignored, so real values stay off version control.
|
|
23
|
+
# Only NEXT_PUBLIC_* variables are exposed to the browser.
|
|
24
|
+
# ============================================
|
|
25
|
+
# bsmnt:endif
|
|
18
26
|
|
|
19
27
|
|
|
20
28
|
# ============================================
|
|
@@ -24,6 +32,8 @@
|
|
|
24
32
|
# Base URL for your site (used for SEO, canonical URLs, etc.)
|
|
25
33
|
# Development: http://localhost:3000
|
|
26
34
|
# Production: https://your-domain.com
|
|
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.
|
|
27
37
|
NEXT_PUBLIC_BASE_URL=""
|
|
28
38
|
|
|
29
39
|
# Draft mode secret for preview functionality
|
|
@@ -46,6 +46,7 @@ jobs:
|
|
|
46
46
|
- name: Lint
|
|
47
47
|
run: bun run lint
|
|
48
48
|
|
|
49
|
+
# bsmnt:if dotenvx
|
|
49
50
|
# dotenvx needs the private key to decrypt secrets at build time. It's
|
|
50
51
|
# pulled from 1Password via an org-wide service-account token + the op-ref
|
|
51
52
|
# committed in .env. Optional: builds pass without it (only plaintext /
|
|
@@ -70,6 +71,12 @@ jobs:
|
|
|
70
71
|
fi
|
|
71
72
|
bun run build
|
|
72
73
|
|
|
74
|
+
# bsmnt:endif
|
|
75
|
+
# bsmnt:if !dotenvx
|
|
76
|
+
- name: Build
|
|
77
|
+
run: bun run build
|
|
78
|
+
|
|
79
|
+
# bsmnt:endif
|
|
73
80
|
# Runs your tests if any exist; a fresh project with none passes cleanly.
|
|
74
81
|
- name: Test
|
|
75
82
|
run: |
|
|
@@ -22,6 +22,7 @@ bun dev
|
|
|
22
22
|
| `bun format` | Format the codebase |
|
|
23
23
|
| `bun typecheck` | Run TypeScript |
|
|
24
24
|
| `bun analyze` | Analyze the Next.js bundle |
|
|
25
|
+
<!-- bsmnt:if dotenvx -->
|
|
25
26
|
| `bun env:set KEY value` | Encrypt one value into `.env` (`--plain`, `--preview`/`--prod`) |
|
|
26
27
|
| `bun env:encrypt-all` | Bulk-encrypt every plaintext value in a file (`--preview`/`--prod`) |
|
|
27
28
|
| `bun env:setup` | Restore `.env.keys` from 1Password |
|
|
@@ -29,7 +30,9 @@ bun dev
|
|
|
29
30
|
| `bun env:setup-remote` | Push private keys to GitHub / Vercel (`github`/`vercel`, or pick) |
|
|
30
31
|
| `bun setup-remote` | Create the GitHub repo, link a Vercel project, and push the key to Vercel env |
|
|
31
32
|
| `bun env:save` | Save the private key to 1Password (Development vault) |
|
|
33
|
+
<!-- bsmnt:endif -->
|
|
32
34
|
|
|
35
|
+
<!-- bsmnt:if dotenvx -->
|
|
33
36
|
## Environment variables
|
|
34
37
|
|
|
35
38
|
Secrets are encrypted at rest with [dotenvx](https://dotenvx.com): the committed `.env` holds encrypted values, the private keys live in `.env.keys` (gitignored), and dev/build run through `dotenvx run` so values decrypt automatically.
|
|
@@ -53,6 +56,15 @@ Secrets are encrypted at rest with [dotenvx](https://dotenvx.com): the committed
|
|
|
53
56
|
|
|
54
57
|
A gitleaks pre-commit hook + CI scan block committed plaintext secrets (install: `brew install gitleaks`).
|
|
55
58
|
|
|
59
|
+
<!-- bsmnt:endif -->
|
|
60
|
+
<!-- bsmnt:if !dotenvx -->
|
|
61
|
+
## Environment variables
|
|
62
|
+
|
|
63
|
+
Copy `.env.example` to `.env.local` and fill in the values you need. `.env.local` is gitignored, so it stays off version control. Only `NEXT_PUBLIC_*` variables are exposed to the browser — everything else is server-only.
|
|
64
|
+
|
|
65
|
+
For deploys, set the same variables in your host's dashboard (e.g. Vercel → Settings → Environment Variables).
|
|
66
|
+
|
|
67
|
+
<!-- bsmnt:endif -->
|
|
56
68
|
## Project Structure
|
|
57
69
|
|
|
58
70
|
```txt
|
|
@@ -90,8 +102,10 @@ The workflow takes its runtime versions from your manifests, so there's a single
|
|
|
90
102
|
|
|
91
103
|
It also caches Bun's install cache between runs, and the test step is a no-op until you add real test files.
|
|
92
104
|
|
|
105
|
+
<!-- bsmnt:if dotenvx -->
|
|
93
106
|
The build decrypts the committed `.env` with the dotenvx private key, pulled from 1Password: set an **org-level** `OP_SERVICE_ACCOUNT_TOKEN` (a read-only [service account](https://developer.1password.com/docs/service-accounts/) scoped to the `Development` vault) — one secret for every repo — and CI reads the key via the op-ref committed in `.env`. A per-repo `DOTENV_PRIVATE_KEY` secret (`bun run env:setup-remote github`) works as a fallback; builds pass without either (only plaintext / `NEXT_PUBLIC_*` is read).
|
|
94
107
|
|
|
108
|
+
<!-- bsmnt:endif -->
|
|
95
109
|
### Branch protection
|
|
96
110
|
|
|
97
111
|
To block merges when CI fails, enable branch protection on `main`:
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { MetadataRoute } from "next"
|
|
2
|
-
|
|
3
|
-
const APP_BASE_URL = process.env.NEXT_PUBLIC_BASE_URL ?? "http://localhost:3000"
|
|
2
|
+
import { getBaseUrl } from "@/lib/utils/url"
|
|
4
3
|
|
|
5
4
|
export default function robots(): MetadataRoute.Robots {
|
|
6
5
|
return {
|
|
@@ -9,6 +8,6 @@ export default function robots(): MetadataRoute.Robots {
|
|
|
9
8
|
allow: "/",
|
|
10
9
|
disallow: [],
|
|
11
10
|
},
|
|
12
|
-
sitemap: `${
|
|
11
|
+
sitemap: `${getBaseUrl()}/sitemap.xml`,
|
|
13
12
|
}
|
|
14
13
|
}
|
|
@@ -12,11 +12,13 @@
|
|
|
12
12
|
"@svgr/webpack",
|
|
13
13
|
"gsap",
|
|
14
14
|
"@gsap/react",
|
|
15
|
-
"motion"
|
|
15
|
+
"motion",
|
|
16
|
+
"detect-gpu",
|
|
17
|
+
"lodash-es",
|
|
18
|
+
"@types/lodash-es",
|
|
19
|
+
"react-device-detect",
|
|
20
|
+
"zustand"
|
|
16
21
|
],
|
|
17
|
-
"ignoreBinaries": [
|
|
18
|
-
|
|
19
|
-
"gh",
|
|
20
|
-
"vercel"
|
|
21
|
-
]
|
|
22
|
+
"ignoreBinaries": ["op", "gh", "vercel"],
|
|
23
|
+
"ignoreFiles": [".agents/**", ".claude/**", "hooks/**"]
|
|
22
24
|
}
|
|
@@ -64,8 +64,10 @@ const nextConfig: NextConfig = {
|
|
|
64
64
|
},
|
|
65
65
|
},
|
|
66
66
|
},
|
|
67
|
+
logging: {
|
|
68
|
+
browserToTerminal: true,
|
|
69
|
+
},
|
|
67
70
|
experimental: {
|
|
68
|
-
browserDebugInfoInTerminal: true,
|
|
69
71
|
optimizePackageImports: [
|
|
70
72
|
"@react-three/drei",
|
|
71
73
|
"@react-three/fiber",
|
|
@@ -86,7 +88,8 @@ const nextConfig: NextConfig = {
|
|
|
86
88
|
},
|
|
87
89
|
headers: async () => [
|
|
88
90
|
{
|
|
89
|
-
|
|
91
|
+
// Excludes /studio* — must stay mutually exclusive with the rule below.
|
|
92
|
+
source: "/((?!studio(?:/|$)).*)",
|
|
90
93
|
headers: [
|
|
91
94
|
{
|
|
92
95
|
key: "X-Content-Type-Options",
|
|
@@ -118,6 +121,37 @@ const nextConfig: NextConfig = {
|
|
|
118
121
|
},
|
|
119
122
|
],
|
|
120
123
|
},
|
|
124
|
+
{
|
|
125
|
+
// X-Frame-Options can't allow a third-party origin, and Next can't unset
|
|
126
|
+
// it from another rule — the iframe needs it absent, not overridden.
|
|
127
|
+
source: "/studio/:path*",
|
|
128
|
+
headers: [
|
|
129
|
+
{
|
|
130
|
+
key: "X-Content-Type-Options",
|
|
131
|
+
value: "nosniff",
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
key: "Content-Security-Policy",
|
|
135
|
+
value: "frame-ancestors 'self' https://*.sanity.io;",
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
key: "X-XSS-Protection",
|
|
139
|
+
value: "1; mode=block",
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
key: "X-DNS-Prefetch-Control",
|
|
143
|
+
value: "on",
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
key: "Strict-Transport-Security",
|
|
147
|
+
value: "max-age=63072000; includeSubDomains; preload",
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
key: "Permissions-Policy",
|
|
151
|
+
value: "camera=(), microphone=(), geolocation=()",
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
},
|
|
121
155
|
],
|
|
122
156
|
redirects: async () => [
|
|
123
157
|
{
|
|
@@ -31,31 +31,31 @@
|
|
|
31
31
|
"class-variance-authority": "^0.7.1",
|
|
32
32
|
"clsx": "^2.1.1",
|
|
33
33
|
"lucide-react": "^0.474.0",
|
|
34
|
-
"next": "^16",
|
|
35
|
-
"react": "^19",
|
|
36
|
-
"react-dom": "^19",
|
|
34
|
+
"next": "^16.3.0",
|
|
35
|
+
"react": "^19.2.8",
|
|
36
|
+
"react-dom": "^19.2.8",
|
|
37
37
|
"tailwind-merge": "^3.4.0",
|
|
38
38
|
"three": "^0.182.0",
|
|
39
39
|
"zod": "^4.3.6"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@biomejs/biome": "^2.4.14",
|
|
43
|
-
"@next/bundle-analyzer": "^16",
|
|
43
|
+
"@next/bundle-analyzer": "^16.3.0",
|
|
44
44
|
"@svgr/webpack": "^8.1.0",
|
|
45
|
-
"@tailwindcss/postcss": "^4",
|
|
45
|
+
"@tailwindcss/postcss": "^4.2.2",
|
|
46
46
|
"@types/bun": "^1.3.6",
|
|
47
47
|
"@types/node": "^24.13.1",
|
|
48
|
-
"@types/react": "^19.2.
|
|
48
|
+
"@types/react": "^19.2.14",
|
|
49
49
|
"@types/react-dom": "^19.2.3",
|
|
50
|
-
"@typescript/native-preview": "^7.0.0-dev.
|
|
50
|
+
"@typescript/native-preview": "^7.0.0-dev.20260323.1",
|
|
51
51
|
"babel-plugin-react-compiler": "^1.0.0",
|
|
52
52
|
"cross-env": "^10.1.0",
|
|
53
53
|
"knip": "^6.16.1",
|
|
54
54
|
"lefthook": "^1.13.0",
|
|
55
55
|
"postcss-preset-env": "^10.6.1",
|
|
56
56
|
"schema-dts": "^2.0.0",
|
|
57
|
-
"tailwindcss": "^4",
|
|
58
|
-
"typescript": "^5"
|
|
57
|
+
"tailwindcss": "^4.2.2",
|
|
58
|
+
"typescript": "^5.9.3"
|
|
59
59
|
},
|
|
60
60
|
"trustedDependencies": [
|
|
61
61
|
"@biomejs/biome",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# ============================================
|
|
2
2
|
# Basement Next Starter - Environment Variables
|
|
3
3
|
# ============================================
|
|
4
|
+
# bsmnt:if dotenvx
|
|
4
5
|
# This project uses dotenvx (https://dotenvx.com): the committed `.env` holds
|
|
5
6
|
# shared values, with secrets encrypted there. The private keys live in
|
|
6
7
|
# `.env.keys` (gitignored) — restore them with `bun run env:setup`.
|
|
@@ -15,8 +16,17 @@
|
|
|
15
16
|
#
|
|
16
17
|
# This file just documents the available variables; real values live in `.env`.
|
|
17
18
|
# ============================================
|
|
19
|
+
# bsmnt:endif
|
|
20
|
+
# bsmnt:if !dotenvx
|
|
21
|
+
# Copy this file to `.env.local` and fill in the values you need.
|
|
22
|
+
# `.env.local` is gitignored, so real values stay off version control.
|
|
23
|
+
# Only NEXT_PUBLIC_* variables are exposed to the browser.
|
|
24
|
+
# ============================================
|
|
25
|
+
# bsmnt:endif
|
|
18
26
|
|
|
19
27
|
# Base URL
|
|
28
|
+
# Inlined at build time — scope this to the Production environment only in
|
|
29
|
+
# Vercel so Preview deployments fall through to VERCEL_PROJECT_PRODUCTION_URL.
|
|
20
30
|
NEXT_PUBLIC_BASE_URL=
|
|
21
31
|
|
|
22
32
|
# Draft Mode
|
|
@@ -46,6 +46,7 @@ jobs:
|
|
|
46
46
|
- name: Lint
|
|
47
47
|
run: bun run lint
|
|
48
48
|
|
|
49
|
+
# bsmnt:if dotenvx
|
|
49
50
|
# dotenvx needs the private key to decrypt secrets at build time. It's
|
|
50
51
|
# pulled from 1Password via an org-wide service-account token + the op-ref
|
|
51
52
|
# committed in .env. Optional: builds pass without it (only plaintext /
|
|
@@ -77,6 +78,20 @@ jobs:
|
|
|
77
78
|
fi
|
|
78
79
|
bun run build
|
|
79
80
|
|
|
81
|
+
# bsmnt:endif
|
|
82
|
+
# bsmnt:if !dotenvx
|
|
83
|
+
- name: Build
|
|
84
|
+
env:
|
|
85
|
+
# Sanity project ID and dataset are public (they ship in the client
|
|
86
|
+
# bundle), so they are stored as repository variables. The Sanity read
|
|
87
|
+
# token is secret — add it under Settings → Secrets and variables →
|
|
88
|
+
# Actions. All three are empty (and harmless) until Sanity is set up.
|
|
89
|
+
NEXT_PUBLIC_SANITY_PROJECT_ID: ${{ vars.NEXT_PUBLIC_SANITY_PROJECT_ID }}
|
|
90
|
+
NEXT_PUBLIC_SANITY_DATASET: ${{ vars.NEXT_PUBLIC_SANITY_DATASET }}
|
|
91
|
+
SANITY_API_READ_TOKEN: ${{ secrets.SANITY_API_READ_TOKEN }}
|
|
92
|
+
run: bun run build
|
|
93
|
+
|
|
94
|
+
# bsmnt:endif
|
|
80
95
|
# Runs your tests if any exist; a fresh project with none passes cleanly.
|
|
81
96
|
- name: Test
|
|
82
97
|
run: |
|
|
@@ -21,6 +21,7 @@ bun dev
|
|
|
21
21
|
- `bun build` - Production build
|
|
22
22
|
- `bun lint` - Run linter
|
|
23
23
|
- `bun sanity:typegen` - Generate Sanity types
|
|
24
|
+
<!-- bsmnt:if dotenvx -->
|
|
24
25
|
- `bun env:set KEY value` - Encrypt one value into `.env` (`--plain`, `--preview`/`--prod`)
|
|
25
26
|
- `bun env:encrypt-all` - Bulk-encrypt every plaintext value in a file (`--preview`/`--prod`)
|
|
26
27
|
- `bun env:setup` - Restore `.env.keys` from 1Password
|
|
@@ -28,7 +29,9 @@ bun dev
|
|
|
28
29
|
- `bun env:setup-remote` - Push private keys to GitHub / Vercel (`github`/`vercel`, or pick)
|
|
29
30
|
- `bun setup-remote` - Create the GitHub repo, link a Vercel project, and push the key to Vercel env
|
|
30
31
|
- `bun env:save` - Save the private key to 1Password (Development vault)
|
|
32
|
+
<!-- bsmnt:endif -->
|
|
31
33
|
|
|
34
|
+
<!-- bsmnt:if dotenvx -->
|
|
32
35
|
## Environment variables
|
|
33
36
|
|
|
34
37
|
Secrets are encrypted at rest with [dotenvx](https://dotenvx.com): the committed `.env` holds encrypted values, the private keys live in `.env.keys` (gitignored), and dev/build run through `dotenvx run` so values decrypt automatically.
|
|
@@ -52,6 +55,46 @@ Secrets are encrypted at rest with [dotenvx](https://dotenvx.com): the committed
|
|
|
52
55
|
|
|
53
56
|
A gitleaks pre-commit hook + CI scan block committed plaintext secrets (install: `brew install gitleaks`).
|
|
54
57
|
|
|
58
|
+
<!-- bsmnt:endif -->
|
|
59
|
+
<!-- bsmnt:if !dotenvx -->
|
|
60
|
+
## Environment variables
|
|
61
|
+
|
|
62
|
+
Copy `.env.example` to `.env.local` and fill in the values you need. `.env.local` is gitignored, so it stays off version control. Only `NEXT_PUBLIC_*` variables are exposed to the browser — everything else is server-only.
|
|
63
|
+
|
|
64
|
+
For deploys, set the same variables in your host's dashboard (e.g. Vercel → Settings → Environment Variables).
|
|
65
|
+
|
|
66
|
+
<!-- bsmnt:endif -->
|
|
67
|
+
## Sanity Dashboard (Studio in an iframe)
|
|
68
|
+
|
|
69
|
+
The Studio at `/studio` can be embedded in the Sanity Dashboard
|
|
70
|
+
(`https://www.sanity.io/@<org>/studio/<appId>/<workspace>`), which loads it in an
|
|
71
|
+
iframe from `www.sanity.io`. Three things have to be true for that to work:
|
|
72
|
+
|
|
73
|
+
1. **Headers.** `next.config.ts` already exempts `/studio/:path*` from the
|
|
74
|
+
site-wide `X-Frame-Options` / `frame-ancestors 'self'` headers, sending
|
|
75
|
+
`frame-ancestors 'self' https://*.sanity.io` there instead and dropping
|
|
76
|
+
`X-Frame-Options` entirely for that path (it has no "allow this origin"
|
|
77
|
+
form — it must be absent, not overridden). Nothing to do here unless
|
|
78
|
+
you've hand-edited `headers()`.
|
|
79
|
+
2. **Bridge script.** Already wired in: `next-sanity/studio`'s `NextStudio`
|
|
80
|
+
re-exports `NextStudioWithBridge`, which renders the
|
|
81
|
+
`core.sanity-cdn.com/bridge.js` tag the Dashboard needs. Don't hand-roll
|
|
82
|
+
that script tag, and don't switch the Studio route to
|
|
83
|
+
`next-sanity/studio/client-component` (the bridge-less variant).
|
|
84
|
+
3. **Registered URL.** In [Sanity Manage](https://sanity.io/manage), the
|
|
85
|
+
Studio application's URL must be the **full path**
|
|
86
|
+
(`https://example.com/studio`), not just the domain. This can't be set
|
|
87
|
+
from code — either click through Manage, or run
|
|
88
|
+
`sanity deploy --external --url https://example.com/studio --yes` from
|
|
89
|
+
`lib/integrations/sanity`. Writing it via the API needs the
|
|
90
|
+
`sanity.project.deployStudio` grant, which read/write tokens don't have.
|
|
91
|
+
|
|
92
|
+
Verify against a production build (`bun build && bun start`), not dev. One trap:
|
|
93
|
+
the Dashboard reports `Failed to load iframe due to X-Frame-Options: SAMEORIGIN`
|
|
94
|
+
for *any* framing failure, including a wrong registered URL with already-correct
|
|
95
|
+
headers. Trust the browser console's `Framing '<url>' violates …` line instead —
|
|
96
|
+
it names the URL actually being framed.
|
|
97
|
+
|
|
55
98
|
## Continuous Integration
|
|
56
99
|
|
|
57
100
|
A GitHub Actions workflow (`.github/workflows/ci.yml`) runs on every pull request to `main` and on pushes to `main`. It runs type checking, linting, the production build, tests, and `knip`.
|
|
@@ -63,8 +106,10 @@ The workflow takes its runtime versions from your manifests, so there's a single
|
|
|
63
106
|
|
|
64
107
|
It also caches Bun's install cache between runs, and the test step is a no-op until you add real test files.
|
|
65
108
|
|
|
109
|
+
<!-- bsmnt:if dotenvx -->
|
|
66
110
|
The build decrypts the committed `.env` with the dotenvx private key, pulled from 1Password: set an **org-level** `OP_SERVICE_ACCOUNT_TOKEN` (a read-only [service account](https://developer.1password.com/docs/service-accounts/) scoped to the `Development` vault) — one secret for every repo — and CI reads the key via the op-ref committed in `.env`. A per-repo `DOTENV_PRIVATE_KEY` secret (`bun run env:setup-remote github`) works as a fallback; builds pass without either (only plaintext / `NEXT_PUBLIC_*` is read).
|
|
67
111
|
|
|
112
|
+
<!-- bsmnt:endif -->
|
|
68
113
|
### Sanity environment
|
|
69
114
|
|
|
70
115
|
The build reads Sanity config from GitHub Actions variables/secrets (empty and harmless until you configure Sanity). Once your repo is on GitHub, the quickest way to set them is:
|