canopycms-next 0.0.61 → 0.0.63
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/client.d.ts +10 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +10 -2
- package/dist/client.js.map +1 -1
- package/dist/client.test.d.ts +2 -0
- package/dist/client.test.d.ts.map +1 -0
- package/dist/client.test.js +29 -0
- package/dist/client.test.js.map +1 -0
- package/dist/context-wrapper.d.ts +74 -2
- package/dist/context-wrapper.d.ts.map +1 -1
- package/dist/context-wrapper.js +76 -25
- package/dist/context-wrapper.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/static.d.ts +122 -5
- package/dist/static.d.ts.map +1 -1
- package/dist/static.js +159 -2
- package/dist/static.js.map +1 -1
- package/package.json +5 -5
package/dist/client.d.ts
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
|
-
import type { CanopyClientConfig } from 'canopycms/client';
|
|
1
|
+
import type { CanopyClientConfig, CustomFieldRenderers } from 'canopycms/client';
|
|
2
2
|
/**
|
|
3
3
|
* Next.js-specific wrapper for CanopyEditorPage that automatically reads
|
|
4
4
|
* URL search params (branch, entry) using Next.js's useSearchParams hook.
|
|
5
5
|
*
|
|
6
|
+
* @param customRenderers Optional per-field-type render overrides, forwarded
|
|
7
|
+
* to `CanopyEditorPage`. Forwarded rather than dropped for a specific reason:
|
|
8
|
+
* Next is the primary target, so this wrapper is the entrypoint adopters
|
|
9
|
+
* actually import (it is what the README's Quick Start scaffolds). Accepting
|
|
10
|
+
* the argument only on the core `CanopyEditorPage` made the extension point
|
|
11
|
+
* unreachable from the path every adopter uses, which is an extension point
|
|
12
|
+
* in name only.
|
|
13
|
+
*
|
|
6
14
|
* @example
|
|
7
15
|
* ```tsx
|
|
8
16
|
* // app/edit/page.tsx
|
|
@@ -17,5 +25,5 @@ import type { CanopyClientConfig } from 'canopycms/client';
|
|
|
17
25
|
* }
|
|
18
26
|
* ```
|
|
19
27
|
*/
|
|
20
|
-
export declare const NextCanopyEditorPage: (config: CanopyClientConfig) => () => import("react/jsx-runtime").JSX.Element;
|
|
28
|
+
export declare const NextCanopyEditorPage: (config: CanopyClientConfig, customRenderers?: CustomFieldRenderers) => () => import("react/jsx-runtime").JSX.Element;
|
|
21
29
|
//# sourceMappingURL=client.d.ts.map
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AAEhF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,kBAAkB,EAC1B,kBAAkB,oBAAoB,kDAwBvC,CAAA"}
|
package/dist/client.js
CHANGED
|
@@ -7,6 +7,14 @@ import { CanopyEditorPage } from 'canopycms/client';
|
|
|
7
7
|
* Next.js-specific wrapper for CanopyEditorPage that automatically reads
|
|
8
8
|
* URL search params (branch, entry) using Next.js's useSearchParams hook.
|
|
9
9
|
*
|
|
10
|
+
* @param customRenderers Optional per-field-type render overrides, forwarded
|
|
11
|
+
* to `CanopyEditorPage`. Forwarded rather than dropped for a specific reason:
|
|
12
|
+
* Next is the primary target, so this wrapper is the entrypoint adopters
|
|
13
|
+
* actually import (it is what the README's Quick Start scaffolds). Accepting
|
|
14
|
+
* the argument only on the core `CanopyEditorPage` made the extension point
|
|
15
|
+
* unreachable from the path every adopter uses, which is an extension point
|
|
16
|
+
* in name only.
|
|
17
|
+
*
|
|
10
18
|
* @example
|
|
11
19
|
* ```tsx
|
|
12
20
|
* // app/edit/page.tsx
|
|
@@ -21,8 +29,8 @@ import { CanopyEditorPage } from 'canopycms/client';
|
|
|
21
29
|
* }
|
|
22
30
|
* ```
|
|
23
31
|
*/
|
|
24
|
-
export const NextCanopyEditorPage = (config) => {
|
|
25
|
-
const CorePage = CanopyEditorPage(config);
|
|
32
|
+
export const NextCanopyEditorPage = (config, customRenderers) => {
|
|
33
|
+
const CorePage = CanopyEditorPage(config, customRenderers);
|
|
26
34
|
// Reads URL search params. Next.js 15 requires useSearchParams() consumers to
|
|
27
35
|
// sit under a <Suspense> boundary, otherwise static rendering errors with
|
|
28
36
|
// "useSearchParams() should be wrapped in a suspense boundary". We provide the
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAChC,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAGnD
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAChC,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAGnD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAClC,MAA0B,EAC1B,eAAsC,EACtC,EAAE;IACF,MAAM,QAAQ,GAAG,gBAAgB,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;IAE1D,8EAA8E;IAC9E,0EAA0E;IAC1E,+EAA+E;IAC/E,4EAA4E;IAC5E,SAAS,sBAAsB;QAC7B,MAAM,eAAe,GAAG,eAAe,EAAE,CAAA;QACzC,MAAM,YAAY,GAAG;YACnB,MAAM,EAAE,eAAe,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,SAAS;YAClD,KAAK,EAAE,eAAe,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,SAAS;SACjD,CAAA;QACD,OAAO,KAAC,QAAQ,IAAC,YAAY,EAAE,YAAY,GAAI,CAAA;IACjD,CAAC;IAED,OAAO,SAAS,cAAc;QAC5B,OAAO,CACL,KAAC,QAAQ,IAAC,QAAQ,EAAE,IAAI,YACtB,KAAC,sBAAsB,KAAG,GACjB,CACZ,CAAA;IACH,CAAC,CAAA;AACH,CAAC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.test.d.ts","sourceRoot":"","sources":["../src/client.test.tsx"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
// Mocked so this stays a node-environment test: the factory under test calls
|
|
3
|
+
// CanopyEditorPage at CALL time (not render time), so asserting what it was
|
|
4
|
+
// handed needs no DOM, no jsdom and no testing-library.
|
|
5
|
+
const canopyEditorPage = vi.fn(() => () => null);
|
|
6
|
+
vi.mock('canopycms/client', () => ({
|
|
7
|
+
CanopyEditorPage: (...args) => canopyEditorPage(...args),
|
|
8
|
+
}));
|
|
9
|
+
vi.mock('next/navigation', () => ({ useSearchParams: () => new URLSearchParams() }));
|
|
10
|
+
const { NextCanopyEditorPage } = await import('./client');
|
|
11
|
+
const config = { apiBase: '/api/canopycms' };
|
|
12
|
+
describe('NextCanopyEditorPage', () => {
|
|
13
|
+
it('forwards customRenderers to CanopyEditorPage', () => {
|
|
14
|
+
// Next is the primary target, so this wrapper is the entrypoint adopters
|
|
15
|
+
// import. Accepting `customRenderers` only on the core CanopyEditorPage
|
|
16
|
+
// left the extension point unreachable from the path every adopter uses
|
|
17
|
+
// - and silently, since an ignored extra argument is not a type error at
|
|
18
|
+
// the call site.
|
|
19
|
+
const customRenderers = { number: () => null };
|
|
20
|
+
NextCanopyEditorPage(config, customRenderers);
|
|
21
|
+
expect(canopyEditorPage).toHaveBeenCalledWith(config, customRenderers);
|
|
22
|
+
});
|
|
23
|
+
it('still works with no customRenderers (the scaffolded call shape)', () => {
|
|
24
|
+
canopyEditorPage.mockClear();
|
|
25
|
+
NextCanopyEditorPage(config);
|
|
26
|
+
expect(canopyEditorPage).toHaveBeenCalledWith(config, undefined);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
//# sourceMappingURL=client.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.test.js","sourceRoot":"","sources":["../src/client.test.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AAEjD,6EAA6E;AAC7E,4EAA4E;AAC5E,wDAAwD;AACxD,MAAM,gBAAgB,GAAG,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAA;AAChD,EAAE,CAAC,IAAI,CAAC,kBAAkB,EAAE,GAAG,EAAE,CAAC,CAAC;IACjC,gBAAgB,EAAE,CAAC,GAAG,IAAe,EAAE,EAAE,CAAC,gBAAgB,CAAC,GAAI,IAAW,CAAC;CAC5E,CAAC,CAAC,CAAA;AACH,EAAE,CAAC,IAAI,CAAC,iBAAiB,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,eAAe,EAAE,GAAG,EAAE,CAAC,IAAI,eAAe,EAAE,EAAE,CAAC,CAAC,CAAA;AAEpF,MAAM,EAAE,oBAAoB,EAAE,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,CAAA;AAEzD,MAAM,MAAM,GAAG,EAAE,OAAO,EAAE,gBAAgB,EAAW,CAAA;AAErD,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IACpC,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACtD,yEAAyE;QACzE,wEAAwE;QACxE,wEAAwE;QACxE,yEAAyE;QACzE,iBAAiB;QACjB,MAAM,eAAe,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE,CAAA;QAE9C,oBAAoB,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;QAE7C,MAAM,CAAC,gBAAgB,CAAC,CAAC,oBAAoB,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;IACxE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,iEAAiE,EAAE,GAAG,EAAE;QACzE,gBAAgB,CAAC,SAAS,EAAE,CAAA;QAE5B,oBAAoB,CAAC,MAAM,CAAC,CAAA;QAE5B,MAAM,CAAC,gBAAgB,CAAC,CAAC,oBAAoB,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;IAClE,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
|
|
@@ -1,12 +1,43 @@
|
|
|
1
|
-
import { type CanopyContext, type CanopyBuildContext, type CanopyServices } from 'canopycms/server';
|
|
1
|
+
import { type CanopyContext, type CanopyBuildContext, type CanopyServices, type SeoFieldLocation } from 'canopycms/server';
|
|
2
2
|
import type { CanopyConfig, AuthPlugin, FieldConfig } from 'canopycms';
|
|
3
|
+
import type { Metadata, MetadataRoute } from 'next';
|
|
3
4
|
import { createCanopyCatchAllHandler } from './adapter';
|
|
4
|
-
import { type GenerateContentStaticParamsOptions } from './static';
|
|
5
|
+
import { type EntryToMetadataOptions, type GenerateContentSitemapOptions, type GenerateContentStaticParamsOptions } from './static';
|
|
6
|
+
/**
|
|
7
|
+
* Merge a context-wide default SEO field location (`createNextCanopyContext({ seo })`) with a
|
|
8
|
+
* per-call override.
|
|
9
|
+
*
|
|
10
|
+
* `generateContentSitemap`'s `noindex` exclusion and `entryToMetadata`'s field extraction/`noindex`
|
|
11
|
+
* read must agree on where the SEO fields live, or the two surfaces drift: an entry can end up
|
|
12
|
+
* `noindex` on the page while the sitemap still advertises its URL (or the reverse), because each
|
|
13
|
+
* call site independently forgot (or mis-set) `group`/`fields`. Both bound helpers below call this
|
|
14
|
+
* SAME function with the SAME context-wide default, which is what makes agreement structural
|
|
15
|
+
* rather than a matter of adopter discipline.
|
|
16
|
+
*
|
|
17
|
+
* Only keys the caller actually supplies override the default — an omitted (or explicitly
|
|
18
|
+
* `undefined`) `fields`/`group` falls through to the context-wide value, so setting a default once
|
|
19
|
+
* and never repeating it per call is the common case. A plain `{ ...contextDefault, ...callOverride
|
|
20
|
+
* }` would NOT have this property: `entryToMetadata`'s options type always has `fields`/`group`
|
|
21
|
+
* keys, so a caller who omits them still produces an object whose `fields`/`group` are present but
|
|
22
|
+
* `undefined`, and spreading that would silently clobber the default. Checking each key explicitly
|
|
23
|
+
* avoids that trap. Exported for direct unit testing.
|
|
24
|
+
*/
|
|
25
|
+
export declare function mergeSeoFieldLocation(contextDefault: SeoFieldLocation | undefined, callOverride: SeoFieldLocation | undefined): SeoFieldLocation;
|
|
5
26
|
export interface NextCanopyOptions {
|
|
6
27
|
config: CanopyConfig;
|
|
7
28
|
/** Auth plugin for user authentication. Optional for static deployments (deployedAs: 'static'). */
|
|
8
29
|
authPlugin?: AuthPlugin;
|
|
9
30
|
entrySchemaRegistry: Record<string, readonly FieldConfig[]>;
|
|
31
|
+
/**
|
|
32
|
+
* Where the SEO fields live (`fields` name overrides, and/or a nested `group`). Set once here
|
|
33
|
+
* and it is shared by BOTH the returned `generateContentSitemap`'s `noindex` exclusion and the
|
|
34
|
+
* returned `entryToMetadata`'s field extraction, so the two surfaces cannot independently forget
|
|
35
|
+
* or mis-set it and drift apart — see `mergeSeoFieldLocation`. Omit for the flat (inline-group)
|
|
36
|
+
* convention, which is the default on both the schema side (`defineSeoFieldGroup()`) and here.
|
|
37
|
+
* A per-call `seo`/`fields`/`group` option on either helper still overrides this default for
|
|
38
|
+
* that one call.
|
|
39
|
+
*/
|
|
40
|
+
seo?: SeoFieldLocation;
|
|
10
41
|
}
|
|
11
42
|
/**
|
|
12
43
|
* Wrap a build context so its operations throw if misused at request time on a production server.
|
|
@@ -55,6 +86,25 @@ export interface NextCanopyContextResult {
|
|
|
55
86
|
readByUrlPath: CanopyContext['readByUrlPath'];
|
|
56
87
|
/** Phase-selecting `read` (build context at build, runtime context at request) — counterpart to readByUrlPath. */
|
|
57
88
|
read: CanopyContext['read'];
|
|
89
|
+
/**
|
|
90
|
+
* Phase-selecting `listEntries` — every entry under `rootPath` in a single filesystem
|
|
91
|
+
* pass, each with its `urlPath`, `slug`, `entryType`, `data` and `schema`. Use it instead
|
|
92
|
+
* of "enumerate paths, then read each one", which is an N+1 over the content tree and
|
|
93
|
+
* invites URL-reconstruction bugs; the returned `urlPath` round-trips through
|
|
94
|
+
* `readByUrlPath` by construction, including for multi-segment paths.
|
|
95
|
+
*
|
|
96
|
+
* ```ts
|
|
97
|
+
* const articles = await listEntries<ArticleContent>({
|
|
98
|
+
* filter: (e) => e.entryType === 'article',
|
|
99
|
+
* })
|
|
100
|
+
* ```
|
|
101
|
+
*
|
|
102
|
+
* At build time this reads filesystem-direct; at request time it uses the branch-aware,
|
|
103
|
+
* **ACL-enforced** runtime context, so entries the current user cannot read are omitted.
|
|
104
|
+
* Note it takes no `branch` option — see `CanopyContext['listEntries']` for the
|
|
105
|
+
* base-branch pinning caveat in prod.
|
|
106
|
+
*/
|
|
107
|
+
listEntries: CanopyContext['listEntries'];
|
|
58
108
|
/**
|
|
59
109
|
* Build the array Next's `generateStaticParams` expects from CanopyCMS content. Enumeration-only
|
|
60
110
|
* (reads the set of routable paths, never entry content) and closes over the build context, so your
|
|
@@ -62,6 +112,28 @@ export interface NextCanopyContextResult {
|
|
|
62
112
|
* `[slug]` route, or the default catch-all for `[...slug]`.
|
|
63
113
|
*/
|
|
64
114
|
generateContentStaticParams: (options?: GenerateContentStaticParamsOptions) => Promise<Array<Record<string, string | string[]>>>;
|
|
115
|
+
/**
|
|
116
|
+
* Build `app/sitemap.ts`'s `MetadataRoute.Sitemap` from CanopyCMS content. Includes EVERY
|
|
117
|
+
* routable entry type by default and excludes `noindex` entries through the same predicate
|
|
118
|
+
* `entryToMetadata` uses for `robots`. Bound to the build context, so your `sitemap.ts` never
|
|
119
|
+
* imports the admin `getCanopyForBuild`.
|
|
120
|
+
*
|
|
121
|
+
* Reads the SEO field location from `createNextCanopyContext({ seo })` by default (shared with
|
|
122
|
+
* `entryToMetadata` below via `mergeSeoFieldLocation` — see that function's doc). Pass `seo` on a
|
|
123
|
+
* given call to override just that call.
|
|
124
|
+
*/
|
|
125
|
+
generateContentSitemap: (options: GenerateContentSitemapOptions) => Promise<MetadataRoute.Sitemap>;
|
|
126
|
+
/**
|
|
127
|
+
* Map an entry's SEO fields onto a Next `Metadata` for `generateMetadata`. Re-exposed here so a
|
|
128
|
+
* page module has one CanopyCMS import (your `lib/canopy.ts`) rather than two.
|
|
129
|
+
*
|
|
130
|
+
* Reads the SAME SEO field location as `generateContentSitemap` above by default (the
|
|
131
|
+
* `createNextCanopyContext({ seo })` value); the two are bound from one context-wide default so
|
|
132
|
+
* they cannot silently disagree about where `noindex` (or a renamed field) lives. Pass
|
|
133
|
+
* `fields`/`group` on a given call to override just that call. This is a pure
|
|
134
|
+
* mapping and touches no context.
|
|
135
|
+
*/
|
|
136
|
+
entryToMetadata: (entryData: unknown, options?: EntryToMetadataOptions) => Metadata;
|
|
65
137
|
/** API catch-all route handler */
|
|
66
138
|
handler: ReturnType<typeof createCanopyCatchAllHandler>;
|
|
67
139
|
/** Underlying services (rarely needed directly) */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context-wrapper.d.ts","sourceRoot":"","sources":["../src/context-wrapper.ts"],"names":[],"mappings":"AAGA,OAAO,EAIL,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,cAAc,
|
|
1
|
+
{"version":3,"file":"context-wrapper.d.ts","sourceRoot":"","sources":["../src/context-wrapper.ts"],"names":[],"mappings":"AAGA,OAAO,EAIL,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,cAAc,EAMnB,KAAK,gBAAgB,EAMtB,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAc,WAAW,EAAE,MAAM,WAAW,CAAA;AAGlF,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,MAAM,CAAA;AACnD,OAAO,EAAE,2BAA2B,EAAE,MAAM,WAAW,CAAA;AACvD,OAAO,EAIL,KAAK,sBAAsB,EAC3B,KAAK,6BAA6B,EAClC,KAAK,kCAAkC,EACxC,MAAM,UAAU,CAAA;AAEjB;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,qBAAqB,CACnC,cAAc,EAAE,gBAAgB,GAAG,SAAS,EAC5C,YAAY,EAAE,gBAAgB,GAAG,SAAS,GACzC,gBAAgB,CAKlB;AAiCD,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,YAAY,CAAA;IACpB,mGAAmG;IACnG,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,WAAW,EAAE,CAAC,CAAA;IAC3D;;;;;;;;OAQG;IACH,GAAG,CAAC,EAAE,gBAAgB,CAAA;CACvB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,kBAAkB,EAC5B,MAAM,EAAE,YAAY,GACnB,kBAAkB,CA4CpB;AAED,MAAM,WAAW,uBAAuB;IACtC,8GAA8G;IAC9G,SAAS,EAAE,MAAM,OAAO,CAAC,aAAa,CAAC,CAAA;IACvC;;;;;;;;;;;;;OAaG;IACH,iBAAiB,EAAE,MAAM,OAAO,CAAC,kBAAkB,CAAC,CAAA;IACpD;;;;;;;OAOG;IACH,aAAa,EAAE,aAAa,CAAC,eAAe,CAAC,CAAA;IAC7C,kHAAkH;IAClH,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;IAC3B;;;;;;;;;;;;;;;;;OAiBG;IACH,WAAW,EAAE,aAAa,CAAC,aAAa,CAAC,CAAA;IACzC;;;;;OAKG;IACH,2BAA2B,EAAE,CAC3B,OAAO,CAAC,EAAE,kCAAkC,KACzC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC,CAAC,CAAA;IACtD;;;;;;;;;OASG;IACH,sBAAsB,EAAE,CAAC,OAAO,EAAE,6BAA6B,KAAK,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,CAAA;IAClG;;;;;;;;;OASG;IACH,eAAe,EAAE,CAAC,SAAS,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,sBAAsB,KAAK,QAAQ,CAAA;IACnF,kCAAkC;IAClC,OAAO,EAAE,UAAU,CAAC,OAAO,2BAA2B,CAAC,CAAA;IACvD,mDAAmD;IACnD,QAAQ,EAAE,cAAc,CAAA;CACzB;AAED;;;;;;;;GAQG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,uBAAuB,CAAC,CAqNlC"}
|
package/dist/context-wrapper.js
CHANGED
|
@@ -1,13 +1,38 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { cache } from 'react';
|
|
3
3
|
import { headers } from 'next/headers';
|
|
4
|
-
import { createCanopyContext, isBuildMode, startDevContentWatcher, createCanopyServices, createAssetStore, operatingStrategy,
|
|
5
|
-
import { authResultToCanopyUser } from 'canopycms';
|
|
4
|
+
import { createCanopyContext, isBuildMode, startDevContentWatcher, createCanopyServices, createAssetStore, operatingStrategy, resolveCanopyUser, STATIC_DEPLOY_USER, } from 'canopycms/server';
|
|
6
5
|
import { assertAuthPluginAllowedForMode } from 'canopycms/auth';
|
|
7
6
|
import { CachingAuthPlugin, FileBasedAuthCache } from 'canopycms/auth/cache';
|
|
8
7
|
import { createCanopyCatchAllHandler } from './adapter';
|
|
9
|
-
import { collectStaticParams } from './static';
|
|
10
|
-
|
|
8
|
+
import { collectStaticParams, entryToMetadata as entryToMetadataCore, generateContentSitemap, } from './static';
|
|
9
|
+
/**
|
|
10
|
+
* Merge a context-wide default SEO field location (`createNextCanopyContext({ seo })`) with a
|
|
11
|
+
* per-call override.
|
|
12
|
+
*
|
|
13
|
+
* `generateContentSitemap`'s `noindex` exclusion and `entryToMetadata`'s field extraction/`noindex`
|
|
14
|
+
* read must agree on where the SEO fields live, or the two surfaces drift: an entry can end up
|
|
15
|
+
* `noindex` on the page while the sitemap still advertises its URL (or the reverse), because each
|
|
16
|
+
* call site independently forgot (or mis-set) `group`/`fields`. Both bound helpers below call this
|
|
17
|
+
* SAME function with the SAME context-wide default, which is what makes agreement structural
|
|
18
|
+
* rather than a matter of adopter discipline.
|
|
19
|
+
*
|
|
20
|
+
* Only keys the caller actually supplies override the default — an omitted (or explicitly
|
|
21
|
+
* `undefined`) `fields`/`group` falls through to the context-wide value, so setting a default once
|
|
22
|
+
* and never repeating it per call is the common case. A plain `{ ...contextDefault, ...callOverride
|
|
23
|
+
* }` would NOT have this property: `entryToMetadata`'s options type always has `fields`/`group`
|
|
24
|
+
* keys, so a caller who omits them still produces an object whose `fields`/`group` are present but
|
|
25
|
+
* `undefined`, and spreading that would silently clobber the default. Checking each key explicitly
|
|
26
|
+
* avoids that trap. Exported for direct unit testing.
|
|
27
|
+
*/
|
|
28
|
+
export function mergeSeoFieldLocation(contextDefault, callOverride) {
|
|
29
|
+
const merged = { ...contextDefault };
|
|
30
|
+
if (callOverride?.fields !== undefined)
|
|
31
|
+
merged.fields = callOverride.fields;
|
|
32
|
+
if (callOverride?.group !== undefined)
|
|
33
|
+
merged.group = callOverride.group;
|
|
34
|
+
return merged;
|
|
35
|
+
}
|
|
11
36
|
let warnedStaticMode = false;
|
|
12
37
|
/**
|
|
13
38
|
* Stub auth plugin for static deployments where no real auth is needed.
|
|
@@ -147,31 +172,29 @@ export async function createNextCanopyContext(options) {
|
|
|
147
172
|
if (options.config.mode === 'dev' && !isBuildMode()) {
|
|
148
173
|
startDevContentWatcher(services, { mode: options.config.dev?.contentSync });
|
|
149
174
|
}
|
|
150
|
-
// User extractor: passes Next.js headers to auth plugin,
|
|
175
|
+
// User extractor: passes Next.js headers to auth plugin, resolves internal
|
|
176
|
+
// groups, applies authorization. Delegates to the shared
|
|
177
|
+
// "authenticate -> load internal groups -> merge" pipeline
|
|
178
|
+
// (resolveCanopyUser, in canopycms core) so this stays in lockstep with
|
|
179
|
+
// http/handler.ts's API-layer equivalent rather than re-implementing it —
|
|
180
|
+
// a previous copy here loaded groups from the base branch content clone
|
|
181
|
+
// (via loadBranchContext), which nothing in the product ever writes
|
|
182
|
+
// groups.json into, so group-based privileges never took effect.
|
|
183
|
+
//
|
|
184
|
+
// No base-branch context resolution here (unlike the previous copy):
|
|
185
|
+
// that call existed only to source groups.json from the (wrong) content
|
|
186
|
+
// branch. Actual content reads (buildContentTree/listEntries/read) already
|
|
187
|
+
// provision the base/active branch themselves via loadOrCreateBranchContext
|
|
188
|
+
// (see context.ts's resolveSchemaContext), so dropping it here removes a
|
|
189
|
+
// redundant per-request EFS round-trip rather than losing provisioning.
|
|
151
190
|
const extractUser = async () => {
|
|
152
191
|
const headersList = await headers();
|
|
153
192
|
const authResult = await authPlugin.authenticate(headersList);
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
branchName: baseBranch,
|
|
159
|
-
mode: operatingMode,
|
|
193
|
+
return resolveCanopyUser(authResult, {
|
|
194
|
+
getSettingsBranchRoot: services.getSettingsBranchRoot,
|
|
195
|
+
mode: services.config.mode ?? 'dev',
|
|
196
|
+
bootstrapAdminIds: services.bootstrapAdminIds,
|
|
160
197
|
});
|
|
161
|
-
const internalGroups = mainBranchContext
|
|
162
|
-
? await loadInternalGroups(mainBranchContext.branchRoot, operatingMode, services.bootstrapAdminIds).catch((err) => {
|
|
163
|
-
console.warn('CanopyCMS: Failed to load internal groups from main branch:', err);
|
|
164
|
-
return [];
|
|
165
|
-
})
|
|
166
|
-
: [];
|
|
167
|
-
if (!warnedNoAdmins && Array.isArray(internalGroups)) {
|
|
168
|
-
const adminsGroup = internalGroups.find((g) => g.id === 'Admins');
|
|
169
|
-
if (!adminsGroup || adminsGroup.members.length === 0) {
|
|
170
|
-
console.warn('CanopyCMS: No admin users configured. Set CANOPY_BOOTSTRAP_ADMIN_IDS or add members to the Admins group.');
|
|
171
|
-
}
|
|
172
|
-
warnedNoAdmins = true;
|
|
173
|
-
}
|
|
174
|
-
return authResultToCanopyUser(authResult, services.bootstrapAdminIds, internalGroups);
|
|
175
198
|
};
|
|
176
199
|
// Create core context with pre-created services (framework-agnostic)
|
|
177
200
|
const coreContext = createCanopyContext({
|
|
@@ -213,11 +236,36 @@ export async function createNextCanopyContext(options) {
|
|
|
213
236
|
const ctx = isBuildMode() ? await getCanopyForBuild() : await getCanopy();
|
|
214
237
|
return ctx.read(input);
|
|
215
238
|
};
|
|
239
|
+
const listEntries = async (opts) => {
|
|
240
|
+
const ctx = isBuildMode() ? await getCanopyForBuild() : await getCanopy();
|
|
241
|
+
return ctx.listEntries(opts);
|
|
242
|
+
};
|
|
216
243
|
// Enumeration-only static-params helper, bound to the (guarded) build context so page modules don't
|
|
217
244
|
// import the admin context just to list paths. generateStaticParams is build-only, so this is safe.
|
|
218
245
|
const generateContentStaticParams = async (options = {}) => {
|
|
219
246
|
return collectStaticParams(await getCanopyForBuild(), options);
|
|
220
247
|
};
|
|
248
|
+
// Sitemap generation reads entry CONTENT (to apply the noindex predicate), so unlike
|
|
249
|
+
// generateContentStaticParams it is not enumeration-only — but sitemap.ts is build-only in a
|
|
250
|
+
// static export, and on a prod `server` deployment guardBuildContext throws if it is reached at
|
|
251
|
+
// request time. Binding it here keeps the admin context out of the route module either way.
|
|
252
|
+
//
|
|
253
|
+
// Both this and boundEntryToMetadata below feed options.seo (the context-wide default set on
|
|
254
|
+
// createNextCanopyContext) through the SAME mergeSeoFieldLocation call, so the noindex exclusion
|
|
255
|
+
// here and entryToMetadata's noindex/field read cannot independently drift out of sync.
|
|
256
|
+
const boundGenerateContentSitemap = async (callOptions) => {
|
|
257
|
+
return generateContentSitemap(await getCanopyForBuild(), {
|
|
258
|
+
...callOptions,
|
|
259
|
+
seo: mergeSeoFieldLocation(options.seo, callOptions.seo),
|
|
260
|
+
});
|
|
261
|
+
};
|
|
262
|
+
const boundEntryToMetadata = (entryData, callOptions = {}) => {
|
|
263
|
+
const seoLocation = mergeSeoFieldLocation(options.seo, {
|
|
264
|
+
fields: callOptions.fields,
|
|
265
|
+
group: callOptions.group,
|
|
266
|
+
});
|
|
267
|
+
return entryToMetadataCore(entryData, { ...callOptions, ...seoLocation });
|
|
268
|
+
};
|
|
221
269
|
// Create API handler using same services
|
|
222
270
|
const handler = createCanopyCatchAllHandler({
|
|
223
271
|
...options,
|
|
@@ -230,7 +278,10 @@ export async function createNextCanopyContext(options) {
|
|
|
230
278
|
getCanopyForBuild,
|
|
231
279
|
readByUrlPath,
|
|
232
280
|
read,
|
|
281
|
+
listEntries,
|
|
233
282
|
generateContentStaticParams,
|
|
283
|
+
generateContentSitemap: boundGenerateContentSitemap,
|
|
284
|
+
entryToMetadata: boundEntryToMetadata,
|
|
234
285
|
handler,
|
|
235
286
|
services,
|
|
236
287
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context-wrapper.js","sourceRoot":"","sources":["../src/context-wrapper.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAA;AAC7B,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtC,OAAO,EACL,mBAAmB,EACnB,WAAW,EACX,sBAAsB,
|
|
1
|
+
{"version":3,"file":"context-wrapper.js","sourceRoot":"","sources":["../src/context-wrapper.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { createCanopyCatchAllHandler, wrapNextRequest, type CanopyNextOptions } from './adapter';
|
|
2
2
|
export { createNextCanopyContext, type NextCanopyOptions, type NextCanopyContextResult, } from './context-wrapper';
|
|
3
|
-
export { collectStaticParams, type GenerateContentStaticParamsOptions } from './static';
|
|
3
|
+
export { collectStaticParams, generateContentSitemap, entryToMetadata, type GenerateContentStaticParamsOptions, type GenerateContentSitemapOptions, type EntryToMetadataOptions, type SitemapExtraUrl, } from './static';
|
|
4
4
|
export { createMockAuthPlugin, createRejectingAuthPlugin } from './test-utils';
|
|
5
5
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,eAAe,EAAE,KAAK,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAEhG,OAAO,EACL,uBAAuB,EACvB,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,GAC7B,MAAM,mBAAmB,CAAA;AAE1B,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,eAAe,EAAE,KAAK,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAEhG,OAAO,EACL,uBAAuB,EACvB,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,GAC7B,MAAM,mBAAmB,CAAA;AAE1B,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,eAAe,EACf,KAAK,kCAAkC,EACvC,KAAK,6BAA6B,EAClC,KAAK,sBAAsB,EAC3B,KAAK,eAAe,GACrB,MAAM,UAAU,CAAA;AAEjB,OAAO,EAAE,oBAAoB,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { createCanopyCatchAllHandler, wrapNextRequest } from './adapter';
|
|
2
2
|
export { createNextCanopyContext, } from './context-wrapper';
|
|
3
|
-
export { collectStaticParams } from './static';
|
|
3
|
+
export { collectStaticParams, generateContentSitemap, entryToMetadata, } from './static';
|
|
4
4
|
export { createMockAuthPlugin, createRejectingAuthPlugin } from './test-utils';
|
|
5
5
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,eAAe,EAA0B,MAAM,WAAW,CAAA;AAEhG,OAAO,EACL,uBAAuB,GAGxB,MAAM,mBAAmB,CAAA;AAE1B,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,eAAe,EAA0B,MAAM,WAAW,CAAA;AAEhG,OAAO,EACL,uBAAuB,GAGxB,MAAM,mBAAmB,CAAA;AAE1B,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,eAAe,GAKhB,MAAM,UAAU,CAAA;AAEjB,OAAO,EAAE,oBAAoB,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAA"}
|
package/dist/static.d.ts
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Metadata, MetadataRoute } from 'next';
|
|
2
|
+
import { type CanopyBuildContext, type CollectStaticPathsOptions, type ExtractSeoFieldsOptions, type RoutableEntry, type SeoFieldLocation, type SeoOgType } from 'canopycms/server';
|
|
2
3
|
/**
|
|
3
|
-
* Next.js static-export helpers built on the framework-agnostic core in `canopycms/server
|
|
4
|
+
* Next.js static-export helpers built on the framework-agnostic core in `canopycms/server`:
|
|
5
|
+
* static params (`collectStaticParams`), the content sitemap (`generateContentSitemap`), and
|
|
6
|
+
* per-entry metadata (`entryToMetadata`).
|
|
4
7
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
+
* The last two ship together on purpose. `noindex` has to suppress a page in BOTH surfaces —
|
|
9
|
+
* `robots: { index: false }` on the page and absence from the sitemap — and both read it through
|
|
10
|
+
* the same core `isNoindexEntry` predicate, so they cannot disagree about which pages are
|
|
11
|
+
* advertised.
|
|
12
|
+
*
|
|
13
|
+
* `robots.txt` is out of scope: it is a handful of static lines with no CanopyCMS content behind
|
|
14
|
+
* it. Write `app/robots.ts` yourself, and point its `sitemap` at the route below.
|
|
8
15
|
*/
|
|
9
16
|
export interface GenerateContentStaticParamsOptions extends CollectStaticPathsOptions {
|
|
10
17
|
/** Route param name. Default 'slug'. */
|
|
@@ -36,4 +43,114 @@ export interface GenerateContentStaticParamsOptions extends CollectStaticPathsOp
|
|
|
36
43
|
* `[[...slug]]`, otherwise exclude it via `options.filter` (e.g. `(e) => e.segments.length > 0`).
|
|
37
44
|
*/
|
|
38
45
|
export declare function collectStaticParams(buildCtx: Pick<CanopyBuildContext, 'listEntries'>, options?: GenerateContentStaticParamsOptions): Promise<Array<Record<string, string | string[]>>>;
|
|
46
|
+
type SitemapItem = MetadataRoute.Sitemap[number];
|
|
47
|
+
/** A URL with no CanopyCMS entry behind it (a hand-written app route, a feed). */
|
|
48
|
+
export interface SitemapExtraUrl {
|
|
49
|
+
/** Site-relative path ('/blog') or an absolute URL. Trailing-slash rules apply to the former. */
|
|
50
|
+
path: string;
|
|
51
|
+
lastModified?: Date | string;
|
|
52
|
+
changeFrequency?: SitemapItem['changeFrequency'];
|
|
53
|
+
priority?: number;
|
|
54
|
+
}
|
|
55
|
+
export interface GenerateContentSitemapOptions {
|
|
56
|
+
/**
|
|
57
|
+
* Site origin, e.g. `https://example.com`. Required: a sitemap must carry absolute URLs, or
|
|
58
|
+
* search engines reject the whole file. Enforced — `generateContentSitemap` throws if this
|
|
59
|
+
* isn't an absolute URL (no scheme, or empty).
|
|
60
|
+
*/
|
|
61
|
+
siteUrl: string;
|
|
62
|
+
/**
|
|
63
|
+
* Emit site-relative URLs with a trailing slash (`/contact/` rather than `/contact`).
|
|
64
|
+
*
|
|
65
|
+
* **Explicit on purpose.** CanopyCMS cannot see your `next.config.ts`, so it cannot know
|
|
66
|
+
* whether your site serves `trailingSlash: true`. Set this to match, or the sitemap advertises
|
|
67
|
+
* URLs that redirect — a real adopter shipped exactly that mismatch.
|
|
68
|
+
*/
|
|
69
|
+
trailingSlash?: boolean;
|
|
70
|
+
/** Scope to a collection logical path (e.g. 'content/posts'). Defaults to the whole content root. */
|
|
71
|
+
rootPath?: string;
|
|
72
|
+
/** Where the SEO fields live, when they aren't the flat defaults. Drives the `noindex` read. */
|
|
73
|
+
seo?: SeoFieldLocation;
|
|
74
|
+
/**
|
|
75
|
+
* Drop entries from the sitemap. Applied ON TOP of the `noindex` exclusion, which is not
|
|
76
|
+
* optional. Use for structural URLs that shouldn't be advertised (e.g. a bare collection index).
|
|
77
|
+
*/
|
|
78
|
+
exclude?: (entry: RoutableEntry) => boolean;
|
|
79
|
+
/**
|
|
80
|
+
* `<lastmod>` per entry. Defaults to the entry's `updatedAt`.
|
|
81
|
+
*
|
|
82
|
+
* **`updatedAt` is filesystem mtime, not an editorial timestamp.** A fresh CI clone resets every
|
|
83
|
+
* file's mtime to checkout time, so on a clean build agent the default dates every URL to the
|
|
84
|
+
* moment the tree was cloned. Treat the default as "changed since the last build" at best; if
|
|
85
|
+
* you need a truthful `<lastmod>`, supply one here (a content date field, or git commit time)
|
|
86
|
+
* or return `undefined` to omit the element entirely, which is better than asserting a date you
|
|
87
|
+
* cannot stand behind.
|
|
88
|
+
*/
|
|
89
|
+
lastModified?: (entry: RoutableEntry) => Date | string | undefined;
|
|
90
|
+
/** `<priority>` per entry. Omitted from the URL when this returns undefined (the default). */
|
|
91
|
+
priority?: (entry: RoutableEntry) => number | undefined;
|
|
92
|
+
/** URLs with no entry behind them — hand-written app routes, feeds — appended to the result. */
|
|
93
|
+
extraUrls?: SitemapExtraUrl[];
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Build Next's `MetadataRoute.Sitemap` from CanopyCMS content.
|
|
97
|
+
*
|
|
98
|
+
* **Every routable entry type is included by default.** There is no list of "sitemap-able" entry
|
|
99
|
+
* types to keep in sync, and that is the whole design: a hand-rolled sitemap that enumerates a
|
|
100
|
+
* remembered list of entry types omits whichever type nobody added, ships green, and takes the
|
|
101
|
+
* missing pages out of search results silently. Omitting a URL here requires an explicit
|
|
102
|
+
* `exclude` predicate or a `noindex` flag on the entry.
|
|
103
|
+
*
|
|
104
|
+
* `noindex` entries are excluded, via the same core `isNoindexEntry` predicate that drives
|
|
105
|
+
* `robots: { index: false }` in `entryToMetadata`. They are still BUILT — enumeration
|
|
106
|
+
* (`generateContentStaticParams`) does not filter them, so the page exists for anyone holding
|
|
107
|
+
* the link; it just isn't advertised.
|
|
108
|
+
*
|
|
109
|
+
* `changeFrequency` is not emitted for entries: a blanket value asserted for every URL carries no
|
|
110
|
+
* information, and search engines say they ignore it. Set it per-URL via `extraUrls` if you want it.
|
|
111
|
+
*
|
|
112
|
+
* Output is stably ordered ('/' first, then alphabetical) so a rebuild doesn't reshuffle the file.
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* // app/sitemap.ts
|
|
116
|
+
* export const dynamic = 'force-static' // required for output: 'export'
|
|
117
|
+
* export default () => contentSitemap({ siteUrl: 'https://example.com', trailingSlash: true })
|
|
118
|
+
*/
|
|
119
|
+
export declare function generateContentSitemap(buildCtx: Pick<CanopyBuildContext, 'listEntries'>, options: GenerateContentSitemapOptions): Promise<MetadataRoute.Sitemap>;
|
|
120
|
+
export interface EntryToMetadataOptions extends ExtractSeoFieldsOptions {
|
|
121
|
+
/** Route path for this entry — the canonical URL when the entry sets none. */
|
|
122
|
+
path?: string;
|
|
123
|
+
/**
|
|
124
|
+
* Site origin for resolving relative canonical / OG image URLs. Omit to leave them relative and
|
|
125
|
+
* let Next's `metadataBase` resolve them.
|
|
126
|
+
*/
|
|
127
|
+
siteUrl?: string;
|
|
128
|
+
/** Emit site-relative URLs with a trailing slash. Match your sitemap and your routing config. */
|
|
129
|
+
trailingSlash?: boolean;
|
|
130
|
+
/** Site name (openGraph.siteName, and the social-card title fallback). */
|
|
131
|
+
siteName?: string;
|
|
132
|
+
/** Root-layout mode: emit `title: { template, default }` (e.g. `'%s | Example'`). */
|
|
133
|
+
titleTemplate?: string;
|
|
134
|
+
/** og:type when the entry sets none. Defaults to 'website'. */
|
|
135
|
+
defaultOgType?: SeoOgType;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Map an entry's SEO fields onto a Next `Metadata` object.
|
|
139
|
+
*
|
|
140
|
+
* Title and description follow ONE convention everywhere: the entry's meta field, else the
|
|
141
|
+
* fallback you pass, else unset (so the root layout's default applies). An empty CMS field counts
|
|
142
|
+
* as unset — see `extractSeoFields`.
|
|
143
|
+
*
|
|
144
|
+
* `noindex` goes through the same core predicate the sitemap uses, so a page marked noindex is
|
|
145
|
+
* suppressed in both surfaces or neither.
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* export async function generateMetadata({ params }): Promise<Metadata> {
|
|
149
|
+
* const { slug } = await params
|
|
150
|
+
* const result = await readByUrlPath(`/posts/${slug}`)
|
|
151
|
+
* return entryToMetadata(result?.data, { path: `/posts/${slug}`, fallbackTitle: result?.data.title })
|
|
152
|
+
* }
|
|
153
|
+
*/
|
|
154
|
+
export declare function entryToMetadata(entryData: unknown, opts?: EntryToMetadataOptions): Metadata;
|
|
155
|
+
export {};
|
|
39
156
|
//# sourceMappingURL=static.d.ts.map
|
package/dist/static.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"static.d.ts","sourceRoot":"","sources":["../src/static.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"static.d.ts","sourceRoot":"","sources":["../src/static.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,MAAM,CAAA;AACnD,OAAO,EAOL,KAAK,kBAAkB,EACvB,KAAK,yBAAyB,EAC9B,KAAK,uBAAuB,EAC5B,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,SAAS,EACf,MAAM,kBAAkB,CAAA;AAEzB;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,kCAAmC,SAAQ,yBAAyB;IACnF,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,KAAK,CAAC,EAAE,WAAW,GAAG,QAAQ,CAAA;IAC9B;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,IAAI,CAAC,kBAAkB,EAAE,aAAa,CAAC,EACjD,OAAO,GAAE,kCAAuC,GAC/C,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC,CAAC,CAyBnD;AAMD,KAAK,WAAW,GAAG,aAAa,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;AAEhD,kFAAkF;AAClF,MAAM,WAAW,eAAe;IAC9B,iGAAiG;IACjG,IAAI,EAAE,MAAM,CAAA;IACZ,YAAY,CAAC,EAAE,IAAI,GAAG,MAAM,CAAA;IAC5B,eAAe,CAAC,EAAE,WAAW,CAAC,iBAAiB,CAAC,CAAA;IAChD,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,6BAA6B;IAC5C;;;;OAIG;IACH,OAAO,EAAE,MAAM,CAAA;IACf;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,qGAAqG;IACrG,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,gGAAgG;IAChG,GAAG,CAAC,EAAE,gBAAgB,CAAA;IACtB;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,OAAO,CAAA;IAC3C;;;;;;;;;OASG;IACH,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,GAAG,MAAM,GAAG,SAAS,CAAA;IAClE,8FAA8F;IAC9F,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,MAAM,GAAG,SAAS,CAAA;IACvD,gGAAgG;IAChG,SAAS,CAAC,EAAE,eAAe,EAAE,CAAA;CAC9B;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,sBAAsB,CAC1C,QAAQ,EAAE,IAAI,CAAC,kBAAkB,EAAE,aAAa,CAAC,EACjD,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,CA4ChC;AAiCD,MAAM,WAAW,sBAAuB,SAAQ,uBAAuB;IACrE,8EAA8E;IAC9E,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,iGAAiG;IACjG,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,qFAAqF;IACrF,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,+DAA+D;IAC/D,aAAa,CAAC,EAAE,SAAS,CAAA;CAC1B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,OAAO,EAAE,IAAI,GAAE,sBAA2B,GAAG,QAAQ,CAkD/F"}
|
package/dist/static.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { collectStaticPaths, } from 'canopycms/server';
|
|
1
|
+
import { collectRoutableEntries, collectStaticPaths, extractSeoFields, isAbsoluteUrl, isNoindexEntry, resolveSeoUrl, } from 'canopycms/server';
|
|
2
2
|
/**
|
|
3
3
|
* Shape CanopyCMS content paths into the array Next's `generateStaticParams` expects.
|
|
4
4
|
*
|
|
@@ -14,7 +14,13 @@ import { collectStaticPaths, } from 'canopycms/server';
|
|
|
14
14
|
export async function collectStaticParams(buildCtx, options = {}) {
|
|
15
15
|
const { paramName = 'slug', shape = 'catch-all', basePath, ...collectOptions } = options;
|
|
16
16
|
let entries = await collectStaticPaths(buildCtx, collectOptions);
|
|
17
|
-
|
|
17
|
+
// basePath only means something for the catch-all shape: it rewrites `segments` relative to the
|
|
18
|
+
// route's URL prefix, and 'single' shape never reads `segments` (it emits `entry.slug`). Applying
|
|
19
|
+
// the prefix FILTER unconditionally — as this once did — silently dropped every entry outside the
|
|
20
|
+
// prefix for 'single' too, contradicting this option's own doc above ("no effect with shape:
|
|
21
|
+
// 'single'") and meaning unbuilt pages for anyone who set basePath alongside 'single'. Scope with
|
|
22
|
+
// `rootPath` for 'single' instead.
|
|
23
|
+
if (basePath && shape !== 'single') {
|
|
18
24
|
// Make segments relative to a nested route's base prefix (e.g. '/docs' for app/docs/[[...slug]]).
|
|
19
25
|
// urlPath is always lowercased (see content-listing), so lowercase the prefix to match.
|
|
20
26
|
const prefix = (basePath.endsWith('/') ? basePath.slice(0, -1) : basePath).toLowerCase();
|
|
@@ -27,4 +33,155 @@ export async function collectStaticParams(buildCtx, options = {}) {
|
|
|
27
33
|
}
|
|
28
34
|
return entries.map((entry) => shape === 'single' ? { [paramName]: entry.slug } : { [paramName]: entry.segments });
|
|
29
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Build Next's `MetadataRoute.Sitemap` from CanopyCMS content.
|
|
38
|
+
*
|
|
39
|
+
* **Every routable entry type is included by default.** There is no list of "sitemap-able" entry
|
|
40
|
+
* types to keep in sync, and that is the whole design: a hand-rolled sitemap that enumerates a
|
|
41
|
+
* remembered list of entry types omits whichever type nobody added, ships green, and takes the
|
|
42
|
+
* missing pages out of search results silently. Omitting a URL here requires an explicit
|
|
43
|
+
* `exclude` predicate or a `noindex` flag on the entry.
|
|
44
|
+
*
|
|
45
|
+
* `noindex` entries are excluded, via the same core `isNoindexEntry` predicate that drives
|
|
46
|
+
* `robots: { index: false }` in `entryToMetadata`. They are still BUILT — enumeration
|
|
47
|
+
* (`generateContentStaticParams`) does not filter them, so the page exists for anyone holding
|
|
48
|
+
* the link; it just isn't advertised.
|
|
49
|
+
*
|
|
50
|
+
* `changeFrequency` is not emitted for entries: a blanket value asserted for every URL carries no
|
|
51
|
+
* information, and search engines say they ignore it. Set it per-URL via `extraUrls` if you want it.
|
|
52
|
+
*
|
|
53
|
+
* Output is stably ordered ('/' first, then alphabetical) so a rebuild doesn't reshuffle the file.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* // app/sitemap.ts
|
|
57
|
+
* export const dynamic = 'force-static' // required for output: 'export'
|
|
58
|
+
* export default () => contentSitemap({ siteUrl: 'https://example.com', trailingSlash: true })
|
|
59
|
+
*/
|
|
60
|
+
export async function generateContentSitemap(buildCtx, options) {
|
|
61
|
+
const { siteUrl, trailingSlash, rootPath, seo, exclude, lastModified, priority } = options;
|
|
62
|
+
// A sitemap with a non-absolute <loc> is invalid — search engines reject the WHOLE file, not
|
|
63
|
+
// just the bad entry, and the build stays green because nothing here threw. 'example.com' (no
|
|
64
|
+
// scheme) and '' both pass through resolveSeoUrl unmodified today; catch them here instead.
|
|
65
|
+
if (!isAbsoluteUrl(siteUrl)) {
|
|
66
|
+
throw new Error(`CanopyCMS: generateContentSitemap requires an absolute siteUrl (e.g. "https://example.com"), ` +
|
|
67
|
+
`got ${JSON.stringify(siteUrl)}. A sitemap whose <loc> values aren't absolute URLs is invalid ` +
|
|
68
|
+
'and search engines silently reject the entire file.');
|
|
69
|
+
}
|
|
70
|
+
const urlOpts = { siteUrl, trailingSlash };
|
|
71
|
+
const entries = await collectRoutableEntries(buildCtx, { rootPath });
|
|
72
|
+
const items = [];
|
|
73
|
+
for (const entry of entries) {
|
|
74
|
+
if (isNoindexEntry(entry.data, seo))
|
|
75
|
+
continue;
|
|
76
|
+
if (exclude?.(entry))
|
|
77
|
+
continue;
|
|
78
|
+
const modified = lastModified ? lastModified(entry) : entry.updatedAt;
|
|
79
|
+
const entryPriority = priority?.(entry);
|
|
80
|
+
items.push({
|
|
81
|
+
url: resolveSeoUrl(entry.urlPath, urlOpts),
|
|
82
|
+
...(modified ? { lastModified: modified } : {}),
|
|
83
|
+
...(entryPriority === undefined ? {} : { priority: entryPriority }),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
for (const extra of options.extraUrls ?? []) {
|
|
87
|
+
items.push({
|
|
88
|
+
url: resolveSeoUrl(extra.path, urlOpts),
|
|
89
|
+
...(extra.lastModified ? { lastModified: extra.lastModified } : {}),
|
|
90
|
+
...(extra.changeFrequency ? { changeFrequency: extra.changeFrequency } : {}),
|
|
91
|
+
...(extra.priority === undefined ? {} : { priority: extra.priority }),
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
const root = resolveSeoUrl('/', urlOpts);
|
|
95
|
+
return dedupeSitemapItems(items).sort((a, b) => a.url === root ? -1 : b.url === root ? 1 : a.url.localeCompare(b.url));
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Drop duplicate `<loc>` entries, keeping the first occurrence and warning about each collision.
|
|
99
|
+
*
|
|
100
|
+
* A duplicate URL isn't fatal to a crawler, but it usually means two entries (or an entry and an
|
|
101
|
+
* `extraUrls` path) are unintentionally sharing one URL — an index entry collapsing onto a
|
|
102
|
+
* sibling's path, or two `urlPath`s that only differ by case (`urlPath` is lowercased — see
|
|
103
|
+
* `content-listing.ts`). Warning rather than silently deduping turns that into a build-time signal
|
|
104
|
+
* an adopter can act on instead of a sitemap that just quietly has fewer URLs than expected.
|
|
105
|
+
*/
|
|
106
|
+
function dedupeSitemapItems(items) {
|
|
107
|
+
const seen = new Set();
|
|
108
|
+
const result = [];
|
|
109
|
+
for (const item of items) {
|
|
110
|
+
if (seen.has(item.url)) {
|
|
111
|
+
console.warn(`CanopyCMS: generateContentSitemap found more than one entry resolving to the same sitemap ` +
|
|
112
|
+
`URL ${JSON.stringify(item.url)}. Keeping the first and dropping the rest — this usually means ` +
|
|
113
|
+
'two entries (or an entry and an extraUrls path) are unintentionally sharing one URL.');
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
seen.add(item.url);
|
|
117
|
+
result.push(item);
|
|
118
|
+
}
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Map an entry's SEO fields onto a Next `Metadata` object.
|
|
123
|
+
*
|
|
124
|
+
* Title and description follow ONE convention everywhere: the entry's meta field, else the
|
|
125
|
+
* fallback you pass, else unset (so the root layout's default applies). An empty CMS field counts
|
|
126
|
+
* as unset — see `extractSeoFields`.
|
|
127
|
+
*
|
|
128
|
+
* `noindex` goes through the same core predicate the sitemap uses, so a page marked noindex is
|
|
129
|
+
* suppressed in both surfaces or neither.
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* export async function generateMetadata({ params }): Promise<Metadata> {
|
|
133
|
+
* const { slug } = await params
|
|
134
|
+
* const result = await readByUrlPath(`/posts/${slug}`)
|
|
135
|
+
* return entryToMetadata(result?.data, { path: `/posts/${slug}`, fallbackTitle: result?.data.title })
|
|
136
|
+
* }
|
|
137
|
+
*/
|
|
138
|
+
export function entryToMetadata(entryData, opts = {}) {
|
|
139
|
+
const seo = extractSeoFields(entryData, opts);
|
|
140
|
+
const urlOpts = { siteUrl: opts.siteUrl, trailingSlash: opts.trailingSlash };
|
|
141
|
+
// The entry's own canonical wins over the route path; an absolute one passes through verbatim
|
|
142
|
+
// (see resolveSeoUrl — the absolute check runs before any normalization, which is a fixed bug).
|
|
143
|
+
const canonicalSource = seo.canonical ?? opts.path;
|
|
144
|
+
const canonical = canonicalSource ? resolveSeoUrl(canonicalSource, urlOpts) : undefined;
|
|
145
|
+
// Images are files, never directory-style URLs: never append a trailing slash to one.
|
|
146
|
+
const ogImage = seo.ogImage ? resolveSeoUrl(seo.ogImage, { siteUrl: opts.siteUrl }) : undefined;
|
|
147
|
+
// og:title falls back to the site name so a page that inherits the layout title still emits a
|
|
148
|
+
// usable card — Next REPLACES `openGraph` wholesale rather than deep-merging it.
|
|
149
|
+
const socialTitle = seo.title ?? opts.siteName;
|
|
150
|
+
const ogType = seo.ogType ?? opts.defaultOgType ?? 'website';
|
|
151
|
+
const openGraphBase = {
|
|
152
|
+
...(socialTitle ? { title: socialTitle } : {}),
|
|
153
|
+
...(seo.description ? { description: seo.description } : {}),
|
|
154
|
+
...(canonical ? { url: canonical } : {}),
|
|
155
|
+
...(opts.siteName ? { siteName: opts.siteName } : {}),
|
|
156
|
+
...(ogImage ? { images: [{ url: ogImage }] } : {}),
|
|
157
|
+
};
|
|
158
|
+
const metadata = {
|
|
159
|
+
...(canonical ? { alternates: { canonical } } : {}),
|
|
160
|
+
// Spelled out per branch rather than cast: Next's OpenGraph metadata is a discriminated
|
|
161
|
+
// union on `type`.
|
|
162
|
+
openGraph: ogType === 'article'
|
|
163
|
+
? { ...openGraphBase, type: 'article' }
|
|
164
|
+
: ogType === 'profile'
|
|
165
|
+
? { ...openGraphBase, type: 'profile' }
|
|
166
|
+
: { ...openGraphBase, type: 'website' },
|
|
167
|
+
twitter: {
|
|
168
|
+
card: seo.twitterCard ?? 'summary_large_image',
|
|
169
|
+
...(socialTitle ? { title: socialTitle } : {}),
|
|
170
|
+
...(seo.description ? { description: seo.description } : {}),
|
|
171
|
+
...(ogImage ? { images: [ogImage] } : {}),
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
if (opts.titleTemplate) {
|
|
175
|
+
metadata.title = { template: opts.titleTemplate, default: seo.title ?? opts.siteName ?? '' };
|
|
176
|
+
}
|
|
177
|
+
else if (seo.title) {
|
|
178
|
+
metadata.title = seo.title;
|
|
179
|
+
}
|
|
180
|
+
if (seo.description)
|
|
181
|
+
metadata.description = seo.description;
|
|
182
|
+
// Same predicate as the sitemap's exclusion — these two must never be derived separately.
|
|
183
|
+
if (isNoindexEntry(entryData, opts))
|
|
184
|
+
metadata.robots = { index: false, follow: false };
|
|
185
|
+
return metadata;
|
|
186
|
+
}
|
|
30
187
|
//# sourceMappingURL=static.js.map
|
package/dist/static.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"static.js","sourceRoot":"","sources":["../src/static.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"static.js","sourceRoot":"","sources":["../src/static.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "canopycms-next",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.63",
|
|
4
4
|
"description": "Next.js adapter for CanopyCMS",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -34,19 +34,19 @@
|
|
|
34
34
|
"node": ">=18"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"next": "^13.5.7 || ^14.2.25 || ^15.
|
|
37
|
+
"next": "^13.5.7 || ^14.2.25 || ^15.5.21 || >=16.0.0 <16.2.0 || >=16.3.0 <17.0.0",
|
|
38
38
|
"react": "^18.0.0 || ^19.0.0",
|
|
39
|
-
"canopycms": "0.0.
|
|
39
|
+
"canopycms": "0.0.63"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/node": "^22.9.0",
|
|
43
43
|
"esbuild": "^0.25.0",
|
|
44
44
|
"@types/react": "^18.3.12",
|
|
45
|
-
"next": "^15.5.
|
|
45
|
+
"next": "^15.5.21",
|
|
46
46
|
"react": "^18.3.1",
|
|
47
47
|
"typescript": "^5.6.3",
|
|
48
48
|
"vitest": "^4.1.0",
|
|
49
|
-
"canopycms": "0.0.
|
|
49
|
+
"canopycms": "0.0.63"
|
|
50
50
|
},
|
|
51
51
|
"scripts": {
|
|
52
52
|
"build": "tsc -p tsconfig.build.json && esbuild src/config.ts --bundle --platform=node --packages=external --external:next --format=cjs --outfile=dist/config.cjs && esbuild src/config.ts --bundle --platform=node --packages=external --external:next --format=esm --outfile=dist/config.mjs",
|