canopycms-next 0.0.38 → 0.0.40
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/context-wrapper.d.ts +45 -7
- package/dist/context-wrapper.d.ts.map +1 -1
- package/dist/context-wrapper.js +74 -6
- package/dist/context-wrapper.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/static.d.ts +39 -0
- package/dist/static.d.ts.map +1 -0
- package/dist/static.js +30 -0
- package/dist/static.js.map +1 -0
- package/package.json +3 -3
|
@@ -1,29 +1,67 @@
|
|
|
1
1
|
import { type CanopyContext, type CanopyBuildContext, type CanopyServices } from 'canopycms/server';
|
|
2
2
|
import type { CanopyConfig, AuthPlugin, FieldConfig } from 'canopycms';
|
|
3
3
|
import { createCanopyCatchAllHandler } from './adapter';
|
|
4
|
+
import { type GenerateContentStaticParamsOptions } from './static';
|
|
4
5
|
export interface NextCanopyOptions {
|
|
5
6
|
config: CanopyConfig;
|
|
6
7
|
/** Auth plugin for user authentication. Optional for static deployments (deployedAs: 'static'). */
|
|
7
8
|
authPlugin?: AuthPlugin;
|
|
8
9
|
entrySchemaRegistry: Record<string, readonly FieldConfig[]>;
|
|
9
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* Wrap a build context so its operations throw if misused at request time on a production server.
|
|
13
|
+
*
|
|
14
|
+
* The build context runs as a synthetic admin and bypasses all branch/path ACLs. At **prod-server
|
|
15
|
+
* request time** there is never a legitimate reason to use it — a real user is on the other end, so
|
|
16
|
+
* content must be read through the request-scoped, ACL-enforcing getCanopy() (or the phase-selecting
|
|
17
|
+
* read/readByUrlPath, which routes to it at request time). So the guard fires for
|
|
18
|
+
* `mode === 'prod' && deployedAs === 'server' && !isBuildMode()` and fails closed (throws) rather than
|
|
19
|
+
* silently leaking protected content.
|
|
20
|
+
*
|
|
21
|
+
* Why not also in dev: Next invokes legitimate static-generation hooks (generateStaticParams,
|
|
22
|
+
* generateMetadata) in `next dev` with the same `!isBuildMode()` signature as the footgun, and there
|
|
23
|
+
* is no reliable way to tell them apart — a dev guard would false-positive on idiomatic code. In prod
|
|
24
|
+
* that ambiguity is gone (generateStaticParams is build-only; request-time build-context use is the
|
|
25
|
+
* footgun). Why not on `static` deployments: they skip ACLs everywhere by design, so there's nothing
|
|
26
|
+
* to leak. `CANOPY_BUILD_MODE=true` marks non-Next static generation as build phase.
|
|
27
|
+
*/
|
|
28
|
+
export declare function guardBuildContext(buildCtx: CanopyBuildContext, config: CanopyConfig): CanopyBuildContext;
|
|
10
29
|
export interface NextCanopyContextResult {
|
|
11
30
|
/** Request-scoped context. Uses headers() + React cache(). Call from server components and route handlers. */
|
|
12
31
|
getCanopy: () => Promise<CanopyContext>;
|
|
13
32
|
/**
|
|
14
33
|
* Build-time context. Uses STATIC_DEPLOY_USER (full admin, no auth), no request scope needed.
|
|
15
|
-
* Safe to call from generateStaticParams, generateMetadata, and
|
|
34
|
+
* Safe to call from generateStaticParams, generateMetadata, sitemap, and build-time page rendering.
|
|
16
35
|
* Memoized for the process lifetime — multiple calls return the same context.
|
|
17
36
|
*
|
|
18
|
-
* Returns a narrower type than getCanopy()
|
|
19
|
-
*
|
|
20
|
-
* per-user content reads.
|
|
37
|
+
* Returns a narrower type than getCanopy() (no `user`) but includes read/readByUrlPath so build-time
|
|
38
|
+
* code can resolve a single entry by path/URL without scanning the whole collection.
|
|
21
39
|
*
|
|
22
|
-
* **Security note:** This context bypasses all branch and path ACLs
|
|
23
|
-
*
|
|
24
|
-
*
|
|
40
|
+
* **Security note:** This context bypasses all branch and path ACLs (synthetic admin, unrestricted
|
|
41
|
+
* read). It's an advanced escape hatch — for ordinary page work prefer the phase-selecting
|
|
42
|
+
* `readByUrlPath`/`read` (content) and `generateContentStaticParams` (paths) below, which don't hand
|
|
43
|
+
* the admin context to your page modules. On a production `server` deployment its operations throw
|
|
44
|
+
* if invoked at request time (where getCanopy() would enforce ACLs).
|
|
25
45
|
*/
|
|
26
46
|
getCanopyForBuild: () => Promise<CanopyBuildContext>;
|
|
47
|
+
/**
|
|
48
|
+
* Phase-selecting `readByUrlPath`. At build time (isBuildMode()) it reads filesystem-direct via the
|
|
49
|
+
* build context; at request time it uses the branch-aware, ACL-enforced runtime context (getCanopy()).
|
|
50
|
+
*
|
|
51
|
+
* This is the recommended way to resolve a page by URL in a `[...slug]`/`[slug]` route: correct in
|
|
52
|
+
* both phases by construction (working tree at build, branch-clone preview in dev), so page code
|
|
53
|
+
* never has to hand-pick the admin build context. Returns null for non-matching/invalid paths.
|
|
54
|
+
*/
|
|
55
|
+
readByUrlPath: CanopyContext['readByUrlPath'];
|
|
56
|
+
/** Phase-selecting `read` (build context at build, runtime context at request) — counterpart to readByUrlPath. */
|
|
57
|
+
read: CanopyContext['read'];
|
|
58
|
+
/**
|
|
59
|
+
* Build the array Next's `generateStaticParams` expects from CanopyCMS content. Enumeration-only
|
|
60
|
+
* (reads the set of routable paths, never entry content) and closes over the build context, so your
|
|
61
|
+
* page module never imports the admin `getCanopyForBuild`. Use `{ rootPath, shape: 'single' }` for a
|
|
62
|
+
* `[slug]` route, or the default catch-all for `[...slug]`.
|
|
63
|
+
*/
|
|
64
|
+
generateContentStaticParams: (options?: GenerateContentStaticParamsOptions) => Promise<Array<Record<string, string | string[]>>>;
|
|
27
65
|
/** API catch-all route handler */
|
|
28
66
|
handler: ReturnType<typeof createCanopyCatchAllHandler>;
|
|
29
67
|
/** 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,
|
|
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,EAUpB,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAc,WAAW,EAAE,MAAM,WAAW,CAAA;AAIlF,OAAO,EAAE,2BAA2B,EAAE,MAAM,WAAW,CAAA;AACvD,OAAO,EAAuB,KAAK,kCAAkC,EAAE,MAAM,UAAU,CAAA;AA2BvF,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;CAC5D;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,kBAAkB,EAC5B,MAAM,EAAE,YAAY,GACnB,kBAAkB,CA0CpB;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;;;;;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,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,CA2KlC"}
|
package/dist/context-wrapper.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { cache } from 'react';
|
|
3
3
|
import { headers } from 'next/headers';
|
|
4
|
-
import { createCanopyContext, createCanopyServices, operatingStrategy, loadInternalGroups, loadBranchContext, STATIC_DEPLOY_USER, } from 'canopycms/server';
|
|
4
|
+
import { createCanopyContext, isBuildMode, startDevContentWatcher, createCanopyServices, operatingStrategy, loadInternalGroups, loadBranchContext, STATIC_DEPLOY_USER, } from 'canopycms/server';
|
|
5
5
|
import { authResultToCanopyUser } from 'canopycms';
|
|
6
6
|
import { CachingAuthPlugin, FileBasedAuthCache } from 'canopycms/auth/cache';
|
|
7
7
|
import { createCanopyCatchAllHandler } from './adapter';
|
|
8
|
+
import { collectStaticParams } from './static';
|
|
8
9
|
let warnedNoAdmins = false;
|
|
9
10
|
let warnedStaticMode = false;
|
|
10
11
|
/**
|
|
@@ -28,6 +29,52 @@ const staticDeployAuthPlugin = {
|
|
|
28
29
|
return [];
|
|
29
30
|
},
|
|
30
31
|
};
|
|
32
|
+
/**
|
|
33
|
+
* Wrap a build context so its operations throw if misused at request time on a production server.
|
|
34
|
+
*
|
|
35
|
+
* The build context runs as a synthetic admin and bypasses all branch/path ACLs. At **prod-server
|
|
36
|
+
* request time** there is never a legitimate reason to use it — a real user is on the other end, so
|
|
37
|
+
* content must be read through the request-scoped, ACL-enforcing getCanopy() (or the phase-selecting
|
|
38
|
+
* read/readByUrlPath, which routes to it at request time). So the guard fires for
|
|
39
|
+
* `mode === 'prod' && deployedAs === 'server' && !isBuildMode()` and fails closed (throws) rather than
|
|
40
|
+
* silently leaking protected content.
|
|
41
|
+
*
|
|
42
|
+
* Why not also in dev: Next invokes legitimate static-generation hooks (generateStaticParams,
|
|
43
|
+
* generateMetadata) in `next dev` with the same `!isBuildMode()` signature as the footgun, and there
|
|
44
|
+
* is no reliable way to tell them apart — a dev guard would false-positive on idiomatic code. In prod
|
|
45
|
+
* that ambiguity is gone (generateStaticParams is build-only; request-time build-context use is the
|
|
46
|
+
* footgun). Why not on `static` deployments: they skip ACLs everywhere by design, so there's nothing
|
|
47
|
+
* to leak. `CANOPY_BUILD_MODE=true` marks non-Next static generation as build phase.
|
|
48
|
+
*/
|
|
49
|
+
export function guardBuildContext(buildCtx, config) {
|
|
50
|
+
const assertBuildPhase = (method) => {
|
|
51
|
+
if (config.mode === 'prod' && config.deployedAs === 'server' && !isBuildMode()) {
|
|
52
|
+
throw new Error(`CanopyCMS: getCanopyForBuild().${method}() was called at request time on a production ` +
|
|
53
|
+
"'server' deployment. The build context bypasses all branch and path ACLs and must only be " +
|
|
54
|
+
'used during static generation (next build / CANOPY_BUILD_MODE=true). Use the request-scoped ' +
|
|
55
|
+
'getCanopy(), or the phase-selecting read()/readByUrlPath() from createNextCanopyContext.');
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
return {
|
|
59
|
+
services: buildCtx.services,
|
|
60
|
+
buildContentTree: (options) => {
|
|
61
|
+
assertBuildPhase('buildContentTree');
|
|
62
|
+
return buildCtx.buildContentTree(options);
|
|
63
|
+
},
|
|
64
|
+
listEntries: (options) => {
|
|
65
|
+
assertBuildPhase('listEntries');
|
|
66
|
+
return buildCtx.listEntries(options);
|
|
67
|
+
},
|
|
68
|
+
read: (input) => {
|
|
69
|
+
assertBuildPhase('read');
|
|
70
|
+
return buildCtx.read(input);
|
|
71
|
+
},
|
|
72
|
+
readByUrlPath: (urlPath, options) => {
|
|
73
|
+
assertBuildPhase('readByUrlPath');
|
|
74
|
+
return buildCtx.readByUrlPath(urlPath, options);
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
31
78
|
/**
|
|
32
79
|
* Create Next.js-specific wrapper around core context.
|
|
33
80
|
* Adds React cache() for per-request memoization and API handler.
|
|
@@ -73,6 +120,11 @@ export async function createNextCanopyContext(options) {
|
|
|
73
120
|
const services = await createCanopyServices(options.config, {
|
|
74
121
|
entrySchemaRegistry: options.entrySchemaRegistry,
|
|
75
122
|
});
|
|
123
|
+
// In dev, surface divergence between working-tree content and the served branch clone (warn-only).
|
|
124
|
+
// All logic lives in the core watcher; this is just the once-at-startup trigger (thin Next wiring).
|
|
125
|
+
if (options.config.mode === 'dev' && !isBuildMode()) {
|
|
126
|
+
startDevContentWatcher(services, { mode: options.config.dev?.contentSync });
|
|
127
|
+
}
|
|
76
128
|
// User extractor: passes Next.js headers to auth plugin, loads internal groups, applies authorization
|
|
77
129
|
const extractUser = async () => {
|
|
78
130
|
const headersList = await headers();
|
|
@@ -119,11 +171,9 @@ export async function createNextCanopyContext(options) {
|
|
|
119
171
|
if (!buildContextPromise) {
|
|
120
172
|
buildContextPromise = buildContext
|
|
121
173
|
.getContext()
|
|
122
|
-
.then(({ buildContentTree, listEntries, services }) =>
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
services,
|
|
126
|
-
}))
|
|
174
|
+
.then(({ buildContentTree, listEntries, read, readByUrlPath, services }) =>
|
|
175
|
+
// Guard so these reads throw if used at request time on a `server` deployment.
|
|
176
|
+
guardBuildContext({ buildContentTree, listEntries, read, readByUrlPath, services }, options.config))
|
|
127
177
|
.catch((err) => {
|
|
128
178
|
buildContextPromise = null;
|
|
129
179
|
throw err;
|
|
@@ -131,6 +181,21 @@ export async function createNextCanopyContext(options) {
|
|
|
131
181
|
}
|
|
132
182
|
return buildContextPromise;
|
|
133
183
|
};
|
|
184
|
+
// Phase-selecting helpers: build context during static generation, branch-aware runtime at request
|
|
185
|
+
// time. Lets page code resolve content without hand-picking the admin build context (see item #5).
|
|
186
|
+
const readByUrlPath = async (urlPath, opts) => {
|
|
187
|
+
const ctx = isBuildMode() ? await getCanopyForBuild() : await getCanopy();
|
|
188
|
+
return ctx.readByUrlPath(urlPath, opts);
|
|
189
|
+
};
|
|
190
|
+
const read = async (input) => {
|
|
191
|
+
const ctx = isBuildMode() ? await getCanopyForBuild() : await getCanopy();
|
|
192
|
+
return ctx.read(input);
|
|
193
|
+
};
|
|
194
|
+
// Enumeration-only static-params helper, bound to the (guarded) build context so page modules don't
|
|
195
|
+
// import the admin context just to list paths. generateStaticParams is build-only, so this is safe.
|
|
196
|
+
const generateContentStaticParams = async (options = {}) => {
|
|
197
|
+
return collectStaticParams(await getCanopyForBuild(), options);
|
|
198
|
+
};
|
|
134
199
|
// Create API handler using same services
|
|
135
200
|
const handler = createCanopyCatchAllHandler({
|
|
136
201
|
...options,
|
|
@@ -140,6 +205,9 @@ export async function createNextCanopyContext(options) {
|
|
|
140
205
|
return {
|
|
141
206
|
getCanopy,
|
|
142
207
|
getCanopyForBuild,
|
|
208
|
+
readByUrlPath,
|
|
209
|
+
read,
|
|
210
|
+
generateContentStaticParams,
|
|
143
211
|
handler,
|
|
144
212
|
services,
|
|
145
213
|
};
|
|
@@ -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,
|
|
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,4 +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
4
|
export { createMockAuthPlugin, createRejectingAuthPlugin } from './test-utils';
|
|
4
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,EAAE,oBAAoB,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAA"}
|
|
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,EAAE,mBAAmB,EAAE,KAAK,kCAAkC,EAAE,MAAM,UAAU,CAAA;AAEvF,OAAO,EAAE,oBAAoB,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { createCanopyCatchAllHandler, wrapNextRequest } from './adapter';
|
|
2
2
|
export { createNextCanopyContext, } from './context-wrapper';
|
|
3
|
+
export { collectStaticParams } from './static';
|
|
3
4
|
export { createMockAuthPlugin, createRejectingAuthPlugin } from './test-utils';
|
|
4
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,EAAE,oBAAoB,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAA"}
|
|
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,EAAE,mBAAmB,EAA2C,MAAM,UAAU,CAAA;AAEvF,OAAO,EAAE,oBAAoB,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAA"}
|
package/dist/static.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type CanopyBuildContext, type CollectStaticPathsOptions } from 'canopycms/server';
|
|
2
|
+
/**
|
|
3
|
+
* Next.js static-export helpers built on the framework-agnostic core in `canopycms/server`.
|
|
4
|
+
*
|
|
5
|
+
* Today this ships static-params generation. Sitemap and SEO-metadata helpers are tracked as
|
|
6
|
+
* follow-up work (see .claude/future-tasks/static-export-sitemap.md and
|
|
7
|
+
* .claude/future-tasks/static-export-seo-metadata.md).
|
|
8
|
+
*/
|
|
9
|
+
export interface GenerateContentStaticParamsOptions extends CollectStaticPathsOptions {
|
|
10
|
+
/** Route param name. Default 'slug'. */
|
|
11
|
+
paramName?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Route shape:
|
|
14
|
+
* - 'catch-all' (default) → `[...slug]` / `[[...slug]]`: param value is the URL `segments` array.
|
|
15
|
+
* - 'single' → `[slug]`: param value is the entry `slug` (pair with `rootPath` to scope a collection).
|
|
16
|
+
*/
|
|
17
|
+
shape?: 'catch-all' | 'single';
|
|
18
|
+
/**
|
|
19
|
+
* For a catch-all route nested under a URL prefix (e.g. `app/docs/[[...slug]]`), set this to the
|
|
20
|
+
* route's base (e.g. `'/docs'`). Entries are scoped to that prefix and `segments` are made relative
|
|
21
|
+
* to it, so the params match the route. Without it, segments are the full URL path. Applies to
|
|
22
|
+
* catch-all shapes (it rewrites `segments`); it has no effect with `shape: 'single'`.
|
|
23
|
+
*/
|
|
24
|
+
basePath?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Shape CanopyCMS content paths into the array Next's `generateStaticParams` expects.
|
|
28
|
+
*
|
|
29
|
+
* This is an **enumeration-only** capability: it reads only the set of routable paths (via the build
|
|
30
|
+
* context's `listEntries`), never entry content, and `generateStaticParams` is build-only — so it
|
|
31
|
+
* cannot serve a user request. It takes a build context directly; prefer the bound
|
|
32
|
+
* `generateContentStaticParams` returned from `createNextCanopyContext`, which closes over the build
|
|
33
|
+
* context so your page modules never import the admin context.
|
|
34
|
+
*
|
|
35
|
+
* Note: a root index ('/') yields empty `segments` — keep it only for an optional catch-all
|
|
36
|
+
* `[[...slug]]`, otherwise exclude it via `options.filter` (e.g. `(e) => e.segments.length > 0`).
|
|
37
|
+
*/
|
|
38
|
+
export declare function collectStaticParams(buildCtx: Pick<CanopyBuildContext, 'listEntries'>, options?: GenerateContentStaticParamsOptions): Promise<Array<Record<string, string | string[]>>>;
|
|
39
|
+
//# sourceMappingURL=static.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"static.d.ts","sourceRoot":"","sources":["../src/static.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,yBAAyB,EAC/B,MAAM,kBAAkB,CAAA;AAEzB;;;;;;GAMG;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,CAmBnD"}
|
package/dist/static.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { collectStaticPaths, } from 'canopycms/server';
|
|
2
|
+
/**
|
|
3
|
+
* Shape CanopyCMS content paths into the array Next's `generateStaticParams` expects.
|
|
4
|
+
*
|
|
5
|
+
* This is an **enumeration-only** capability: it reads only the set of routable paths (via the build
|
|
6
|
+
* context's `listEntries`), never entry content, and `generateStaticParams` is build-only — so it
|
|
7
|
+
* cannot serve a user request. It takes a build context directly; prefer the bound
|
|
8
|
+
* `generateContentStaticParams` returned from `createNextCanopyContext`, which closes over the build
|
|
9
|
+
* context so your page modules never import the admin context.
|
|
10
|
+
*
|
|
11
|
+
* Note: a root index ('/') yields empty `segments` — keep it only for an optional catch-all
|
|
12
|
+
* `[[...slug]]`, otherwise exclude it via `options.filter` (e.g. `(e) => e.segments.length > 0`).
|
|
13
|
+
*/
|
|
14
|
+
export async function collectStaticParams(buildCtx, options = {}) {
|
|
15
|
+
const { paramName = 'slug', shape = 'catch-all', basePath, ...collectOptions } = options;
|
|
16
|
+
let entries = await collectStaticPaths(buildCtx, collectOptions);
|
|
17
|
+
if (basePath) {
|
|
18
|
+
// Make segments relative to a nested route's base prefix (e.g. '/docs' for app/docs/[[...slug]]).
|
|
19
|
+
// urlPath is always lowercased (see content-listing), so lowercase the prefix to match.
|
|
20
|
+
const prefix = (basePath.endsWith('/') ? basePath.slice(0, -1) : basePath).toLowerCase();
|
|
21
|
+
entries = entries
|
|
22
|
+
.filter((entry) => entry.urlPath === prefix || entry.urlPath.startsWith(`${prefix}/`))
|
|
23
|
+
.map((entry) => {
|
|
24
|
+
const rel = entry.urlPath === prefix ? '' : entry.urlPath.slice(prefix.length + 1);
|
|
25
|
+
return { ...entry, segments: rel ? rel.split('/') : [] };
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return entries.map((entry) => shape === 'single' ? { [paramName]: entry.slug } : { [paramName]: entry.segments });
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=static.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"static.js","sourceRoot":"","sources":["../src/static.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,GAGnB,MAAM,kBAAkB,CAAA;AA4BzB;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,QAAiD,EACjD,UAA8C,EAAE;IAEhD,MAAM,EAAE,SAAS,GAAG,MAAM,EAAE,KAAK,GAAG,WAAW,EAAE,QAAQ,EAAE,GAAG,cAAc,EAAE,GAAG,OAAO,CAAA;IACxF,IAAI,OAAO,GAAG,MAAM,kBAAkB,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAA;IAEhE,IAAI,QAAQ,EAAE,CAAC;QACb,kGAAkG;QAClG,wFAAwF;QACxF,MAAM,MAAM,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAA;QACxF,OAAO,GAAG,OAAO;aACd,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,KAAK,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,MAAM,GAAG,CAAC,CAAC;aACrF,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;YACb,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,KAAK,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;YAClF,OAAO,EAAE,GAAG,KAAK,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAA;QAC1D,CAAC,CAAC,CAAA;IACN,CAAC;IAED,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAC3B,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,SAAS,CAAC,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,SAAS,CAAC,EAAE,KAAK,CAAC,QAAQ,EAAE,CACnF,CAAA;AACH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "canopycms-next",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.40",
|
|
4
4
|
"description": "Next.js adapter for CanopyCMS",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"peerDependencies": {
|
|
37
37
|
"next": "^13.5.7 || ^14.2.25 || ^15.2.3 || ^16",
|
|
38
38
|
"react": "^18.0.0 || ^19.0.0",
|
|
39
|
-
"canopycms": "0.0.
|
|
39
|
+
"canopycms": "0.0.40"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/node": "^22.9.0",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"react": "^18.3.1",
|
|
47
47
|
"typescript": "^5.6.3",
|
|
48
48
|
"vitest": "^1.6.0",
|
|
49
|
-
"canopycms": "0.0.
|
|
49
|
+
"canopycms": "0.0.40"
|
|
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",
|