canopycms 0.0.17 → 0.0.18
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 +120 -34
- package/dist/ai/json-to-markdown.js +1 -1
- package/dist/ai/json-to-markdown.js.map +1 -1
- package/dist/api/content.d.ts.map +1 -1
- package/dist/api/content.js +12 -11
- package/dist/api/content.js.map +1 -1
- package/dist/cli/generate-ai-content.js +39 -20
- package/dist/cli/init.d.ts +5 -0
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +66 -15
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/template-files/canopy.ts.template +10 -6
- package/dist/cli/template-files/edit-page-dev.tsx.template +16 -0
- package/dist/cli/template-files/edit-page.tsx.template +14 -21
- package/dist/cli/template-files/middleware-clerk.ts.template +13 -0
- package/dist/cli/template-files/middleware.ts.template +30 -0
- package/dist/cli/template-files/next.config-static.ts.template +13 -0
- package/dist/cli/template-files/next.config.ts.template +5 -0
- package/dist/cli/template-files/schemas.ts.template +4 -2
- package/dist/cli/templates.d.ts +8 -0
- package/dist/cli/templates.d.ts.map +1 -1
- package/dist/cli/templates.js +22 -2
- package/dist/cli/templates.js.map +1 -1
- package/dist/config/schemas/field.d.ts +21 -0
- package/dist/config/schemas/field.d.ts.map +1 -1
- package/dist/config/schemas/field.js +1 -0
- package/dist/config/schemas/field.js.map +1 -1
- package/dist/config/types.d.ts +7 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/content-listing.d.ts +3 -2
- package/dist/content-listing.d.ts.map +1 -1
- package/dist/content-listing.js +8 -5
- package/dist/content-listing.js.map +1 -1
- package/dist/content-reader.d.ts.map +1 -1
- package/dist/content-reader.js +13 -9
- package/dist/content-reader.js.map +1 -1
- package/dist/content-store.d.ts +5 -0
- package/dist/content-store.d.ts.map +1 -1
- package/dist/content-store.js +29 -19
- package/dist/content-store.js.map +1 -1
- package/dist/content-tree.d.ts.map +1 -1
- package/dist/content-tree.js +7 -2
- package/dist/content-tree.js.map +1 -1
- package/dist/context.d.ts +38 -7
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +32 -0
- package/dist/context.js.map +1 -1
- package/dist/editor/editor-config.js +2 -2
- package/dist/editor/editor-config.js.map +1 -1
- package/dist/entry-schema-registry.d.ts.map +1 -1
- package/dist/entry-schema-registry.js +9 -0
- package/dist/entry-schema-registry.js.map +1 -1
- package/dist/entry-schema.d.ts +1 -0
- package/dist/entry-schema.d.ts.map +1 -1
- package/dist/entry-schema.js.map +1 -1
- package/dist/github-service.d.ts.map +1 -1
- package/dist/github-service.js +11 -7
- package/dist/github-service.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/paths/index.d.ts +1 -1
- package/dist/paths/index.d.ts.map +1 -1
- package/dist/paths/index.js +1 -1
- package/dist/paths/index.js.map +1 -1
- package/dist/paths/normalize.d.ts +8 -0
- package/dist/paths/normalize.d.ts.map +1 -1
- package/dist/paths/normalize.js +17 -0
- package/dist/paths/normalize.js.map +1 -1
- package/dist/url-path-resolver.d.ts +16 -0
- package/dist/url-path-resolver.d.ts.map +1 -0
- package/dist/url-path-resolver.js +31 -0
- package/dist/url-path-resolver.js.map +1 -0
- package/dist/utils/body-field.d.ts +18 -0
- package/dist/utils/body-field.d.ts.map +1 -0
- package/dist/utils/body-field.js +39 -0
- package/dist/utils/body-field.js.map +1 -0
- package/dist/utils/sanitize-href.d.ts +19 -0
- package/dist/utils/sanitize-href.d.ts.map +1 -0
- package/dist/utils/sanitize-href.js +30 -0
- package/dist/utils/sanitize-href.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves a URL path to candidate entryPath/slug pairs for content lookup.
|
|
3
|
+
*
|
|
4
|
+
* Returns an ordered list of attempts:
|
|
5
|
+
* 1. Direct entry: last segment is slug, rest is collection path
|
|
6
|
+
* 2. Index fallback: full path is collection, slug is 'index'
|
|
7
|
+
*
|
|
8
|
+
* @param urlPath - URL path like '/docs/guides/getting-started' or 'docs/guides'
|
|
9
|
+
* @param contentRoot - Content root directory name (default: 'content')
|
|
10
|
+
* @returns Array of { entryPath, slug } candidates to try in order
|
|
11
|
+
*/
|
|
12
|
+
export declare function resolveUrlPathCandidates(urlPath: string, contentRoot: string): Array<{
|
|
13
|
+
entryPath: string;
|
|
14
|
+
slug: string;
|
|
15
|
+
}>;
|
|
16
|
+
//# sourceMappingURL=url-path-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url-path-resolver.d.ts","sourceRoot":"","sources":["../src/url-path-resolver.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,GAClB,KAAK,CAAC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAsB5C"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { trimSlashes } from './paths/normalize.js';
|
|
2
|
+
/**
|
|
3
|
+
* Resolves a URL path to candidate entryPath/slug pairs for content lookup.
|
|
4
|
+
*
|
|
5
|
+
* Returns an ordered list of attempts:
|
|
6
|
+
* 1. Direct entry: last segment is slug, rest is collection path
|
|
7
|
+
* 2. Index fallback: full path is collection, slug is 'index'
|
|
8
|
+
*
|
|
9
|
+
* @param urlPath - URL path like '/docs/guides/getting-started' or 'docs/guides'
|
|
10
|
+
* @param contentRoot - Content root directory name (default: 'content')
|
|
11
|
+
* @returns Array of { entryPath, slug } candidates to try in order
|
|
12
|
+
*/
|
|
13
|
+
export function resolveUrlPathCandidates(urlPath, contentRoot) {
|
|
14
|
+
const normalized = trimSlashes(urlPath);
|
|
15
|
+
const segments = normalized.split('/').filter(Boolean);
|
|
16
|
+
if (segments.length === 0)
|
|
17
|
+
return [];
|
|
18
|
+
const candidates = [];
|
|
19
|
+
// Try 1: last segment is the entry slug, rest is the collection path
|
|
20
|
+
const slug = segments[segments.length - 1];
|
|
21
|
+
const collectionSegments = segments.slice(0, -1);
|
|
22
|
+
const entryPath = collectionSegments.length > 0 ? `${contentRoot}/${collectionSegments.join('/')}` : contentRoot;
|
|
23
|
+
candidates.push({ entryPath, slug });
|
|
24
|
+
// Try 2: full path is a collection with an index entry
|
|
25
|
+
candidates.push({
|
|
26
|
+
entryPath: `${contentRoot}/${segments.join('/')}`,
|
|
27
|
+
slug: 'index',
|
|
28
|
+
});
|
|
29
|
+
return candidates;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=url-path-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url-path-resolver.js","sourceRoot":"","sources":["../src/url-path-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAE/C;;;;;;;;;;GAUG;AACH,MAAM,UAAU,wBAAwB,CACtC,OAAe,EACf,WAAmB;IAEnB,MAAM,UAAU,GAAG,WAAW,CAAC,OAAO,CAAC,CAAA;IACvC,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IACtD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAA;IAEpC,MAAM,UAAU,GAA+C,EAAE,CAAA;IAEjE,qEAAqE;IACrE,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IAC1C,MAAM,kBAAkB,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;IAChD,MAAM,SAAS,GACb,kBAAkB,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,WAAW,IAAI,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,CAAA;IAEhG,UAAU,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAEpC,uDAAuD;IACvD,UAAU,CAAC,IAAI,CAAC;QACd,SAAS,EAAE,GAAG,WAAW,IAAI,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE;QACjD,IAAI,EAAE,OAAO;KACd,CAAC,CAAA;IAEF,OAAO,UAAU,CAAA;AACnB,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { FieldConfig } from '../config';
|
|
2
|
+
/**
|
|
3
|
+
* Count the number of top-level fields marked `isBody: true` in a schema.
|
|
4
|
+
* Does NOT recurse into objects — isBody only makes sense at the top level
|
|
5
|
+
* because it maps to the file's markdown content.
|
|
6
|
+
*/
|
|
7
|
+
export declare function countBodyFields(fields: readonly FieldConfig[]): number;
|
|
8
|
+
/**
|
|
9
|
+
* Find the name of the field marked `isBody: true`, or `'body'` as the default.
|
|
10
|
+
* Used at read time to map the markdown file's content to the correct data field.
|
|
11
|
+
*/
|
|
12
|
+
export declare function findBodyFieldName(fields: readonly FieldConfig[]): string;
|
|
13
|
+
/**
|
|
14
|
+
* Find isBody fields that have an invalid type (not 'markdown' or 'mdx').
|
|
15
|
+
* Returns field names that fail validation.
|
|
16
|
+
*/
|
|
17
|
+
export declare function findInvalidBodyFields(fields: readonly FieldConfig[]): string[];
|
|
18
|
+
//# sourceMappingURL=body-field.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"body-field.d.ts","sourceRoot":"","sources":["../../src/utils/body-field.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAI5C;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,WAAW,EAAE,GAAG,MAAM,CAMtE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,WAAW,EAAE,GAAG,MAAM,CAKxE;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,WAAW,EAAE,GAAG,MAAM,EAAE,CAQ9E"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const BODY_FIELD_TYPES = new Set(['markdown', 'mdx']);
|
|
2
|
+
/**
|
|
3
|
+
* Count the number of top-level fields marked `isBody: true` in a schema.
|
|
4
|
+
* Does NOT recurse into objects — isBody only makes sense at the top level
|
|
5
|
+
* because it maps to the file's markdown content.
|
|
6
|
+
*/
|
|
7
|
+
export function countBodyFields(fields) {
|
|
8
|
+
let count = 0;
|
|
9
|
+
for (const field of fields) {
|
|
10
|
+
if ('isBody' in field && field.isBody)
|
|
11
|
+
count++;
|
|
12
|
+
}
|
|
13
|
+
return count;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Find the name of the field marked `isBody: true`, or `'body'` as the default.
|
|
17
|
+
* Used at read time to map the markdown file's content to the correct data field.
|
|
18
|
+
*/
|
|
19
|
+
export function findBodyFieldName(fields) {
|
|
20
|
+
for (const field of fields) {
|
|
21
|
+
if ('isBody' in field && field.isBody)
|
|
22
|
+
return field.name;
|
|
23
|
+
}
|
|
24
|
+
return 'body';
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Find isBody fields that have an invalid type (not 'markdown' or 'mdx').
|
|
28
|
+
* Returns field names that fail validation.
|
|
29
|
+
*/
|
|
30
|
+
export function findInvalidBodyFields(fields) {
|
|
31
|
+
const invalid = [];
|
|
32
|
+
for (const field of fields) {
|
|
33
|
+
if ('isBody' in field && field.isBody && !BODY_FIELD_TYPES.has(field.type)) {
|
|
34
|
+
invalid.push(field.name);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return invalid;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=body-field.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"body-field.js","sourceRoot":"","sources":["../../src/utils/body-field.ts"],"names":[],"mappings":"AAEA,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC,CAAA;AAErD;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,MAA8B;IAC5D,IAAI,KAAK,GAAG,CAAC,CAAA;IACb,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,QAAQ,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM;YAAE,KAAK,EAAE,CAAA;IAChD,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAA8B;IAC9D,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,QAAQ,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM;YAAE,OAAO,KAAK,CAAC,IAAI,CAAA;IAC1D,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,MAA8B;IAClE,MAAM,OAAO,GAAa,EAAE,CAAA;IAC5B,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,QAAQ,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YAC3E,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QAC1B,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sanitize an untrusted URL for use in `href` attributes.
|
|
3
|
+
*
|
|
4
|
+
* Parses the input with `new URL()` and only allows `http:` and `https:` protocols,
|
|
5
|
+
* blocking `javascript:`, `data:`, `vbscript:`, and other dangerous schemes.
|
|
6
|
+
* Returns the fallback (default `'#'`) for invalid or disallowed URLs.
|
|
7
|
+
*
|
|
8
|
+
* This utility breaks CodeQL's taint chain by constructing a new string from
|
|
9
|
+
* the parsed URL rather than passing the original input through.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```tsx
|
|
13
|
+
* import { sanitizeHref } from 'canopycms'
|
|
14
|
+
*
|
|
15
|
+
* <a href={sanitizeHref(cta.link)}>{cta.text}</a>
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export declare function sanitizeHref(url: string, fallback?: string): string;
|
|
19
|
+
//# sourceMappingURL=sanitize-href.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanitize-href.d.ts","sourceRoot":"","sources":["../../src/utils/sanitize-href.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,SAAM,GAAG,MAAM,CAUhE"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sanitize an untrusted URL for use in `href` attributes.
|
|
3
|
+
*
|
|
4
|
+
* Parses the input with `new URL()` and only allows `http:` and `https:` protocols,
|
|
5
|
+
* blocking `javascript:`, `data:`, `vbscript:`, and other dangerous schemes.
|
|
6
|
+
* Returns the fallback (default `'#'`) for invalid or disallowed URLs.
|
|
7
|
+
*
|
|
8
|
+
* This utility breaks CodeQL's taint chain by constructing a new string from
|
|
9
|
+
* the parsed URL rather than passing the original input through.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```tsx
|
|
13
|
+
* import { sanitizeHref } from 'canopycms'
|
|
14
|
+
*
|
|
15
|
+
* <a href={sanitizeHref(cta.link)}>{cta.text}</a>
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export function sanitizeHref(url, fallback = '#') {
|
|
19
|
+
try {
|
|
20
|
+
const parsed = new URL(url);
|
|
21
|
+
if (parsed.protocol === 'http:' || parsed.protocol === 'https:') {
|
|
22
|
+
return parsed.href;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// invalid URL
|
|
27
|
+
}
|
|
28
|
+
return fallback;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=sanitize-href.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanitize-href.js","sourceRoot":"","sources":["../../src/utils/sanitize-href.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,YAAY,CAAC,GAAW,EAAE,QAAQ,GAAG,GAAG;IACtD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAA;QAC3B,IAAI,MAAM,CAAC,QAAQ,KAAK,OAAO,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAChE,OAAO,MAAM,CAAC,IAAI,CAAA;QACpB,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,cAAc;IAChB,CAAC;IACD,OAAO,QAAQ,CAAA;AACjB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"//": "@codemirror/language, @lezer/highlight: workaround — @mdxeditor/editor uses cm6-theme-basic-light which peer-requires these but mdxeditor doesn't declare them as dependencies",
|
|
3
3
|
"name": "canopycms",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.18",
|
|
5
5
|
"description": "CanopyCMS core package: schema-driven content, branch-aware editing, and editor UI for Next.js.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|