bsmnt 0.4.3 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- 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 +33 -8
- package/dist/application/add-integration/index.d.ts.map +1 -1
- package/dist/application/add-integration/index.js +221 -90
- package/dist/application/add-integration/index.js.map +1 -1
- package/dist/application/create-project/build-context.d.ts +3 -1
- package/dist/application/create-project/build-context.d.ts.map +1 -1
- package/dist/application/create-project/build-context.js +2 -1
- package/dist/application/create-project/build-context.js.map +1 -1
- package/dist/application/create-project/index.d.ts +3 -1
- package/dist/application/create-project/index.d.ts.map +1 -1
- package/dist/application/create-project/index.js +4 -2
- package/dist/application/create-project/index.js.map +1 -1
- package/dist/application/create-project/map-selection-to-registry.js +1 -1
- package/dist/application/create-project/map-selection-to-registry.js.map +1 -1
- package/dist/application/install-skills/index.js +1 -1
- package/dist/application/install-skills/index.js.map +1 -1
- package/dist/application/shared/project-detection.d.ts +2 -2
- package/dist/application/shared/project-detection.d.ts.map +1 -1
- package/dist/application/shared/project-detection.js +2 -2
- package/dist/application/shared/project-detection.js.map +1 -1
- package/dist/core/create/execute-plan.d.ts +4 -0
- package/dist/core/create/execute-plan.d.ts.map +1 -1
- package/dist/core/create/execute-plan.js +8 -0
- package/dist/core/create/execute-plan.js.map +1 -1
- package/dist/core/create/internal-ids.d.ts +4 -1
- package/dist/core/create/internal-ids.d.ts.map +1 -1
- package/dist/core/create/internal-ids.js +6 -1
- package/dist/core/create/internal-ids.js.map +1 -1
- package/dist/core/create/types.d.ts +2 -0
- package/dist/core/create/types.d.ts.map +1 -1
- package/dist/domain/cms.d.ts +0 -7
- package/dist/domain/cms.d.ts.map +1 -1
- package/dist/domain/cms.js.map +1 -1
- package/dist/domain/forms.d.ts +17 -0
- package/dist/domain/forms.d.ts.map +1 -0
- package/dist/domain/forms.js +13 -0
- package/dist/domain/forms.js.map +1 -0
- package/dist/domain/integration.d.ts +30 -0
- package/dist/domain/integration.d.ts.map +1 -0
- package/dist/domain/integration.js +12 -0
- package/dist/domain/integration.js.map +1 -0
- package/dist/domain/skills.d.ts +58 -0
- package/dist/domain/skills.d.ts.map +1 -1
- package/dist/domain/skills.js +52 -0
- package/dist/domain/skills.js.map +1 -1
- package/dist/index.js +114 -38
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/agents/register-sanity-mcp.js +9 -6
- package/dist/infrastructure/agents/register-sanity-mcp.js.map +1 -1
- package/dist/infrastructure/create/apply-package-json.d.ts.map +1 -1
- package/dist/infrastructure/create/apply-package-json.js +19 -0
- package/dist/infrastructure/create/apply-package-json.js.map +1 -1
- package/dist/infrastructure/create/executor.d.ts.map +1 -1
- package/dist/infrastructure/create/executor.js +5 -0
- package/dist/infrastructure/create/executor.js.map +1 -1
- package/dist/infrastructure/create/install-dependencies.d.ts.map +1 -1
- package/dist/infrastructure/create/install-dependencies.js +4 -0
- package/dist/infrastructure/create/install-dependencies.js.map +1 -1
- package/dist/infrastructure/create/lifecycle-registry.d.ts.map +1 -1
- package/dist/infrastructure/create/lifecycle-registry.js +2 -0
- package/dist/infrastructure/create/lifecycle-registry.js.map +1 -1
- package/dist/infrastructure/create/merge-registry.d.ts.map +1 -1
- package/dist/infrastructure/create/merge-registry.js +8 -0
- package/dist/infrastructure/create/merge-registry.js.map +1 -1
- package/dist/infrastructure/create/setup-agent.d.ts +2 -0
- package/dist/infrastructure/create/setup-agent.d.ts.map +1 -1
- package/dist/infrastructure/create/setup-agent.js +32 -8
- package/dist/infrastructure/create/setup-agent.js.map +1 -1
- package/dist/infrastructure/create/setup-dotenvx.d.ts +41 -0
- package/dist/infrastructure/create/setup-dotenvx.d.ts.map +1 -0
- package/dist/infrastructure/create/setup-dotenvx.js +502 -0
- package/dist/infrastructure/create/setup-dotenvx.js.map +1 -0
- package/dist/infrastructure/create/setup-sanity.d.ts +13 -1
- package/dist/infrastructure/create/setup-sanity.d.ts.map +1 -1
- package/dist/infrastructure/create/setup-sanity.js +74 -31
- package/dist/infrastructure/create/setup-sanity.js.map +1 -1
- package/dist/infrastructure/integrations/inject-integration.d.ts +3 -3
- package/dist/infrastructure/integrations/inject-integration.d.ts.map +1 -1
- package/dist/infrastructure/integrations/inject-integration.js +81 -37
- package/dist/infrastructure/integrations/inject-integration.js.map +1 -1
- package/dist/modules/features/cms/sanity/config.d.ts.map +1 -1
- package/dist/modules/features/cms/sanity/config.js +13 -6
- package/dist/modules/features/cms/sanity/config.js.map +1 -1
- package/dist/modules/features/cms/sanity/index.js +8 -2
- package/dist/modules/features/cms/sanity/index.js.map +1 -1
- package/dist/modules/features/cms/sanity/mergers/ci-workflow.d.ts +13 -0
- package/dist/modules/features/cms/sanity/mergers/ci-workflow.d.ts.map +1 -0
- package/dist/modules/features/cms/sanity/mergers/ci-workflow.js +38 -0
- package/dist/modules/features/cms/sanity/mergers/ci-workflow.js.map +1 -0
- package/dist/modules/features/cms/sanity/mergers/knip.d.ts +12 -0
- package/dist/modules/features/cms/sanity/mergers/knip.d.ts.map +1 -0
- package/dist/modules/features/cms/sanity/mergers/knip.js +38 -0
- package/dist/modules/features/cms/sanity/mergers/knip.js.map +1 -0
- package/dist/modules/features/cms/sanity/mergers/layout.d.ts.map +1 -1
- package/dist/modules/features/cms/sanity/mergers/layout.js +17 -3
- package/dist/modules/features/cms/sanity/mergers/layout.js.map +1 -1
- package/dist/modules/features/cms/sanity/mergers/sitemap.d.ts +3 -3
- package/dist/modules/features/cms/sanity/mergers/sitemap.d.ts.map +1 -1
- package/dist/modules/features/cms/sanity/mergers/sitemap.js +48 -62
- package/dist/modules/features/cms/sanity/mergers/sitemap.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 +8 -5
- package/dist/modules/features/cms/sanity-pagebuilder/config.js.map +1 -1
- package/dist/modules/features/cms/sanity-pagebuilder/index.js +2 -2
- package/dist/modules/features/cms/sanity-pagebuilder/index.js.map +1 -1
- package/dist/modules/features/env/dotenvx/config.d.ts +13 -0
- package/dist/modules/features/env/dotenvx/config.d.ts.map +1 -0
- package/dist/modules/features/env/dotenvx/config.js +47 -0
- package/dist/modules/features/env/dotenvx/config.js.map +1 -0
- package/dist/modules/features/forms/hubspot/config.d.ts +31 -0
- package/dist/modules/features/forms/hubspot/config.d.ts.map +1 -0
- package/dist/modules/features/forms/hubspot/config.js +25 -0
- package/dist/modules/features/forms/hubspot/config.js.map +1 -0
- package/dist/paths.d.ts +16 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +16 -0
- package/dist/paths.js.map +1 -1
- package/package.json +21 -9
- package/src/agent-skills/json-ld/SKILL.md +85 -0
- package/src/agent-skills/manage-env/SKILL.md +67 -0
- package/src/assets/check-node-version.mjs +22 -0
- package/src/modules/features/cms/sanity/files/app/blog/[slug]/page.tsx +48 -21
- package/src/modules/features/cms/sanity/files/app/blog/page.tsx +54 -0
- package/src/modules/features/cms/sanity/files/app/feed.xml/route.ts +36 -0
- package/src/modules/features/cms/sanity/files/components/sanity/structured-data.tsx +83 -0
- package/src/modules/features/cms/sanity/files/lib/integrations/sanity/components/disable-draft-mode.tsx +3 -3
- package/src/modules/features/cms/sanity/files/lib/integrations/sanity/queries.ts +14 -0
- package/src/modules/features/cms/sanity/files/lib/integrations/sanity/sanity.config.ts +19 -3
- package/src/modules/features/cms/sanity/files/lib/integrations/sanity/sanity.types.ts +1 -0
- package/src/modules/features/cms/sanity/files/lib/integrations/sanity/schemas/article.ts +7 -7
- package/src/modules/features/cms/sanity/files/lib/integrations/sanity/schemas/index.ts +36 -1
- package/src/modules/features/cms/sanity/files/lib/integrations/sanity/schemas/metadata.ts +12 -1
- package/src/modules/features/cms/sanity/files/lib/integrations/sanity/schemas/structured-data-blocks.ts +416 -0
- package/src/modules/features/cms/sanity/files/lib/integrations/sanity/schemas/structured-data.ts +100 -0
- package/src/modules/features/cms/sanity/files/lib/integrations/sanity/structure.ts +37 -2
- package/src/modules/features/cms/sanity/files/lib/integrations/sanity/utils/page-json-ld.ts +208 -0
- package/src/modules/features/cms/sanity/files/lib/scripts/setup-ci-secrets.ts +117 -0
- package/src/modules/features/cms/sanity/files/lib/utils/rss.ts +96 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/.knip.json +8 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/app/api/[[...slug]]/route.ts +100 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/app/api/draft-mode/disable/route.ts +7 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/app/api/draft-mode/enable/route.ts +20 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/app/api/revalidate/route.ts +126 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/app/feed.xml/route.ts +40 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/app/sitemap.md/route.ts +124 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/app/studio/[[...tool]]/page.tsx +8 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/components/sanity/draft-mode-toggle.tsx +27 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/components/sanity/rich-text.tsx +87 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/components/sanity/visual-editing.tsx +28 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/components/ui/sanity-image/index.tsx +41 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/check-integration.ts +5 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/client.ts +27 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/disable-draft-mode.tsx +23 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/page-builder-input.tsx +39 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/page-category-input.tsx +50 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/same-as-input.tsx +66 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/slug-field.tsx +18 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/slug-input.tsx +14 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/confirm-publish-action.ts +40 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/env.ts +34 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/fetchers/layout.ts +67 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/fetchers/redirects.ts +17 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/icons.ts +79 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/live/index.tsx +88 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/markdown-proxy.config.ts +50 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/page-builder-config.ts +130 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/page-category.ts +28 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/page-tree-pane.tsx +420 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/pinned-pages.ts +54 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/presentation.ts +119 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/queries.ts +407 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/sanity.cli.ts +29 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/sanity.config.ts +113 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/index.ts +4 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/reusable/blog-content.ts +89 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/reusable/description.ts +29 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/reusable/hero.ts +28 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/singleton/content-collection.ts +45 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/content/author.ts +70 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/content/blog-category.ts +55 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/index.ts +124 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/layout/company-data.ts +70 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/layout/footer.ts +79 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/layout/navbar.ts +75 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/layout/redirect.ts +120 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/layout/structured-data.ts +124 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/link.ts +125 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/logo-field.ts +9 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/metadata.ts +79 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/nav-objects.ts +192 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/page-builder.ts +39 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/page-folder.ts +124 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/page.ts +242 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/richText.ts +63 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/structured-data-blocks.ts +436 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/singletons.ts +51 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/structure.ts +330 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/utils/image.ts +8 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/utils/link.ts +140 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/utils/page-builder-markdown.ts +81 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/utils/page-json-ld.ts +208 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/scripts/sanity-typegen.ts +45 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/check-redirect.ts +30 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/json-ld.tsx +521 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/metadata.ts +167 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/rss.ts +96 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/sitemap.ts +40 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/proxy.ts +81 -0
- package/src/modules/features/env/dotenvx/files/decrypt-env.mjs +45 -0
- package/src/modules/features/env/dotenvx/files/gitleaks.yml +25 -0
- package/src/modules/features/env/dotenvx/files/instrumentation.ts +14 -0
- package/src/modules/features/env/dotenvx/files/push-keys.mjs +129 -0
- package/src/modules/features/env/dotenvx/files/save-key.mjs +108 -0
- package/src/modules/features/env/dotenvx/files/setup-env.mjs +67 -0
- package/src/modules/features/env/dotenvx/files/vercelignore +4 -0
- package/src/modules/features/env/dotenvx/files/write-env.mjs +47 -0
- package/src/templates/next-default/.env.example +14 -6
- package/src/templates/next-default/.github/workflows/ci.yml +83 -0
- package/src/templates/next-default/.vscode/extensions.json +3 -0
- package/src/templates/next-default/README.md +53 -0
- package/src/templates/next-default/knip.json +14 -0
- package/src/templates/next-default/lefthook.yml +5 -0
- package/src/templates/next-default/lib/utils/json-ld.tsx +327 -21
- package/src/templates/next-default/next.config.ts +6 -0
- package/src/templates/next-default/package.json +8 -4
- package/src/templates/next-experiments/.env.example +14 -6
- package/src/templates/next-experiments/.github/workflows/ci.yml +83 -0
- package/src/templates/next-experiments/.vscode/extensions.json +3 -0
- package/src/templates/next-experiments/README.md +53 -0
- package/src/templates/next-experiments/knip.json +14 -0
- package/src/templates/next-experiments/lefthook.yml +30 -0
- package/src/templates/next-experiments/lib/utils/json-ld.tsx +327 -21
- package/src/templates/next-experiments/next.config.ts +6 -0
- package/src/templates/next-experiments/package.json +10 -2
- package/src/templates/next-pagebuilder/.env.example +19 -1
- package/src/templates/next-pagebuilder/.github/workflows/ci.yml +90 -0
- package/src/templates/next-pagebuilder/.vscode/extensions.json +3 -0
- package/src/templates/next-pagebuilder/README.md +65 -0
- package/src/templates/next-pagebuilder/app/(content)/[[...slug]]/page.tsx +6 -1
- package/src/templates/next-pagebuilder/app/(content)/layout.tsx +3 -12
- package/src/templates/next-pagebuilder/components/layout/footer/index.tsx +11 -10
- package/src/templates/next-pagebuilder/components/layout/header/index.tsx +5 -3
- package/src/templates/next-pagebuilder/components/layout/json-ld/index.tsx +38 -26
- package/src/templates/next-pagebuilder/components/layout/wrapper/index.tsx +3 -11
- package/src/templates/next-pagebuilder/components/page-builder/components/content-collection/index.tsx +1 -1
- package/src/templates/next-pagebuilder/components/page-document/index.tsx +38 -0
- package/src/templates/next-pagebuilder/knip.json +27 -0
- package/src/templates/next-pagebuilder/lefthook.yml +5 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.types.ts +36 -2
- package/src/templates/next-pagebuilder/lib/scripts/setup-ci-secrets.ts +117 -0
- package/src/templates/next-pagebuilder/next.config.ts +6 -0
- package/src/templates/next-pagebuilder/package.json +9 -3
- package/src/templates/next-webgl/.env.example +14 -6
- package/src/templates/next-webgl/.github/workflows/ci.yml +83 -0
- package/src/templates/next-webgl/.vscode/extensions.json +3 -0
- package/src/templates/next-webgl/README.md +53 -0
- package/src/templates/next-webgl/knip.json +13 -0
- package/src/templates/next-webgl/lefthook.yml +5 -0
- package/src/templates/next-webgl/lib/utils/json-ld.tsx +327 -21
- package/src/templates/next-webgl/next.config.ts +6 -0
- package/src/templates/next-webgl/package.json +8 -4
- package/src/modules/features/cms/sanity/files/lib/utils/json-ld.tsx +0 -244
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
# ============================================
|
|
2
2
|
# Basement Next Starter - Environment Variables
|
|
3
3
|
# ============================================
|
|
4
|
-
#
|
|
5
|
-
#
|
|
4
|
+
# This project uses dotenvx (https://dotenvx.com): the committed `.env` holds
|
|
5
|
+
# shared values, with secrets encrypted there. The private keys live in
|
|
6
|
+
# `.env.keys` (gitignored) — restore them with `bun run env:setup`.
|
|
6
7
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
8
|
+
# Add or change a variable in `.env` (not .env.local):
|
|
9
|
+
# - Secret (API key, token, …): bun run env:set KEY value (--preview / --prod)
|
|
10
|
+
# - Trivial / NEXT_PUBLIC_*: bun run env:set KEY value --plain
|
|
11
|
+
# - Bulk-encrypt a file edited by hand: bun run env:encrypt-all (--preview / --prod)
|
|
12
|
+
#
|
|
13
|
+
# Edge runtime (middleware, `runtime = "edge"`): dotenvx can't decrypt there.
|
|
14
|
+
# Set any secret those read directly in Vercel's env vars, not here.
|
|
15
|
+
#
|
|
16
|
+
# This file just documents the available variables; real values live in `.env`.
|
|
9
17
|
# ============================================
|
|
10
18
|
|
|
11
19
|
|
|
@@ -16,7 +24,7 @@
|
|
|
16
24
|
# Base URL for your site (used for SEO, canonical URLs, etc.)
|
|
17
25
|
# Development: http://localhost:3000
|
|
18
26
|
# Production: https://your-domain.com
|
|
19
|
-
NEXT_PUBLIC_BASE_URL="
|
|
27
|
+
NEXT_PUBLIC_BASE_URL=""
|
|
20
28
|
|
|
21
29
|
# Draft mode secret for preview functionality
|
|
22
30
|
# Generate a random string: openssl rand -base64 32
|
|
@@ -32,7 +40,7 @@ DRAFT_MODE_TOKEN=""
|
|
|
32
40
|
|
|
33
41
|
# Required for Sanity
|
|
34
42
|
NEXT_PUBLIC_SANITY_PROJECT_ID=""
|
|
35
|
-
NEXT_PUBLIC_SANITY_DATASET="
|
|
43
|
+
NEXT_PUBLIC_SANITY_DATASET=""
|
|
36
44
|
NEXT_PUBLIC_SANITY_API_READ_TOKEN=""
|
|
37
45
|
|
|
38
46
|
# Optional - for server-side mutations and visual editing
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
push:
|
|
8
|
+
branches:
|
|
9
|
+
- main
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
ci:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- name: Checkout
|
|
17
|
+
uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
# Node version is read from .nvmrc — the single source of truth.
|
|
20
|
+
- name: Setup Node
|
|
21
|
+
uses: actions/setup-node@v4
|
|
22
|
+
with:
|
|
23
|
+
node-version-file: ".nvmrc"
|
|
24
|
+
|
|
25
|
+
# Bun version is read from package.json's "packageManager" field — the
|
|
26
|
+
# single source of truth. Do NOT pin a version here, or the two can drift.
|
|
27
|
+
- name: Setup Bun
|
|
28
|
+
uses: oven-sh/setup-bun@v2
|
|
29
|
+
|
|
30
|
+
# Cache Bun's global install cache so dependencies aren't re-downloaded
|
|
31
|
+
# on every run. Keyed on the lockfile so it busts when deps change.
|
|
32
|
+
- name: Cache Bun dependencies
|
|
33
|
+
uses: actions/cache@v4
|
|
34
|
+
with:
|
|
35
|
+
path: ~/.bun/install/cache
|
|
36
|
+
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }}
|
|
37
|
+
restore-keys: |
|
|
38
|
+
${{ runner.os }}-bun-
|
|
39
|
+
|
|
40
|
+
- name: Install dependencies
|
|
41
|
+
run: bun install --frozen-lockfile
|
|
42
|
+
|
|
43
|
+
- name: Type check
|
|
44
|
+
run: bun run typecheck:compare
|
|
45
|
+
|
|
46
|
+
- name: Lint
|
|
47
|
+
run: bun run lint
|
|
48
|
+
|
|
49
|
+
# dotenvx needs the private key to decrypt secrets at build time. It's
|
|
50
|
+
# pulled from 1Password via an org-wide service-account token + the op-ref
|
|
51
|
+
# committed in .env. Optional: builds pass without it (only plaintext /
|
|
52
|
+
# NEXT_PUBLIC_* is read), and a per-repo DOTENV_PRIVATE_KEY secret still
|
|
53
|
+
# works as a fallback.
|
|
54
|
+
- name: Install 1Password CLI
|
|
55
|
+
if: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN != '' }}
|
|
56
|
+
uses: 1password/install-cli-action@v1
|
|
57
|
+
|
|
58
|
+
- name: Build
|
|
59
|
+
env:
|
|
60
|
+
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
|
|
61
|
+
DOTENV_PRIVATE_KEY: ${{ secrets.DOTENV_PRIVATE_KEY }}
|
|
62
|
+
run: |
|
|
63
|
+
if [ -n "$OP_SERVICE_ACCOUNT_TOKEN" ]; then
|
|
64
|
+
ref="$(grep -m1 '^DOTENV_PRIVATE_KEY_OP_REF=' .env | cut -d= -f2-)"
|
|
65
|
+
if [ -n "$ref" ]; then
|
|
66
|
+
DOTENV_PRIVATE_KEY="$(op read "$ref")"
|
|
67
|
+
echo "::add-mask::$DOTENV_PRIVATE_KEY"
|
|
68
|
+
export DOTENV_PRIVATE_KEY
|
|
69
|
+
fi
|
|
70
|
+
fi
|
|
71
|
+
bun run build
|
|
72
|
+
|
|
73
|
+
# Runs your tests if any exist; a fresh project with none passes cleanly.
|
|
74
|
+
- name: Test
|
|
75
|
+
run: |
|
|
76
|
+
if git ls-files '*.test.*' '*.spec.*' '*_test.*' '*_spec.*' | grep -q .; then
|
|
77
|
+
bun test
|
|
78
|
+
else
|
|
79
|
+
echo "No test files found — skipping bun test."
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
- name: Knip
|
|
83
|
+
run: bun run knip
|
|
@@ -22,6 +22,35 @@ 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
|
+
| `bun env:set KEY value` | Encrypt one value into `.env` (`--plain`, `--preview`/`--prod`) |
|
|
26
|
+
| `bun env:encrypt-all` | Bulk-encrypt every plaintext value in a file (`--preview`/`--prod`) |
|
|
27
|
+
| `bun env:setup` | Restore `.env.keys` from 1Password |
|
|
28
|
+
| `bun env:view` | Print the decrypted `.env` (`--preview` / `--prod`) |
|
|
29
|
+
| `bun env:setup-remote` | Push private keys to GitHub / Vercel (`github`/`vercel`, or pick) |
|
|
30
|
+
| `bun env:save` | Save the private key to 1Password (Development vault) |
|
|
31
|
+
|
|
32
|
+
## Environment variables
|
|
33
|
+
|
|
34
|
+
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.
|
|
35
|
+
|
|
36
|
+
**First run.** `bun install` restores `.env.keys` from 1Password (via `DOTENV_PRIVATE_KEY_OP_REF` in `.env`), or run `bun run env:setup`. Needs 1Password access + the `op` CLI:
|
|
37
|
+
|
|
38
|
+
- macOS: `brew install --cask 1password-cli`
|
|
39
|
+
- Windows: `winget install 1password-cli`
|
|
40
|
+
- Linux / other: [1Password CLI docs](https://developer.1password.com/docs/cli/get-started/)
|
|
41
|
+
|
|
42
|
+
**Add or change a variable** (in `.env`):
|
|
43
|
+
|
|
44
|
+
- Secret: `bun run env:set KEY value` (`--preview`/`--prod` to target an environment)
|
|
45
|
+
- Public / `NEXT_PUBLIC_*`: `bun run env:set KEY value --plain`
|
|
46
|
+
- Bulk-encrypt a hand-edited file: `bun run env:encrypt-all`
|
|
47
|
+
- Inspect: `bun run env:view` (`--preview`/`--prod`)
|
|
48
|
+
|
|
49
|
+
**Per environment.** Scripts load `.env.$VERCEL_ENV` over `.env`. After adding a per-env file, push its key with `bun run env:setup-remote vercel` — `instrumentation.ts` uses it to decrypt at runtime.
|
|
50
|
+
|
|
51
|
+
**Edge runtime.** dotenvx decrypts only in the Node runtime; secrets read in `middleware.ts` or a `runtime = "edge"` route must be set as plain Vercel env vars.
|
|
52
|
+
|
|
53
|
+
A gitleaks pre-commit hook + CI scan block committed plaintext secrets (install: `brew install gitleaks`).
|
|
25
54
|
|
|
26
55
|
## Project Structure
|
|
27
56
|
|
|
@@ -48,3 +77,27 @@ lib/
|
|
|
48
77
|
- Edit `lib/styles/tokens.css` for theme variables, breakpoints, and custom Tailwind utilities.
|
|
49
78
|
- Edit `lib/styles/global.css` for reset rules and app-wide global styles.
|
|
50
79
|
- `lib/styles/fonts.ts` handles font variables.
|
|
80
|
+
|
|
81
|
+
## Continuous Integration
|
|
82
|
+
|
|
83
|
+
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`.
|
|
84
|
+
|
|
85
|
+
The workflow takes its runtime versions from your manifests, so there's a single source of truth and nothing to keep in sync by hand:
|
|
86
|
+
|
|
87
|
+
- **Node** comes from `.nvmrc`.
|
|
88
|
+
- **Bun** comes from the `packageManager` field in `package.json`.
|
|
89
|
+
|
|
90
|
+
It also caches Bun's install cache between runs, and the test step is a no-op until you add real test files.
|
|
91
|
+
|
|
92
|
+
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).
|
|
93
|
+
|
|
94
|
+
### Branch protection
|
|
95
|
+
|
|
96
|
+
To block merges when CI fails, enable branch protection on `main`:
|
|
97
|
+
|
|
98
|
+
1. Go to **Settings → Branches → Add branch ruleset** (or **Add rule**).
|
|
99
|
+
2. Target the `main` branch.
|
|
100
|
+
3. Enable **Require status checks to pass before merging** and select the **CI** check.
|
|
101
|
+
4. (Recommended) Enable **Require a pull request before merging**.
|
|
102
|
+
|
|
103
|
+
Pull requests then cannot be merged until the CI workflow passes.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://unpkg.com/knip@6/schema.json",
|
|
3
|
+
"entry": [
|
|
4
|
+
"lib/scripts/**/*.{ts,tsx}",
|
|
5
|
+
"lib/hooks/**/*.{ts,tsx}",
|
|
6
|
+
"lib/utils/**/*.{ts,tsx}",
|
|
7
|
+
"lib/renderer.ts",
|
|
8
|
+
"components/**/*.{ts,tsx}"
|
|
9
|
+
],
|
|
10
|
+
"ignoreDependencies": [
|
|
11
|
+
"zod",
|
|
12
|
+
"@svgr/webpack"
|
|
13
|
+
]
|
|
14
|
+
}
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
Article,
|
|
3
3
|
BreadcrumbList,
|
|
4
|
+
Event as EventSchema,
|
|
5
|
+
FAQPage,
|
|
6
|
+
ItemList,
|
|
7
|
+
JobPosting,
|
|
4
8
|
Organization,
|
|
5
9
|
SearchAction,
|
|
10
|
+
SoftwareApplication,
|
|
6
11
|
Thing,
|
|
12
|
+
VideoObject,
|
|
7
13
|
WebPage,
|
|
8
14
|
WebSite,
|
|
9
15
|
WithContext,
|
|
@@ -41,10 +47,10 @@ export function JsonLd<T extends Thing>({ data }: { data: WithContext<T> }) {
|
|
|
41
47
|
|
|
42
48
|
interface WebSiteJsonLdOptions {
|
|
43
49
|
name: string
|
|
44
|
-
url?: string
|
|
45
|
-
description?: string
|
|
50
|
+
url?: string | undefined
|
|
51
|
+
description?: string | undefined
|
|
46
52
|
/** URL to site-wide search (e.g. "/search?q={search_term_string}") */
|
|
47
|
-
searchUrl?: string
|
|
53
|
+
searchUrl?: string | undefined
|
|
48
54
|
}
|
|
49
55
|
|
|
50
56
|
export function generateWebSiteJsonLd(
|
|
@@ -70,21 +76,69 @@ export function generateWebSiteJsonLd(
|
|
|
70
76
|
}
|
|
71
77
|
}
|
|
72
78
|
|
|
79
|
+
interface OrganizationContactPoint {
|
|
80
|
+
email?: string | null
|
|
81
|
+
telephone?: string | null
|
|
82
|
+
contactType?: string | null
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
interface OrganizationAddress {
|
|
86
|
+
streetAddress?: string | null
|
|
87
|
+
locality?: string | null
|
|
88
|
+
region?: string | null
|
|
89
|
+
postalCode?: string | null
|
|
90
|
+
country?: string | null
|
|
91
|
+
}
|
|
92
|
+
|
|
73
93
|
interface OrganizationJsonLdOptions {
|
|
74
94
|
name: string
|
|
75
|
-
url?: string
|
|
76
|
-
logo?: string
|
|
77
|
-
description?: string
|
|
78
|
-
sameAs?: string[]
|
|
95
|
+
url?: string | undefined
|
|
96
|
+
logo?: string | undefined
|
|
97
|
+
description?: string | undefined
|
|
98
|
+
sameAs?: string[] | undefined
|
|
99
|
+
legalName?: string | undefined
|
|
100
|
+
contactPoint?: OrganizationContactPoint | null | undefined
|
|
101
|
+
address?: OrganizationAddress | null | undefined
|
|
79
102
|
}
|
|
80
103
|
|
|
81
104
|
export function generateOrganizationJsonLd(
|
|
82
105
|
options: OrganizationJsonLdOptions
|
|
83
106
|
): WithContext<Organization> {
|
|
84
|
-
const { name, url, logo, description, sameAs } =
|
|
107
|
+
const { name, url, logo, description, sameAs, legalName, contactPoint, address } =
|
|
108
|
+
options
|
|
85
109
|
const resolvedUrl = resolveUrl(url)
|
|
86
110
|
const resolvedLogo = resolveUrl(logo)
|
|
87
111
|
|
|
112
|
+
const contactPointNode =
|
|
113
|
+
contactPoint &&
|
|
114
|
+
(contactPoint.email || contactPoint.telephone || contactPoint.contactType)
|
|
115
|
+
? {
|
|
116
|
+
"@type": "ContactPoint" as const,
|
|
117
|
+
...(contactPoint.email && { email: contactPoint.email }),
|
|
118
|
+
...(contactPoint.telephone && { telephone: contactPoint.telephone }),
|
|
119
|
+
...(contactPoint.contactType && {
|
|
120
|
+
contactType: contactPoint.contactType,
|
|
121
|
+
}),
|
|
122
|
+
}
|
|
123
|
+
: undefined
|
|
124
|
+
|
|
125
|
+
const addressNode =
|
|
126
|
+
address &&
|
|
127
|
+
(address.streetAddress ||
|
|
128
|
+
address.locality ||
|
|
129
|
+
address.region ||
|
|
130
|
+
address.postalCode ||
|
|
131
|
+
address.country)
|
|
132
|
+
? {
|
|
133
|
+
"@type": "PostalAddress" as const,
|
|
134
|
+
...(address.streetAddress && { streetAddress: address.streetAddress }),
|
|
135
|
+
...(address.locality && { addressLocality: address.locality }),
|
|
136
|
+
...(address.region && { addressRegion: address.region }),
|
|
137
|
+
...(address.postalCode && { postalCode: address.postalCode }),
|
|
138
|
+
...(address.country && { addressCountry: address.country }),
|
|
139
|
+
}
|
|
140
|
+
: undefined
|
|
141
|
+
|
|
88
142
|
return {
|
|
89
143
|
"@context": "https://schema.org",
|
|
90
144
|
"@type": "Organization",
|
|
@@ -92,17 +146,20 @@ export function generateOrganizationJsonLd(
|
|
|
92
146
|
...(resolvedUrl && { url: resolvedUrl }),
|
|
93
147
|
...(resolvedLogo && { logo: resolvedLogo }),
|
|
94
148
|
...(description && { description }),
|
|
95
|
-
...(
|
|
149
|
+
...(legalName && { legalName }),
|
|
150
|
+
...(sameAs?.length && { sameAs }),
|
|
151
|
+
...(contactPointNode && { contactPoint: contactPointNode }),
|
|
152
|
+
...(addressNode && { address: addressNode }),
|
|
96
153
|
}
|
|
97
154
|
}
|
|
98
155
|
|
|
99
156
|
interface WebPageJsonLdOptions {
|
|
100
157
|
title: string
|
|
101
|
-
url?: string
|
|
102
|
-
description?: string
|
|
103
|
-
image?: string
|
|
104
|
-
datePublished?: string
|
|
105
|
-
dateModified?: string
|
|
158
|
+
url?: string | undefined
|
|
159
|
+
description?: string | undefined
|
|
160
|
+
image?: string | undefined
|
|
161
|
+
datePublished?: string | undefined
|
|
162
|
+
dateModified?: string | undefined
|
|
106
163
|
}
|
|
107
164
|
|
|
108
165
|
export function generateWebPageJsonLd(
|
|
@@ -127,13 +184,13 @@ export function generateWebPageJsonLd(
|
|
|
127
184
|
|
|
128
185
|
interface ArticleJsonLdOptions {
|
|
129
186
|
title: string
|
|
130
|
-
url?: string
|
|
131
|
-
description?: string
|
|
132
|
-
image?: string
|
|
133
|
-
datePublished?: string
|
|
134
|
-
dateModified?: string
|
|
135
|
-
authorName?: string
|
|
136
|
-
authorUrl?: string
|
|
187
|
+
url?: string | undefined
|
|
188
|
+
description?: string | undefined
|
|
189
|
+
image?: string | undefined
|
|
190
|
+
datePublished?: string | undefined
|
|
191
|
+
dateModified?: string | undefined
|
|
192
|
+
authorName?: string | undefined
|
|
193
|
+
authorUrl?: string | undefined
|
|
137
194
|
}
|
|
138
195
|
|
|
139
196
|
export function generateArticleJsonLd(
|
|
@@ -194,3 +251,252 @@ export function generateBreadcrumbJsonLd(
|
|
|
194
251
|
}),
|
|
195
252
|
}
|
|
196
253
|
}
|
|
254
|
+
|
|
255
|
+
// Page-level generators for the schema.org blocks editors attach via
|
|
256
|
+
// `metadata.jsonLd`. Plain options keep them CMS-independent.
|
|
257
|
+
|
|
258
|
+
export interface FaqJsonLdItem {
|
|
259
|
+
question: string
|
|
260
|
+
answer: string
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function generateFaqJsonLd(
|
|
264
|
+
items: FaqJsonLdItem[]
|
|
265
|
+
): WithContext<FAQPage> {
|
|
266
|
+
return {
|
|
267
|
+
"@context": "https://schema.org",
|
|
268
|
+
"@type": "FAQPage",
|
|
269
|
+
mainEntity: items.map((item) => ({
|
|
270
|
+
"@type": "Question" as const,
|
|
271
|
+
name: item.question,
|
|
272
|
+
acceptedAnswer: {
|
|
273
|
+
"@type": "Answer" as const,
|
|
274
|
+
text: item.answer,
|
|
275
|
+
},
|
|
276
|
+
})),
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export interface JobPostingJsonLdOptions {
|
|
281
|
+
title: string
|
|
282
|
+
description: string
|
|
283
|
+
datePosted: string
|
|
284
|
+
validThrough?: string | undefined
|
|
285
|
+
employmentType?: string | undefined
|
|
286
|
+
organizationName?: string | undefined
|
|
287
|
+
organizationUrl?: string | undefined
|
|
288
|
+
remote?: boolean | undefined
|
|
289
|
+
locality?: string | undefined
|
|
290
|
+
region?: string | undefined
|
|
291
|
+
country?: string | undefined
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export function generateJobPostingJsonLd(
|
|
295
|
+
options: JobPostingJsonLdOptions
|
|
296
|
+
): WithContext<JobPosting> {
|
|
297
|
+
const {
|
|
298
|
+
title,
|
|
299
|
+
description,
|
|
300
|
+
datePosted,
|
|
301
|
+
validThrough,
|
|
302
|
+
employmentType,
|
|
303
|
+
organizationName,
|
|
304
|
+
organizationUrl,
|
|
305
|
+
remote,
|
|
306
|
+
locality,
|
|
307
|
+
region,
|
|
308
|
+
country,
|
|
309
|
+
} = options
|
|
310
|
+
const orgUrl = organizationUrl ? resolveUrl(organizationUrl) : undefined
|
|
311
|
+
|
|
312
|
+
const hiringOrganization = organizationName
|
|
313
|
+
? {
|
|
314
|
+
"@type": "Organization" as const,
|
|
315
|
+
name: organizationName,
|
|
316
|
+
...(orgUrl && { url: orgUrl }),
|
|
317
|
+
}
|
|
318
|
+
: undefined
|
|
319
|
+
|
|
320
|
+
const jobLocation =
|
|
321
|
+
locality || region || country
|
|
322
|
+
? {
|
|
323
|
+
"@type": "Place" as const,
|
|
324
|
+
address: {
|
|
325
|
+
"@type": "PostalAddress" as const,
|
|
326
|
+
...(locality && { addressLocality: locality }),
|
|
327
|
+
...(region && { addressRegion: region }),
|
|
328
|
+
...(country && { addressCountry: country }),
|
|
329
|
+
},
|
|
330
|
+
}
|
|
331
|
+
: undefined
|
|
332
|
+
|
|
333
|
+
return {
|
|
334
|
+
"@context": "https://schema.org",
|
|
335
|
+
"@type": "JobPosting",
|
|
336
|
+
title,
|
|
337
|
+
description,
|
|
338
|
+
datePosted,
|
|
339
|
+
...(validThrough && { validThrough }),
|
|
340
|
+
...(employmentType && { employmentType }),
|
|
341
|
+
...(hiringOrganization && { hiringOrganization }),
|
|
342
|
+
...(remote && { jobLocationType: "TELECOMMUTE" }),
|
|
343
|
+
...(jobLocation && { jobLocation }),
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export interface SoftwareApplicationJsonLdOptions {
|
|
348
|
+
name: string
|
|
349
|
+
description?: string | undefined
|
|
350
|
+
applicationCategory?: string | undefined
|
|
351
|
+
operatingSystem?: string | undefined
|
|
352
|
+
price?: number | undefined
|
|
353
|
+
currency?: string | undefined
|
|
354
|
+
url?: string | undefined
|
|
355
|
+
image?: string | undefined
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export function generateSoftwareApplicationJsonLd(
|
|
359
|
+
options: SoftwareApplicationJsonLdOptions
|
|
360
|
+
): WithContext<SoftwareApplication> {
|
|
361
|
+
const {
|
|
362
|
+
name,
|
|
363
|
+
description,
|
|
364
|
+
applicationCategory,
|
|
365
|
+
operatingSystem,
|
|
366
|
+
price,
|
|
367
|
+
currency,
|
|
368
|
+
url,
|
|
369
|
+
image,
|
|
370
|
+
} = options
|
|
371
|
+
const resolvedUrl = url ? resolveUrl(url) : undefined
|
|
372
|
+
const resolvedImage = image ? resolveUrl(image) : undefined
|
|
373
|
+
|
|
374
|
+
const offers =
|
|
375
|
+
typeof price === "number"
|
|
376
|
+
? {
|
|
377
|
+
"@type": "Offer" as const,
|
|
378
|
+
price: price.toString(),
|
|
379
|
+
...(currency && { priceCurrency: currency }),
|
|
380
|
+
}
|
|
381
|
+
: undefined
|
|
382
|
+
|
|
383
|
+
return {
|
|
384
|
+
"@context": "https://schema.org",
|
|
385
|
+
"@type": "SoftwareApplication",
|
|
386
|
+
name,
|
|
387
|
+
...(description && { description }),
|
|
388
|
+
...(applicationCategory && { applicationCategory }),
|
|
389
|
+
...(operatingSystem && { operatingSystem }),
|
|
390
|
+
...(resolvedUrl && { url: resolvedUrl }),
|
|
391
|
+
...(resolvedImage && { image: resolvedImage }),
|
|
392
|
+
...(offers && { offers }),
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
export interface ItemListEntry {
|
|
397
|
+
name: string
|
|
398
|
+
url?: string | undefined
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export interface ItemListJsonLdOptions {
|
|
402
|
+
name?: string | undefined
|
|
403
|
+
items: ItemListEntry[]
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
export function generateItemListJsonLd(
|
|
407
|
+
options: ItemListJsonLdOptions
|
|
408
|
+
): WithContext<ItemList> {
|
|
409
|
+
const { name, items } = options
|
|
410
|
+
|
|
411
|
+
return {
|
|
412
|
+
"@context": "https://schema.org",
|
|
413
|
+
"@type": "ItemList",
|
|
414
|
+
...(name && { name }),
|
|
415
|
+
itemListElement: items.map((item, index) => {
|
|
416
|
+
const itemUrl = item.url ? resolveUrl(item.url) : undefined
|
|
417
|
+
return {
|
|
418
|
+
"@type": "ListItem" as const,
|
|
419
|
+
position: index + 1,
|
|
420
|
+
name: item.name,
|
|
421
|
+
...(itemUrl && { url: itemUrl }),
|
|
422
|
+
}
|
|
423
|
+
}),
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
export interface EventJsonLdOptions {
|
|
428
|
+
name: string
|
|
429
|
+
description?: string | undefined
|
|
430
|
+
startDate: string
|
|
431
|
+
endDate?: string | undefined
|
|
432
|
+
locationName?: string | undefined
|
|
433
|
+
locationAddress?: string | undefined
|
|
434
|
+
url?: string | undefined
|
|
435
|
+
image?: string | undefined
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
export function generateEventJsonLd(
|
|
439
|
+
options: EventJsonLdOptions
|
|
440
|
+
): WithContext<EventSchema> {
|
|
441
|
+
const {
|
|
442
|
+
name,
|
|
443
|
+
description,
|
|
444
|
+
startDate,
|
|
445
|
+
endDate,
|
|
446
|
+
locationName,
|
|
447
|
+
locationAddress,
|
|
448
|
+
url,
|
|
449
|
+
image,
|
|
450
|
+
} = options
|
|
451
|
+
const resolvedUrl = resolveUrl(url)
|
|
452
|
+
const resolvedImage = resolveUrl(image)
|
|
453
|
+
|
|
454
|
+
const location =
|
|
455
|
+
locationName || locationAddress
|
|
456
|
+
? {
|
|
457
|
+
"@type": "Place" as const,
|
|
458
|
+
...(locationName && { name: locationName }),
|
|
459
|
+
...(locationAddress && { address: locationAddress }),
|
|
460
|
+
}
|
|
461
|
+
: undefined
|
|
462
|
+
|
|
463
|
+
return {
|
|
464
|
+
"@context": "https://schema.org",
|
|
465
|
+
"@type": "Event",
|
|
466
|
+
name,
|
|
467
|
+
startDate,
|
|
468
|
+
...(endDate && { endDate }),
|
|
469
|
+
...(description && { description }),
|
|
470
|
+
...(resolvedUrl && { url: resolvedUrl }),
|
|
471
|
+
...(resolvedImage && { image: resolvedImage }),
|
|
472
|
+
...(location && { location }),
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
export interface VideoJsonLdOptions {
|
|
477
|
+
name: string
|
|
478
|
+
description: string
|
|
479
|
+
thumbnail: string
|
|
480
|
+
uploadDate: string
|
|
481
|
+
contentUrl?: string | undefined
|
|
482
|
+
embedUrl?: string | undefined
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
export function generateVideoJsonLd(
|
|
486
|
+
options: VideoJsonLdOptions
|
|
487
|
+
): WithContext<VideoObject> {
|
|
488
|
+
const { name, description, thumbnail, uploadDate, contentUrl, embedUrl } =
|
|
489
|
+
options
|
|
490
|
+
const resolvedThumbnail = resolveUrl(thumbnail) ?? thumbnail
|
|
491
|
+
|
|
492
|
+
return {
|
|
493
|
+
"@context": "https://schema.org",
|
|
494
|
+
"@type": "VideoObject",
|
|
495
|
+
name,
|
|
496
|
+
description,
|
|
497
|
+
thumbnailUrl: resolvedThumbnail,
|
|
498
|
+
uploadDate,
|
|
499
|
+
...(contentUrl && { contentUrl }),
|
|
500
|
+
...(embedUrl && { embedUrl }),
|
|
501
|
+
}
|
|
502
|
+
}
|
|
@@ -5,6 +5,12 @@ const nextConfig: NextConfig = {
|
|
|
5
5
|
reactStrictMode: true,
|
|
6
6
|
reactCompiler: true,
|
|
7
7
|
typedRoutes: true,
|
|
8
|
+
// Bundle the committed encrypted env files into serverless functions so
|
|
9
|
+
// instrumentation.ts can decrypt them at runtime. .env.keys is never listed
|
|
10
|
+
// (and is git/vercel-ignored); missing files are skipped.
|
|
11
|
+
outputFileTracingIncludes: {
|
|
12
|
+
"/**": ["./.env", "./.env.production", "./.env.preview"],
|
|
13
|
+
},
|
|
8
14
|
turbopack: {
|
|
9
15
|
rules: {
|
|
10
16
|
"*.svg": {
|
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
"description": "Basement Next.js starter template",
|
|
4
4
|
"version": "0.1.0",
|
|
5
5
|
"private": true,
|
|
6
|
-
"packageManager": "bun@1.3.
|
|
6
|
+
"packageManager": "bun@1.3.14",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=24"
|
|
9
|
+
},
|
|
7
10
|
"scripts": {
|
|
8
11
|
"analyze": "cross-env ANALYZE=true bun run build",
|
|
9
12
|
"analyze:experimental": "next experimental-analyze",
|
|
@@ -12,8 +15,8 @@
|
|
|
12
15
|
"dev:https": "bun ./lib/scripts/dev.ts --https",
|
|
13
16
|
"dev:inspect": "bun ./lib/scripts/dev.ts --inspect",
|
|
14
17
|
"format": "biome format --write .",
|
|
15
|
-
"knip": "
|
|
16
|
-
"knip:fix": "
|
|
18
|
+
"knip": "knip",
|
|
19
|
+
"knip:fix": "knip --fix",
|
|
17
20
|
"lighthouse": "bunx @unlighthouse/cli --site http://localhost:3000",
|
|
18
21
|
"lint": "biome lint --max-diagnostics=200",
|
|
19
22
|
"lint:fix": "biome check --write --unsafe --max-diagnostics=200",
|
|
@@ -39,12 +42,13 @@
|
|
|
39
42
|
"@svgr/webpack": "^8.1.0",
|
|
40
43
|
"@tailwindcss/postcss": "^4",
|
|
41
44
|
"@types/bun": "^1.3.6",
|
|
42
|
-
"@types/node": "^
|
|
45
|
+
"@types/node": "^24.13.1",
|
|
43
46
|
"@types/react": "^19.2.9",
|
|
44
47
|
"@types/react-dom": "^19.2.3",
|
|
45
48
|
"@typescript/native-preview": "^7.0.0-dev.20260122.3",
|
|
46
49
|
"babel-plugin-react-compiler": "^1.0.0",
|
|
47
50
|
"cross-env": "^10.1.0",
|
|
51
|
+
"knip": "^6.16.1",
|
|
48
52
|
"lefthook": "^1.13.0",
|
|
49
53
|
"postcss-preset-env": "^10.6.1",
|
|
50
54
|
"schema-dts": "^2.0.0",
|