bsmnt 0.7.0 → 0.9.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 +1 -0
- 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 +10 -4
- 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/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-pagebuilder/config.d.ts.map +1 -1
- package/dist/modules/features/cms/sanity-pagebuilder/config.js +2 -0
- package/dist/modules/features/cms/sanity-pagebuilder/config.js.map +1 -1
- package/package.json +2 -1
- package/src/agent-skills/manage-env/SKILL.md +5 -3
- package/src/modules/features/cms/sanity/files/app/blog/[slug]/page.tsx +10 -9
- package/src/modules/features/cms/sanity/files/app/blog/page.tsx +5 -5
- package/src/modules/features/cms/sanity/files/lib/scripts/copy-sanity-mcp.ts +6 -6
- 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/icons.ts +2 -0
- 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/page-tree-pane.tsx +4 -6
- 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 +5 -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/scripts/sanity-typegen.ts +1 -1
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/json-ld.tsx +1 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/metadata.ts +27 -5
- package/src/modules/features/env/dotenvx/files/decrypt-env.mjs +12 -12
- package/src/modules/features/env/dotenvx/files/push-keys.mjs +3 -3
- package/src/modules/features/env/dotenvx/files/save-key.mjs +3 -3
- package/src/modules/features/env/dotenvx/files/setup-remote.ts +3 -1
- package/src/modules/features/env/dotenvx/files/write-env.mjs +6 -6
- package/src/modules/features/forms/hubspot/files/lib/integrations/hubspot/hubspot-form.css +2 -0
- package/src/templates/next-default/.env.example +8 -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/biome.json +1 -1
- package/src/templates/next-default/components/basement.svg +1 -1
- package/src/templates/next-default/knip.json +8 -6
- package/src/templates/next-default/lefthook.yml +0 -5
- package/src/templates/next-experiments/.env.example +8 -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/biome.json +1 -1
- package/src/templates/next-experiments/components/basement.svg +1 -1
- package/src/templates/next-experiments/knip.json +8 -6
- package/src/templates/next-experiments/lefthook.yml +0 -5
- package/src/templates/next-pagebuilder/.env.example +8 -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 +14 -0
- package/src/templates/next-pagebuilder/app/(content)/[[...slug]]/page.tsx +2 -2
- package/src/templates/next-pagebuilder/app/actions/unlock-gated-content.ts +39 -0
- package/src/templates/next-pagebuilder/biome.json +1 -1
- 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 +3 -0
- 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-webgl/.env.example +8 -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/biome.json +1 -1
- package/src/templates/next-webgl/components/basement.svg +1 -1
- package/src/templates/next-webgl/knip.json +8 -6
- package/src/templates/next-webgl/lefthook.yml +0 -5
|
@@ -6,6 +6,9 @@ interface GenerateMetadataOptions {
|
|
|
6
6
|
title?: string | undefined;
|
|
7
7
|
description?: string | undefined;
|
|
8
8
|
keywords?: string[] | undefined;
|
|
9
|
+
ogTitle?: string | undefined;
|
|
10
|
+
ogDescription?: string | undefined;
|
|
11
|
+
canonicalUrl?: string | undefined;
|
|
9
12
|
image?:
|
|
10
13
|
| {
|
|
11
14
|
url?: string | undefined;
|
|
@@ -33,6 +36,10 @@ interface SanityMetadataImage {
|
|
|
33
36
|
interface SanityPageMetadata {
|
|
34
37
|
metaTitle?: string | null;
|
|
35
38
|
metaDescription?: string | null;
|
|
39
|
+
keywords?: string[] | null;
|
|
40
|
+
ogTitle?: string | null;
|
|
41
|
+
ogDescription?: string | null;
|
|
42
|
+
canonicalUrl?: string | null;
|
|
36
43
|
og?: {
|
|
37
44
|
image?: SanityMetadataImage | null;
|
|
38
45
|
} | null;
|
|
@@ -81,6 +88,9 @@ const generatePageMetadata = (options: GenerateMetadataOptions): Metadata => {
|
|
|
81
88
|
title,
|
|
82
89
|
description,
|
|
83
90
|
keywords,
|
|
91
|
+
ogTitle,
|
|
92
|
+
ogDescription,
|
|
93
|
+
canonicalUrl,
|
|
84
94
|
image,
|
|
85
95
|
url,
|
|
86
96
|
siteName = "Basement",
|
|
@@ -98,29 +108,33 @@ const generatePageMetadata = (options: GenerateMetadataOptions): Metadata => {
|
|
|
98
108
|
const width = image?.width || 1200;
|
|
99
109
|
const height = image?.height || 630;
|
|
100
110
|
|
|
111
|
+
// Social title/description fall back to the SEO meta title/description.
|
|
112
|
+
const socialTitle = ogTitle || title;
|
|
113
|
+
const socialDescription = ogDescription || description;
|
|
114
|
+
|
|
101
115
|
const metadata: Metadata = {
|
|
102
116
|
metadataBase: new URL(APP_BASE_URL),
|
|
103
117
|
title,
|
|
104
118
|
description,
|
|
105
119
|
keywords,
|
|
106
|
-
alternates: { canonical: url || "/" },
|
|
120
|
+
alternates: { canonical: canonicalUrl || url || "/" },
|
|
107
121
|
openGraph: {
|
|
108
|
-
|
|
109
|
-
description,
|
|
122
|
+
description: socialDescription,
|
|
110
123
|
url: fullUrl,
|
|
111
124
|
siteName,
|
|
112
125
|
locale: "en_US",
|
|
113
126
|
type,
|
|
114
127
|
images: [{ url: imageUrl, width, height }],
|
|
128
|
+
...(socialTitle && { title: socialTitle }),
|
|
115
129
|
...(publishedTime && { publishedTime }),
|
|
116
130
|
...(modifiedTime && { modifiedTime }),
|
|
117
131
|
...(authors && { authors }),
|
|
118
132
|
},
|
|
119
133
|
twitter: {
|
|
120
134
|
card: "summary_large_image",
|
|
121
|
-
|
|
122
|
-
description,
|
|
135
|
+
description: socialDescription,
|
|
123
136
|
images: [{ url: imageUrl, width, height }],
|
|
137
|
+
...(socialTitle && { title: socialTitle }),
|
|
124
138
|
},
|
|
125
139
|
other: { "fb:app_id": process.env.NEXT_PUBLIC_FACEBOOK_APP_ID || "" },
|
|
126
140
|
};
|
|
@@ -155,9 +169,17 @@ export const generateSanityMetadata = ({
|
|
|
155
169
|
pageMetadata?.index ??
|
|
156
170
|
(pageMetadata?.noIndex != null ? !pageMetadata.noIndex : true);
|
|
157
171
|
|
|
172
|
+
const keywords = pageMetadata?.keywords?.length
|
|
173
|
+
? pageMetadata.keywords
|
|
174
|
+
: undefined;
|
|
175
|
+
|
|
158
176
|
return generatePageMetadata({
|
|
159
177
|
title,
|
|
160
178
|
description,
|
|
179
|
+
keywords,
|
|
180
|
+
ogTitle: pageMetadata?.ogTitle || undefined,
|
|
181
|
+
ogDescription: pageMetadata?.ogDescription || undefined,
|
|
182
|
+
canonicalUrl: pageMetadata?.canonicalUrl || undefined,
|
|
161
183
|
image: imageUrl ? { url: imageUrl, width: 1200, height: 630 } : undefined,
|
|
162
184
|
url,
|
|
163
185
|
siteName,
|
|
@@ -17,21 +17,21 @@ if (process.env.CI || process.env.VERCEL) {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
const args = process.argv.slice(2);
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
let file = ".env";
|
|
21
|
+
if (args.includes("--prod") || args.includes("--production")) {
|
|
22
|
+
file = ".env.production";
|
|
23
|
+
} else if (args.includes("--preview")) {
|
|
24
|
+
file = ".env.preview";
|
|
25
|
+
}
|
|
26
26
|
|
|
27
27
|
// Friendlier than dotenvx's MISSING_ENV_FILE error when the file isn't created yet.
|
|
28
28
|
if (!existsSync(file)) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
let flag = "";
|
|
30
|
+
if (file === ".env.production") {
|
|
31
|
+
flag = " --prod";
|
|
32
|
+
} else if (file === ".env.preview") {
|
|
33
|
+
flag = " --preview";
|
|
34
|
+
}
|
|
35
35
|
console.error(
|
|
36
36
|
`No ${file} yet. Add values with: bun run env:set KEY value${flag}`,
|
|
37
37
|
);
|
|
@@ -96,11 +96,11 @@ async function main() {
|
|
|
96
96
|
let ok = true;
|
|
97
97
|
for (const { name, value } of keys) {
|
|
98
98
|
if (target === "github") {
|
|
99
|
-
console.
|
|
99
|
+
console.info(`→ gh secret set ${name}`);
|
|
100
100
|
ok = pipe("gh", ["secret", "set", name], value) && ok;
|
|
101
101
|
} else {
|
|
102
102
|
for (const env of vercelEnvsFor(name)) {
|
|
103
|
-
console.
|
|
103
|
+
console.info(`→ vercel env add ${name} ${env} (sensitive)`);
|
|
104
104
|
// --sensitive: write-only, never shown in the dashboard.
|
|
105
105
|
ok = pipe("vercel", ["env", "add", name, env, "--sensitive"], value) && ok;
|
|
106
106
|
}
|
|
@@ -116,7 +116,7 @@ async function main() {
|
|
|
116
116
|
process.exit(1);
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
console.
|
|
119
|
+
console.info(
|
|
120
120
|
target === "vercel"
|
|
121
121
|
? "\nDone. Redeploy for the new env vars to take effect."
|
|
122
122
|
: "\nDone.",
|
|
@@ -131,6 +131,6 @@ try {
|
|
|
131
131
|
const ref = `op://${VAULT}/${itemRef}/private-key`;
|
|
132
132
|
setOpRef(ref);
|
|
133
133
|
|
|
134
|
-
console.
|
|
135
|
-
console.
|
|
136
|
-
console.
|
|
134
|
+
console.info(`\n✓ Saved to 1Password (${VAULT} / "${title}").`);
|
|
135
|
+
console.info(` Set DOTENV_PRIVATE_KEY_OP_REF=${ref} in .env.`);
|
|
136
|
+
console.info(" Teammates can now restore with `bun run env:setup`.");
|
|
@@ -166,8 +166,10 @@ function main(): void {
|
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
// Push after connecting so the git integration builds the first deployment.
|
|
169
|
+
// `--no-verify` skips pre-push hooks — this is the initial scaffold push, and
|
|
170
|
+
// a hook failure here would strand it behind an already-created empty remote.
|
|
169
171
|
console.info("→ git push");
|
|
170
|
-
if (!run("git", ["push", "-u", "origin", "HEAD"], "git not found.")) {
|
|
172
|
+
if (!run("git", ["push", "-u", "origin", "HEAD", "--no-verify"], "git not found.")) {
|
|
171
173
|
console.error(
|
|
172
174
|
"\nConnected, but the push failed — run `git push` to trigger the first deploy.",
|
|
173
175
|
);
|
|
@@ -12,12 +12,12 @@ const [mode, ...rest] = process.argv.slice(2);
|
|
|
12
12
|
|
|
13
13
|
// --prod / --preview pick the per-environment file; default is the shared .env.
|
|
14
14
|
const ENV_FLAGS = new Set(["--prod", "--production", "--preview"]);
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
let file = ".env";
|
|
16
|
+
if (rest.includes("--prod") || rest.includes("--production")) {
|
|
17
|
+
file = ".env.production";
|
|
18
|
+
} else if (rest.includes("--preview")) {
|
|
19
|
+
file = ".env.preview";
|
|
20
|
+
}
|
|
21
21
|
|
|
22
22
|
// On bulk encrypt, keep browser-public NEXT_PUBLIC_* and the 1Password pointer
|
|
23
23
|
// plaintext (same exclusions the scaffold uses).
|
|
@@ -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
|
# ============================================
|
|
@@ -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 +1 @@
|
|
|
1
|
-
<svg width="123" height="18" viewBox="0 0 123 18" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M4.43626 6.76458V.252441H.381348V17.1671H4.33011v-3.0447c.40337 2.1989 1.78331 3.2984 4.13983 3.2984 3.96996 0 4.86166-.9515 4.86166-5.6664V9.91493c0-4.7361-.8704-5.66641-4.64937-5.66641-2.54758 0-3.77892.54973-4.24597 2.51606Zm.02123 5.94122V9.42863c.10615-1.14174.84919-1.69146 2.29282-1.69146 1.95315 0 2.31406.42286 2.31406 2.30463v1.628c0 1.8395-.36091 2.2623-2.31406 2.2623-1.4224 0-2.16544-.4228-2.29282-1.2263Zm10.23281.8035c0 3.4252.7643 4.0384 4.9253 3.9115 1.9956-.0211 3.418-.8035 3.97-2.4949v2.2412h4.0337V8.62519c0-3.70008-.8917-4.37667-5.6259-4.37667h-.9766c-4.9678 0-5.9019.65544-5.9019 4.14409v.50744h4.0337c-.0425-1.45889.3184-1.71261 2.314-1.71261 1.7833 0 2.123.25372 2.123 1.71261v.71887l-4.0337.02114c-4.0973 0-4.8616.61314-4.8616 3.86924Zm4.246.0423c0-.7823.2123-.9303 1.3162-.9303l3.3331-.0212v.5497c0 .9938-.6793 1.3744-2.4627 1.4166h-.2122c-1.656 0-1.9744-.1691-1.9744-1.0148Zm10.0417-.3383c0 3.5309.8917 4.2075 5.5622 4.2075h.9766c5.3499 0 6.3265-.592 6.3265-3.6367 0-1.9451-.4883-3.108-2.5051-3.7-1.104-.31718-2.0805-.5709-4.1611-.90919-1.3799-.21143-1.762-.46515-1.762-.99373 0-.78231.4246-.99374 1.9956-.99374 1.6347 0 2.0805.29601 2.0805 1.33203v.38058h4.0337v-.38058c0-3.61551-.8917-4.27095-5.6259-4.27095h-.9766c-4.8192 0-5.7533.57087-5.7533 3.8058 0 1.69146.4458 2.64288 2.1867 3.31948 1.2738.4863 3.2057.7823 3.9487.9303 1.8682.3383 2.3141.6766 2.3141 1.184 0 .7612-.4883.9726-2.3141.9726-1.9744 0-2.5051-.2748-2.5051-1.2474v-.2749H28.978v.2749Zm14.0967-2.3681c0 5.5396.9765 6.5756 6.1779 6.5756h.9765c5.1589 0 6.1142-.6766 6.1142-4.271h-4.2459c0 1.0784-.3609 1.3109-2.3353 1.3109-2.0593 0-2.4415-.3594-2.4415-2.1989v-.0634h9.1289v-1.9875c0-5.03207-.9766-5.96238-6.2204-5.96238h-.9765c-5.2014 0-6.1779 1.01488-6.1779 6.59668Zm4.2459-1.628v-.06343c0-1.64918.3822-1.96633 2.4415-1.96633 1.9956 0 2.4202.31715 2.4414 2.02976h-4.8829Zm10.9122 7.9499h4.0337V9.83035c0-1.48003.7006-2.22004 2.1017-2.22004 1.6135 0 1.932.31715 1.932 1.96632v7.59047h4.0124l-.0212-7.46361c.0424-1.39546.743-2.09318 2.1017-2.09318 1.6135 0 1.8895.31715 1.8895 1.96632v7.59047h4.0337l-.0212-8.20362c0-3.97494-.7219-4.71496-4.6069-4.71496-2.1443 0-3.4393.90916-3.9063 2.91778-.1699-2.41034-.8705-2.91778-4.0974-2.91778-1.8895 0-2.9722.76116-3.418 2.66406V4.50224h-4.0337V17.1671Zm21.6545-6.3219c0 5.5396.9766 6.5756 6.1991 6.5756h.9766c5.1164 0 6.0717-.6766 6.0717-4.271H88.91c0 1.0784-.3609 1.3109-2.3141 1.3109-2.1017 0-2.4839-.3594-2.4839-2.1989v-.0634h9.1289v-1.9875c0-5.03207-.9766-5.96238-6.1779-5.96238h-.9766c-5.2225 0-6.1991 1.01488-6.1991 6.59668Zm4.2247-1.628v-.06343c0-1.64918.3822-1.96633 2.4839-1.96633 1.9744 0 2.399.31715 2.4202 2.02976H84.112Zm10.891 7.9499h4.0337V9.61892c.0212-1.35317.7642-2.00861 2.2293-2.00861 1.868 0 2.208.35943 2.208 2.30462v7.25217h4.033l-.021-8.20362c0-3.97494-.7-4.71496-4.394-4.71496-2.336 0-3.5671.76116-4.0553 2.81206V4.50224H95.003V17.1671Zm13.545-9.30307h1.252v5.05327c0 3.5732.828 4.2498 5.095 4.2498h2.76v-3.5944h-2.335c-1.253 0-1.486-.2114-1.486-1.2686V7.86403h3.63V4.50224h-3.63V1.77476h-3.821v2.72748h-1.465v3.36179Zm10.636 9.30307h3.63v-3.5944h-3.63v3.5944Z" fill="#EFEFEF"/></svg>
|
|
1
|
+
<svg width="123" height="18" viewBox="0 0 123 18" fill="none" xmlns="http://www.w3.org/2000/svg"><title>basement</title><path d="M4.43626 6.76458V.252441H.381348V17.1671H4.33011v-3.0447c.40337 2.1989 1.78331 3.2984 4.13983 3.2984 3.96996 0 4.86166-.9515 4.86166-5.6664V9.91493c0-4.7361-.8704-5.66641-4.64937-5.66641-2.54758 0-3.77892.54973-4.24597 2.51606Zm.02123 5.94122V9.42863c.10615-1.14174.84919-1.69146 2.29282-1.69146 1.95315 0 2.31406.42286 2.31406 2.30463v1.628c0 1.8395-.36091 2.2623-2.31406 2.2623-1.4224 0-2.16544-.4228-2.29282-1.2263Zm10.23281.8035c0 3.4252.7643 4.0384 4.9253 3.9115 1.9956-.0211 3.418-.8035 3.97-2.4949v2.2412h4.0337V8.62519c0-3.70008-.8917-4.37667-5.6259-4.37667h-.9766c-4.9678 0-5.9019.65544-5.9019 4.14409v.50744h4.0337c-.0425-1.45889.3184-1.71261 2.314-1.71261 1.7833 0 2.123.25372 2.123 1.71261v.71887l-4.0337.02114c-4.0973 0-4.8616.61314-4.8616 3.86924Zm4.246.0423c0-.7823.2123-.9303 1.3162-.9303l3.3331-.0212v.5497c0 .9938-.6793 1.3744-2.4627 1.4166h-.2122c-1.656 0-1.9744-.1691-1.9744-1.0148Zm10.0417-.3383c0 3.5309.8917 4.2075 5.5622 4.2075h.9766c5.3499 0 6.3265-.592 6.3265-3.6367 0-1.9451-.4883-3.108-2.5051-3.7-1.104-.31718-2.0805-.5709-4.1611-.90919-1.3799-.21143-1.762-.46515-1.762-.99373 0-.78231.4246-.99374 1.9956-.99374 1.6347 0 2.0805.29601 2.0805 1.33203v.38058h4.0337v-.38058c0-3.61551-.8917-4.27095-5.6259-4.27095h-.9766c-4.8192 0-5.7533.57087-5.7533 3.8058 0 1.69146.4458 2.64288 2.1867 3.31948 1.2738.4863 3.2057.7823 3.9487.9303 1.8682.3383 2.3141.6766 2.3141 1.184 0 .7612-.4883.9726-2.3141.9726-1.9744 0-2.5051-.2748-2.5051-1.2474v-.2749H28.978v.2749Zm14.0967-2.3681c0 5.5396.9765 6.5756 6.1779 6.5756h.9765c5.1589 0 6.1142-.6766 6.1142-4.271h-4.2459c0 1.0784-.3609 1.3109-2.3353 1.3109-2.0593 0-2.4415-.3594-2.4415-2.1989v-.0634h9.1289v-1.9875c0-5.03207-.9766-5.96238-6.2204-5.96238h-.9765c-5.2014 0-6.1779 1.01488-6.1779 6.59668Zm4.2459-1.628v-.06343c0-1.64918.3822-1.96633 2.4415-1.96633 1.9956 0 2.4202.31715 2.4414 2.02976h-4.8829Zm10.9122 7.9499h4.0337V9.83035c0-1.48003.7006-2.22004 2.1017-2.22004 1.6135 0 1.932.31715 1.932 1.96632v7.59047h4.0124l-.0212-7.46361c.0424-1.39546.743-2.09318 2.1017-2.09318 1.6135 0 1.8895.31715 1.8895 1.96632v7.59047h4.0337l-.0212-8.20362c0-3.97494-.7219-4.71496-4.6069-4.71496-2.1443 0-3.4393.90916-3.9063 2.91778-.1699-2.41034-.8705-2.91778-4.0974-2.91778-1.8895 0-2.9722.76116-3.418 2.66406V4.50224h-4.0337V17.1671Zm21.6545-6.3219c0 5.5396.9766 6.5756 6.1991 6.5756h.9766c5.1164 0 6.0717-.6766 6.0717-4.271H88.91c0 1.0784-.3609 1.3109-2.3141 1.3109-2.1017 0-2.4839-.3594-2.4839-2.1989v-.0634h9.1289v-1.9875c0-5.03207-.9766-5.96238-6.1779-5.96238h-.9766c-5.2225 0-6.1991 1.01488-6.1991 6.59668Zm4.2247-1.628v-.06343c0-1.64918.3822-1.96633 2.4839-1.96633 1.9744 0 2.399.31715 2.4202 2.02976H84.112Zm10.891 7.9499h4.0337V9.61892c.0212-1.35317.7642-2.00861 2.2293-2.00861 1.868 0 2.208.35943 2.208 2.30462v7.25217h4.033l-.021-8.20362c0-3.97494-.7-4.71496-4.394-4.71496-2.336 0-3.5671.76116-4.0553 2.81206V4.50224H95.003V17.1671Zm13.545-9.30307h1.252v5.05327c0 3.5732.828 4.2498 5.095 4.2498h2.76v-3.5944h-2.335c-1.253 0-1.486-.2114-1.486-1.2686V7.86403h3.63V4.50224h-3.63V1.77476h-3.821v2.72748h-1.465v3.36179Zm10.636 9.30307h3.63v-3.5944h-3.63v3.5944Z" fill="#EFEFEF"/></svg>
|
|
@@ -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
|
}
|
|
@@ -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
|
# ============================================
|
|
@@ -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 +1 @@
|
|
|
1
|
-
<svg width="123" height="18" viewBox="0 0 123 18" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M4.43626 6.76458V.252441H.381348V17.1671H4.33011v-3.0447c.40337 2.1989 1.78331 3.2984 4.13983 3.2984 3.96996 0 4.86166-.9515 4.86166-5.6664V9.91493c0-4.7361-.8704-5.66641-4.64937-5.66641-2.54758 0-3.77892.54973-4.24597 2.51606Zm.02123 5.94122V9.42863c.10615-1.14174.84919-1.69146 2.29282-1.69146 1.95315 0 2.31406.42286 2.31406 2.30463v1.628c0 1.8395-.36091 2.2623-2.31406 2.2623-1.4224 0-2.16544-.4228-2.29282-1.2263Zm10.23281.8035c0 3.4252.7643 4.0384 4.9253 3.9115 1.9956-.0211 3.418-.8035 3.97-2.4949v2.2412h4.0337V8.62519c0-3.70008-.8917-4.37667-5.6259-4.37667h-.9766c-4.9678 0-5.9019.65544-5.9019 4.14409v.50744h4.0337c-.0425-1.45889.3184-1.71261 2.314-1.71261 1.7833 0 2.123.25372 2.123 1.71261v.71887l-4.0337.02114c-4.0973 0-4.8616.61314-4.8616 3.86924Zm4.246.0423c0-.7823.2123-.9303 1.3162-.9303l3.3331-.0212v.5497c0 .9938-.6793 1.3744-2.4627 1.4166h-.2122c-1.656 0-1.9744-.1691-1.9744-1.0148Zm10.0417-.3383c0 3.5309.8917 4.2075 5.5622 4.2075h.9766c5.3499 0 6.3265-.592 6.3265-3.6367 0-1.9451-.4883-3.108-2.5051-3.7-1.104-.31718-2.0805-.5709-4.1611-.90919-1.3799-.21143-1.762-.46515-1.762-.99373 0-.78231.4246-.99374 1.9956-.99374 1.6347 0 2.0805.29601 2.0805 1.33203v.38058h4.0337v-.38058c0-3.61551-.8917-4.27095-5.6259-4.27095h-.9766c-4.8192 0-5.7533.57087-5.7533 3.8058 0 1.69146.4458 2.64288 2.1867 3.31948 1.2738.4863 3.2057.7823 3.9487.9303 1.8682.3383 2.3141.6766 2.3141 1.184 0 .7612-.4883.9726-2.3141.9726-1.9744 0-2.5051-.2748-2.5051-1.2474v-.2749H28.978v.2749Zm14.0967-2.3681c0 5.5396.9765 6.5756 6.1779 6.5756h.9765c5.1589 0 6.1142-.6766 6.1142-4.271h-4.2459c0 1.0784-.3609 1.3109-2.3353 1.3109-2.0593 0-2.4415-.3594-2.4415-2.1989v-.0634h9.1289v-1.9875c0-5.03207-.9766-5.96238-6.2204-5.96238h-.9765c-5.2014 0-6.1779 1.01488-6.1779 6.59668Zm4.2459-1.628v-.06343c0-1.64918.3822-1.96633 2.4415-1.96633 1.9956 0 2.4202.31715 2.4414 2.02976h-4.8829Zm10.9122 7.9499h4.0337V9.83035c0-1.48003.7006-2.22004 2.1017-2.22004 1.6135 0 1.932.31715 1.932 1.96632v7.59047h4.0124l-.0212-7.46361c.0424-1.39546.743-2.09318 2.1017-2.09318 1.6135 0 1.8895.31715 1.8895 1.96632v7.59047h4.0337l-.0212-8.20362c0-3.97494-.7219-4.71496-4.6069-4.71496-2.1443 0-3.4393.90916-3.9063 2.91778-.1699-2.41034-.8705-2.91778-4.0974-2.91778-1.8895 0-2.9722.76116-3.418 2.66406V4.50224h-4.0337V17.1671Zm21.6545-6.3219c0 5.5396.9766 6.5756 6.1991 6.5756h.9766c5.1164 0 6.0717-.6766 6.0717-4.271H88.91c0 1.0784-.3609 1.3109-2.3141 1.3109-2.1017 0-2.4839-.3594-2.4839-2.1989v-.0634h9.1289v-1.9875c0-5.03207-.9766-5.96238-6.1779-5.96238h-.9766c-5.2225 0-6.1991 1.01488-6.1991 6.59668Zm4.2247-1.628v-.06343c0-1.64918.3822-1.96633 2.4839-1.96633 1.9744 0 2.399.31715 2.4202 2.02976H84.112Zm10.891 7.9499h4.0337V9.61892c.0212-1.35317.7642-2.00861 2.2293-2.00861 1.868 0 2.208.35943 2.208 2.30462v7.25217h4.033l-.021-8.20362c0-3.97494-.7-4.71496-4.394-4.71496-2.336 0-3.5671.76116-4.0553 2.81206V4.50224H95.003V17.1671Zm13.545-9.30307h1.252v5.05327c0 3.5732.828 4.2498 5.095 4.2498h2.76v-3.5944h-2.335c-1.253 0-1.486-.2114-1.486-1.2686V7.86403h3.63V4.50224h-3.63V1.77476h-3.821v2.72748h-1.465v3.36179Zm10.636 9.30307h3.63v-3.5944h-3.63v3.5944Z" fill="#EFEFEF"/></svg>
|
|
1
|
+
<svg width="123" height="18" viewBox="0 0 123 18" fill="none" xmlns="http://www.w3.org/2000/svg"><title>basement</title><path d="M4.43626 6.76458V.252441H.381348V17.1671H4.33011v-3.0447c.40337 2.1989 1.78331 3.2984 4.13983 3.2984 3.96996 0 4.86166-.9515 4.86166-5.6664V9.91493c0-4.7361-.8704-5.66641-4.64937-5.66641-2.54758 0-3.77892.54973-4.24597 2.51606Zm.02123 5.94122V9.42863c.10615-1.14174.84919-1.69146 2.29282-1.69146 1.95315 0 2.31406.42286 2.31406 2.30463v1.628c0 1.8395-.36091 2.2623-2.31406 2.2623-1.4224 0-2.16544-.4228-2.29282-1.2263Zm10.23281.8035c0 3.4252.7643 4.0384 4.9253 3.9115 1.9956-.0211 3.418-.8035 3.97-2.4949v2.2412h4.0337V8.62519c0-3.70008-.8917-4.37667-5.6259-4.37667h-.9766c-4.9678 0-5.9019.65544-5.9019 4.14409v.50744h4.0337c-.0425-1.45889.3184-1.71261 2.314-1.71261 1.7833 0 2.123.25372 2.123 1.71261v.71887l-4.0337.02114c-4.0973 0-4.8616.61314-4.8616 3.86924Zm4.246.0423c0-.7823.2123-.9303 1.3162-.9303l3.3331-.0212v.5497c0 .9938-.6793 1.3744-2.4627 1.4166h-.2122c-1.656 0-1.9744-.1691-1.9744-1.0148Zm10.0417-.3383c0 3.5309.8917 4.2075 5.5622 4.2075h.9766c5.3499 0 6.3265-.592 6.3265-3.6367 0-1.9451-.4883-3.108-2.5051-3.7-1.104-.31718-2.0805-.5709-4.1611-.90919-1.3799-.21143-1.762-.46515-1.762-.99373 0-.78231.4246-.99374 1.9956-.99374 1.6347 0 2.0805.29601 2.0805 1.33203v.38058h4.0337v-.38058c0-3.61551-.8917-4.27095-5.6259-4.27095h-.9766c-4.8192 0-5.7533.57087-5.7533 3.8058 0 1.69146.4458 2.64288 2.1867 3.31948 1.2738.4863 3.2057.7823 3.9487.9303 1.8682.3383 2.3141.6766 2.3141 1.184 0 .7612-.4883.9726-2.3141.9726-1.9744 0-2.5051-.2748-2.5051-1.2474v-.2749H28.978v.2749Zm14.0967-2.3681c0 5.5396.9765 6.5756 6.1779 6.5756h.9765c5.1589 0 6.1142-.6766 6.1142-4.271h-4.2459c0 1.0784-.3609 1.3109-2.3353 1.3109-2.0593 0-2.4415-.3594-2.4415-2.1989v-.0634h9.1289v-1.9875c0-5.03207-.9766-5.96238-6.2204-5.96238h-.9765c-5.2014 0-6.1779 1.01488-6.1779 6.59668Zm4.2459-1.628v-.06343c0-1.64918.3822-1.96633 2.4415-1.96633 1.9956 0 2.4202.31715 2.4414 2.02976h-4.8829Zm10.9122 7.9499h4.0337V9.83035c0-1.48003.7006-2.22004 2.1017-2.22004 1.6135 0 1.932.31715 1.932 1.96632v7.59047h4.0124l-.0212-7.46361c.0424-1.39546.743-2.09318 2.1017-2.09318 1.6135 0 1.8895.31715 1.8895 1.96632v7.59047h4.0337l-.0212-8.20362c0-3.97494-.7219-4.71496-4.6069-4.71496-2.1443 0-3.4393.90916-3.9063 2.91778-.1699-2.41034-.8705-2.91778-4.0974-2.91778-1.8895 0-2.9722.76116-3.418 2.66406V4.50224h-4.0337V17.1671Zm21.6545-6.3219c0 5.5396.9766 6.5756 6.1991 6.5756h.9766c5.1164 0 6.0717-.6766 6.0717-4.271H88.91c0 1.0784-.3609 1.3109-2.3141 1.3109-2.1017 0-2.4839-.3594-2.4839-2.1989v-.0634h9.1289v-1.9875c0-5.03207-.9766-5.96238-6.1779-5.96238h-.9766c-5.2225 0-6.1991 1.01488-6.1991 6.59668Zm4.2247-1.628v-.06343c0-1.64918.3822-1.96633 2.4839-1.96633 1.9744 0 2.399.31715 2.4202 2.02976H84.112Zm10.891 7.9499h4.0337V9.61892c.0212-1.35317.7642-2.00861 2.2293-2.00861 1.868 0 2.208.35943 2.208 2.30462v7.25217h4.033l-.021-8.20362c0-3.97494-.7-4.71496-4.394-4.71496-2.336 0-3.5671.76116-4.0553 2.81206V4.50224H95.003V17.1671Zm13.545-9.30307h1.252v5.05327c0 3.5732.828 4.2498 5.095 4.2498h2.76v-3.5944h-2.335c-1.253 0-1.486-.2114-1.486-1.2686V7.86403h3.63V4.50224h-3.63V1.77476h-3.821v2.72748h-1.465v3.36179Zm10.636 9.30307h3.63v-3.5944h-3.63v3.5944Z" fill="#EFEFEF"/></svg>
|
|
@@ -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
|
}
|
|
@@ -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
|
# Base URL
|
|
20
28
|
NEXT_PUBLIC_BASE_URL=
|
|
@@ -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,15 @@ 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 -->
|
|
55
67
|
## Continuous Integration
|
|
56
68
|
|
|
57
69
|
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 +75,10 @@ The workflow takes its runtime versions from your manifests, so there's a single
|
|
|
63
75
|
|
|
64
76
|
It also caches Bun's install cache between runs, and the test step is a no-op until you add real test files.
|
|
65
77
|
|
|
78
|
+
<!-- bsmnt:if dotenvx -->
|
|
66
79
|
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
80
|
|
|
81
|
+
<!-- bsmnt:endif -->
|
|
68
82
|
### Sanity environment
|
|
69
83
|
|
|
70
84
|
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:
|
|
@@ -14,7 +14,7 @@ import { checkRedirect } from "@/lib/utils/check-redirect"
|
|
|
14
14
|
import { generateSanityMetadata } from "@/lib/utils/metadata"
|
|
15
15
|
import { getSlugTag } from "@/lib/utils/slug-tag"
|
|
16
16
|
|
|
17
|
-
const getPageDocument = async (slug: string | null, stega
|
|
17
|
+
const getPageDocument = async (slug: string | null, stega?: boolean) =>
|
|
18
18
|
fetchSanity<PAGE_QUERY_RESULT>({
|
|
19
19
|
query: PAGE_QUERY,
|
|
20
20
|
params: { slug },
|
|
@@ -27,7 +27,7 @@ const toPath = (segments?: string[]) =>
|
|
|
27
27
|
|
|
28
28
|
const getResolvedPage = async (
|
|
29
29
|
path: string | null,
|
|
30
|
-
stega
|
|
30
|
+
stega?: boolean
|
|
31
31
|
): Promise<NonNullable<PAGE_QUERY_RESULT> | null> => {
|
|
32
32
|
const { data: page } = await getPageDocument(path, stega)
|
|
33
33
|
return (page as NonNullable<PAGE_QUERY_RESULT> | null) ?? null
|