bsmnt 0.5.0 → 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 +13 -6
- package/dist/application/add-integration/index.d.ts.map +1 -1
- package/dist/application/add-integration/index.js +145 -62
- 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/install-skills/index.js +1 -1
- package/dist/application/install-skills/index.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/types.d.ts +2 -0
- package/dist/core/create/types.d.ts.map +1 -1
- package/dist/domain/skills.d.ts +26 -0
- package/dist/domain/skills.d.ts.map +1 -1
- package/dist/domain/skills.js +13 -0
- package/dist/domain/skills.js.map +1 -1
- package/dist/index.js +26 -8
- package/dist/index.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/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/setup-agent.d.ts +2 -0
- package/dist/infrastructure/create/setup-agent.d.ts.map +1 -1
- package/dist/infrastructure/create/setup-agent.js +9 -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 +71 -31
- package/dist/infrastructure/create/setup-sanity.js.map +1 -1
- package/dist/modules/features/cms/sanity/config.d.ts.map +1 -1
- package/dist/modules/features/cms/sanity/config.js +2 -1
- package/dist/modules/features/cms/sanity/config.js.map +1 -1
- package/dist/modules/features/cms/sanity/mergers/layout.d.ts.map +1 -1
- package/dist/modules/features/cms/sanity/mergers/layout.js +10 -0
- package/dist/modules/features/cms/sanity/mergers/layout.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/paths.d.ts +8 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +8 -0
- package/dist/paths.js.map +1 -1
- package/package.json +5 -1
- package/src/agent-skills/json-ld/SKILL.md +85 -0
- package/src/agent-skills/manage-env/SKILL.md +67 -0
- package/src/modules/features/cms/sanity/files/app/blog/[slug]/page.tsx +48 -21
- package/src/modules/features/cms/sanity/files/components/sanity/structured-data.tsx +83 -0
- 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/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-pagebuilder/files/lib/integrations/sanity/components/same-as-input.tsx +66 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/fetchers/layout.ts +20 -1
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/icons.ts +17 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/queries.ts +22 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/index.ts +28 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/layout/company-data.ts +8 -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/metadata.ts +12 -1
- 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 +13 -1
- 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/utils/json-ld.tsx +324 -15
- 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 +22 -1
- package/src/templates/next-default/.vscode/extensions.json +3 -0
- package/src/templates/next-default/README.md +31 -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-experiments/.env.example +14 -6
- package/src/templates/next-experiments/.github/workflows/ci.yml +22 -1
- package/src/templates/next-experiments/.vscode/extensions.json +3 -0
- package/src/templates/next-experiments/README.md +31 -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-pagebuilder/.env.example +19 -1
- package/src/templates/next-pagebuilder/.github/workflows/ci.yml +21 -1
- package/src/templates/next-pagebuilder/.vscode/extensions.json +3 -0
- package/src/templates/next-pagebuilder/README.md +31 -0
- package/src/templates/next-pagebuilder/components/layout/json-ld/index.tsx +32 -19
- package/src/templates/next-pagebuilder/components/page-document/index.tsx +38 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.types.ts +36 -2
- package/src/templates/next-pagebuilder/next.config.ts +6 -0
- package/src/templates/next-webgl/.env.example +14 -6
- package/src/templates/next-webgl/.github/workflows/ci.yml +22 -1
- package/src/templates/next-webgl/.vscode/extensions.json +3 -0
- package/src/templates/next-webgl/README.md +31 -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/modules/features/cms/sanity/files/lib/utils/json-ld.tsx +0 -244
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: manage-env
|
|
3
|
+
description: >-
|
|
4
|
+
Use when adding or changing an environment variable in a project that uses our
|
|
5
|
+
dotenvx setup. Decides secret vs trivial config: trivial/`NEXT_PUBLIC_*` the
|
|
6
|
+
agent handles fully, but for a secret the agent only wires the key in code
|
|
7
|
+
(process.env, validation schema, types) and hands the value-setting to the
|
|
8
|
+
human — it never types a secret value into a command. Triggers on "add an env
|
|
9
|
+
var", "new environment variable", "set an API key/token/secret", "put X in
|
|
10
|
+
.env", "encrypt env", or editing any `.env*` file.
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Add or change an environment variable
|
|
14
|
+
|
|
15
|
+
This project uses **dotenvx**. Env files:
|
|
16
|
+
|
|
17
|
+
- `.env` — shared / development, committed **encrypted**
|
|
18
|
+
- `.env.preview`, `.env.production` — per-environment overrides, committed encrypted (created on demand)
|
|
19
|
+
- `.env.local` — plaintext, gitignored (personal local overrides)
|
|
20
|
+
- `.env.keys` — private keys, gitignored (backed up in the team password manager)
|
|
21
|
+
|
|
22
|
+
Scripts run `dotenvx run -f .env.$VERCEL_ENV -f .env -- …`, so the env-specific
|
|
23
|
+
file overrides `.env`. A **gitleaks** pre-commit hook + CI scan are the real
|
|
24
|
+
enforcement — this skill just makes the right choice the default.
|
|
25
|
+
|
|
26
|
+
## 1. Secret or trivial config?
|
|
27
|
+
|
|
28
|
+
- **Secret** — API keys, tokens, DB URLs, anything you'd rotate if it leaked.
|
|
29
|
+
- **Trivial** — public base URLs, feature flags, anything `NEXT_PUBLIC_*` (it
|
|
30
|
+
ships to the browser anyway). Plaintext is fine; encrypting buys nothing.
|
|
31
|
+
|
|
32
|
+
## 2a. Secret → never put the value in a command
|
|
33
|
+
|
|
34
|
+
A raw secret in a command (`env:set KEY <value>`) leaks the plaintext into the
|
|
35
|
+
tool call and the conversation context. So split the work:
|
|
36
|
+
|
|
37
|
+
- **Agent — code wiring only, never the value.** Read it via `process.env.KEY`,
|
|
38
|
+
add it to the env validation schema / types if the project has one, and add a
|
|
39
|
+
placeholder line to `.env.example`. Document the key name and where it's used.
|
|
40
|
+
- **Human — owns the value.** Either run `bun run env:set KEY <value>` yourself
|
|
41
|
+
(`--preview` / `--prod` for per-environment), or set it directly in the Vercel
|
|
42
|
+
environment. `env:set` encrypts in place and adds the matching
|
|
43
|
+
`DOTENV_PRIVATE_KEY*` to `.env.keys`.
|
|
44
|
+
- **Verify (agent):** only via `git diff` showing `KEY=encrypted:…`. Never run
|
|
45
|
+
`env:view` on a secret — it prints the decrypted value into context.
|
|
46
|
+
- Each `DOTENV_PRIVATE_KEY*` must exist on the matching Vercel environment and as a
|
|
47
|
+
CI secret, or it won't decrypt there.
|
|
48
|
+
- **Edge runtime caveat.** dotenvx only decrypts in the Node runtime (via
|
|
49
|
+
`instrumentation.ts`). A secret read in `middleware.ts` or a `runtime = "edge"`
|
|
50
|
+
route is **not** decrypted from `.env` — it must be set directly as a Vercel env
|
|
51
|
+
var. The encrypted `.env` covers Node functions + build, not the edge.
|
|
52
|
+
|
|
53
|
+
## 2b. Trivial config → plaintext
|
|
54
|
+
|
|
55
|
+
Add it plaintext — no encryption — with `bun run env:set KEY value --plain`, or
|
|
56
|
+
just append a line to the file:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
NEXT_PUBLIC_FOO=bar
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
gitleaks ignores non-secret plaintext, so it won't fail the scan.
|
|
63
|
+
|
|
64
|
+
## Rule
|
|
65
|
+
|
|
66
|
+
Never commit a real secret in plaintext. If one lands there, bulk-encrypt it in
|
|
67
|
+
place with `bun run env:encrypt-all` (no value typed).
|
|
@@ -6,6 +6,9 @@ import {
|
|
|
6
6
|
ALL_ARTICLES_QUERY,
|
|
7
7
|
ARTICLE_QUERY,
|
|
8
8
|
} from "@/lib/integrations/sanity/queries";
|
|
9
|
+
import { urlForImage } from "@/lib/integrations/sanity/utils/image";
|
|
10
|
+
import { buildPageJsonLd } from "@/lib/integrations/sanity/utils/page-json-ld";
|
|
11
|
+
import { generateArticleJsonLd, JsonLd } from "@/lib/utils/json-ld";
|
|
9
12
|
|
|
10
13
|
export async function generateStaticParams() {
|
|
11
14
|
if (!client) return [];
|
|
@@ -30,28 +33,52 @@ export default async function BlogArticlePage({ params }: Props) {
|
|
|
30
33
|
|
|
31
34
|
if (!article) return notFound();
|
|
32
35
|
|
|
36
|
+
const articleUrl = `/blog/${slug}`;
|
|
37
|
+
const articleImage = article.featuredImage
|
|
38
|
+
? urlForImage(article.featuredImage).url()
|
|
39
|
+
: article.metadata?.image
|
|
40
|
+
? urlForImage(article.metadata.image).url()
|
|
41
|
+
: undefined;
|
|
42
|
+
const pageJsonLd = buildPageJsonLd(article.metadata?.jsonLd);
|
|
43
|
+
|
|
33
44
|
return (
|
|
34
|
-
|
|
35
|
-
<
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
<>
|
|
46
|
+
<JsonLd
|
|
47
|
+
data={generateArticleJsonLd({
|
|
48
|
+
title: article.metadata?.title || article.title || "",
|
|
49
|
+
url: articleUrl,
|
|
50
|
+
description: article.metadata?.description || article.excerpt,
|
|
51
|
+
image: articleImage,
|
|
52
|
+
datePublished: article.publishedAt,
|
|
53
|
+
dateModified: article._updatedAt,
|
|
54
|
+
authorName: article.author,
|
|
55
|
+
})}
|
|
56
|
+
/>
|
|
57
|
+
{pageJsonLd.map(({ key, data }) => (
|
|
58
|
+
<JsonLd key={key} data={data} />
|
|
59
|
+
))}
|
|
60
|
+
<article className="container mx-auto px-4 py-8 max-w-4xl">
|
|
61
|
+
<header className="mb-8">
|
|
62
|
+
<h1 className="text-4xl font-bold mb-4">{article.title}</h1>
|
|
63
|
+
{article.excerpt && (
|
|
64
|
+
<p className="text-lg text-gray-600">{article.excerpt}</p>
|
|
46
65
|
)}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
66
|
+
<div className="flex gap-4 mt-4 text-sm text-gray-500">
|
|
67
|
+
{article.author && <span>By {article.author}</span>}
|
|
68
|
+
{article.publishedAt && (
|
|
69
|
+
<time dateTime={article.publishedAt}>
|
|
70
|
+
{new Date(article.publishedAt).toLocaleDateString()}
|
|
71
|
+
</time>
|
|
72
|
+
)}
|
|
73
|
+
</div>
|
|
74
|
+
</header>
|
|
75
|
+
|
|
76
|
+
{article.content && (
|
|
77
|
+
<div className="prose max-w-none">
|
|
78
|
+
<RichText content={article.content} />
|
|
79
|
+
</div>
|
|
80
|
+
)}
|
|
81
|
+
</article>
|
|
82
|
+
</>
|
|
56
83
|
);
|
|
57
84
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { stegaClean } from "next-sanity";
|
|
2
|
+
import { sanityFetch } from "@/lib/integrations/sanity/live";
|
|
3
|
+
import { STRUCTURED_DATA_QUERY } from "@/lib/integrations/sanity/queries";
|
|
4
|
+
import {
|
|
5
|
+
generateOrganizationJsonLd,
|
|
6
|
+
generateWebSiteJsonLd,
|
|
7
|
+
JsonLd,
|
|
8
|
+
} from "@/lib/utils/json-ld";
|
|
9
|
+
|
|
10
|
+
type StructuredDataResult = {
|
|
11
|
+
name?: string | null;
|
|
12
|
+
legalName?: string | null;
|
|
13
|
+
description?: string | null;
|
|
14
|
+
url?: string | null;
|
|
15
|
+
logo?: string | null;
|
|
16
|
+
sameAs?: (string | null)[] | null;
|
|
17
|
+
contactPoint?: {
|
|
18
|
+
email?: string | null;
|
|
19
|
+
telephone?: string | null;
|
|
20
|
+
contactType?: string | null;
|
|
21
|
+
} | null;
|
|
22
|
+
address?: {
|
|
23
|
+
streetAddress?: string | null;
|
|
24
|
+
locality?: string | null;
|
|
25
|
+
region?: string | null;
|
|
26
|
+
postalCode?: string | null;
|
|
27
|
+
country?: string | null;
|
|
28
|
+
} | null;
|
|
29
|
+
} | null;
|
|
30
|
+
|
|
31
|
+
const nonEmpty = (value: unknown): string | undefined => {
|
|
32
|
+
const cleaned = stegaClean(value);
|
|
33
|
+
return typeof cleaned === "string" && cleaned.trim() !== ""
|
|
34
|
+
? cleaned
|
|
35
|
+
: undefined;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Site-wide Organization + WebSite JSON-LD from the Structured Data
|
|
40
|
+
* singleton, with the static app values as fallback.
|
|
41
|
+
*/
|
|
42
|
+
export async function SanityStructuredData({
|
|
43
|
+
fallbackName,
|
|
44
|
+
fallbackDescription,
|
|
45
|
+
fallbackLogo,
|
|
46
|
+
}: {
|
|
47
|
+
fallbackName: string;
|
|
48
|
+
fallbackDescription?: string;
|
|
49
|
+
fallbackLogo?: string;
|
|
50
|
+
}) {
|
|
51
|
+
const { data } = await sanityFetch({ query: STRUCTURED_DATA_QUERY });
|
|
52
|
+
// Deep-clean stega artifacts up front so nested objects (contactPoint,
|
|
53
|
+
// address) never leak visual-editing markers into the emitted JSON-LD.
|
|
54
|
+
const structuredData = stegaClean(data as unknown as StructuredDataResult);
|
|
55
|
+
|
|
56
|
+
const name = nonEmpty(structuredData?.name) ?? fallbackName;
|
|
57
|
+
const description =
|
|
58
|
+
nonEmpty(structuredData?.description) ?? fallbackDescription;
|
|
59
|
+
const url = nonEmpty(structuredData?.url);
|
|
60
|
+
const logo = nonEmpty(structuredData?.logo) ?? fallbackLogo;
|
|
61
|
+
|
|
62
|
+
const sameAs = (structuredData?.sameAs ?? [])
|
|
63
|
+
.map(nonEmpty)
|
|
64
|
+
.filter((value): value is string => Boolean(value));
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<>
|
|
68
|
+
<JsonLd data={generateWebSiteJsonLd({ name, url, description })} />
|
|
69
|
+
<JsonLd
|
|
70
|
+
data={generateOrganizationJsonLd({
|
|
71
|
+
name,
|
|
72
|
+
url,
|
|
73
|
+
logo,
|
|
74
|
+
description,
|
|
75
|
+
legalName: nonEmpty(structuredData?.legalName),
|
|
76
|
+
sameAs: sameAs.length ? sameAs : undefined,
|
|
77
|
+
contactPoint: structuredData?.contactPoint,
|
|
78
|
+
address: structuredData?.address,
|
|
79
|
+
})}
|
|
80
|
+
/>
|
|
81
|
+
</>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
@@ -64,3 +64,17 @@ export const ARTICLE_BY_ID_QUERY = defineQuery(`
|
|
|
64
64
|
_updatedAt
|
|
65
65
|
}
|
|
66
66
|
`);
|
|
67
|
+
|
|
68
|
+
// Site-wide structured data (JSON-LD Organization + WebSite identity)
|
|
69
|
+
export const STRUCTURED_DATA_QUERY = defineQuery(`
|
|
70
|
+
*[_type == "structuredData"][0]{
|
|
71
|
+
name,
|
|
72
|
+
legalName,
|
|
73
|
+
description,
|
|
74
|
+
url,
|
|
75
|
+
"logo": logo.asset->url,
|
|
76
|
+
sameAs,
|
|
77
|
+
contactPoint,
|
|
78
|
+
address
|
|
79
|
+
}
|
|
80
|
+
`);
|
|
@@ -16,6 +16,10 @@ import { apiVersion, dataset, previewURL, projectId } from "./env";
|
|
|
16
16
|
import { schema } from "./schemas";
|
|
17
17
|
import { structure } from "./structure";
|
|
18
18
|
|
|
19
|
+
// Document types pinned to a single editable document.
|
|
20
|
+
const SINGLETON_TYPES = new Set(["structuredData"]);
|
|
21
|
+
const SINGLETON_ACTIONS = new Set(["publish", "discardChanges", "restore"]);
|
|
22
|
+
|
|
19
23
|
// Helper function for URL resolution
|
|
20
24
|
function resolveHref(documentType?: string, slug?: string): string | undefined {
|
|
21
25
|
switch (documentType) {
|
|
@@ -77,15 +81,27 @@ export default defineConfig({
|
|
|
77
81
|
media(),
|
|
78
82
|
],
|
|
79
83
|
document: {
|
|
80
|
-
actions: (prev) => {
|
|
84
|
+
actions: (prev, context) => {
|
|
81
85
|
const isProd = process.env.NODE_ENV === "production";
|
|
82
|
-
if (!isProd) return prev;
|
|
83
86
|
|
|
84
|
-
|
|
87
|
+
// Singletons can't be created or deleted from the document pane.
|
|
88
|
+
const filtered = SINGLETON_TYPES.has(context.schemaType)
|
|
89
|
+
? prev.filter(
|
|
90
|
+
(action) =>
|
|
91
|
+
action.action !== undefined &&
|
|
92
|
+
SINGLETON_ACTIONS.has(action.action),
|
|
93
|
+
)
|
|
94
|
+
: prev;
|
|
95
|
+
|
|
96
|
+
if (!isProd) return filtered;
|
|
97
|
+
|
|
98
|
+
return filtered.map((action) =>
|
|
85
99
|
action.action === "publish"
|
|
86
100
|
? createConfirmPublishAction(action)
|
|
87
101
|
: action,
|
|
88
102
|
);
|
|
89
103
|
},
|
|
104
|
+
newDocumentOptions: (prev) =>
|
|
105
|
+
prev.filter((option) => !SINGLETON_TYPES.has(option.templateId)),
|
|
90
106
|
},
|
|
91
107
|
});
|
|
@@ -13,9 +13,34 @@ import { link } from "./link";
|
|
|
13
13
|
import { metadata } from "./metadata";
|
|
14
14
|
import { navigation } from "./navigation";
|
|
15
15
|
import { richText } from "./richText";
|
|
16
|
+
import { structuredData } from "./structured-data";
|
|
17
|
+
import {
|
|
18
|
+
customJsonLd,
|
|
19
|
+
eventJsonLd,
|
|
20
|
+
faqJsonLd,
|
|
21
|
+
itemListJsonLd,
|
|
22
|
+
jobPostingJsonLd,
|
|
23
|
+
softwareApplicationJsonLd,
|
|
24
|
+
videoJsonLd,
|
|
25
|
+
} from "./structured-data-blocks";
|
|
16
26
|
|
|
17
27
|
// Re-export all schemas for convenience
|
|
18
|
-
export {
|
|
28
|
+
export {
|
|
29
|
+
article,
|
|
30
|
+
customJsonLd,
|
|
31
|
+
eventJsonLd,
|
|
32
|
+
example,
|
|
33
|
+
faqJsonLd,
|
|
34
|
+
itemListJsonLd,
|
|
35
|
+
jobPostingJsonLd,
|
|
36
|
+
link,
|
|
37
|
+
metadata,
|
|
38
|
+
navigation,
|
|
39
|
+
richText,
|
|
40
|
+
softwareApplicationJsonLd,
|
|
41
|
+
structuredData,
|
|
42
|
+
videoJsonLd,
|
|
43
|
+
};
|
|
19
44
|
|
|
20
45
|
// Schema collection for Sanity configuration
|
|
21
46
|
export const schema: { types: SchemaTypeDefinition[] } = {
|
|
@@ -25,11 +50,21 @@ export const schema: { types: SchemaTypeDefinition[] } = {
|
|
|
25
50
|
metadata,
|
|
26
51
|
richText,
|
|
27
52
|
|
|
53
|
+
// Page-level JSON-LD blocks (selectable in metadata)
|
|
54
|
+
faqJsonLd,
|
|
55
|
+
eventJsonLd,
|
|
56
|
+
videoJsonLd,
|
|
57
|
+
jobPostingJsonLd,
|
|
58
|
+
softwareApplicationJsonLd,
|
|
59
|
+
itemListJsonLd,
|
|
60
|
+
customJsonLd,
|
|
61
|
+
|
|
28
62
|
// Document types (content pages)
|
|
29
63
|
article,
|
|
30
64
|
example,
|
|
31
65
|
|
|
32
66
|
// Singleton types (one-off content)
|
|
33
67
|
navigation,
|
|
68
|
+
structuredData,
|
|
34
69
|
],
|
|
35
70
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { defineField, defineType } from "sanity";
|
|
1
|
+
import { defineArrayMember, defineField, defineType } from "sanity";
|
|
2
|
+
import { pageJsonLdMembers } from "./structured-data-blocks";
|
|
2
3
|
|
|
3
4
|
export const metadata = defineType({
|
|
4
5
|
name: "metadata",
|
|
@@ -64,5 +65,15 @@ export const metadata = defineType({
|
|
|
64
65
|
description: "Prevent search engines from indexing this page",
|
|
65
66
|
initialValue: false,
|
|
66
67
|
}),
|
|
68
|
+
defineField({
|
|
69
|
+
name: "jsonLd",
|
|
70
|
+
title: "Structured Data (JSON-LD)",
|
|
71
|
+
type: "array",
|
|
72
|
+
description:
|
|
73
|
+
"Page-level schema.org structured data emitted as JSON-LD. Pick a predefined type, or add a Custom JSON-LD block for anything not listed.",
|
|
74
|
+
of: pageJsonLdMembers.map((member) =>
|
|
75
|
+
defineArrayMember({ type: member.name }),
|
|
76
|
+
),
|
|
77
|
+
}),
|
|
67
78
|
],
|
|
68
79
|
});
|