canopycms 0.0.54 → 0.0.56
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/api/assets.d.ts +14 -2
- package/dist/api/assets.d.ts.map +1 -1
- package/dist/api/assets.js +14 -3
- package/dist/api/assets.js.map +1 -1
- package/dist/api/branch-status.d.ts.map +1 -1
- package/dist/api/branch-status.js +8 -2
- package/dist/api/branch-status.js.map +1 -1
- package/dist/api/client.d.ts +2 -2
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +29 -14
- package/dist/api/client.js.map +1 -1
- package/dist/api/content.d.ts +2 -2
- package/dist/api/content.d.ts.map +1 -1
- package/dist/api/content.js +160 -26
- package/dist/api/content.js.map +1 -1
- package/dist/api/entries.d.ts.map +1 -1
- package/dist/api/entries.js +8 -4
- package/dist/api/entries.js.map +1 -1
- package/dist/api/github-sync.d.ts.map +1 -1
- package/dist/api/github-sync.js +45 -7
- package/dist/api/github-sync.js.map +1 -1
- package/dist/api/groups.d.ts.map +1 -1
- package/dist/api/groups.js +15 -5
- package/dist/api/groups.js.map +1 -1
- package/dist/api/permissions.d.ts +6 -6
- package/dist/api/permissions.d.ts.map +1 -1
- package/dist/api/permissions.js +24 -9
- package/dist/api/permissions.js.map +1 -1
- package/dist/api/request-body-hash.d.ts +33 -0
- package/dist/api/request-body-hash.d.ts.map +1 -0
- package/dist/api/request-body-hash.js +43 -0
- package/dist/api/request-body-hash.js.map +1 -0
- package/dist/api/resolve-references.d.ts.map +1 -1
- package/dist/api/resolve-references.js +8 -1
- package/dist/api/resolve-references.js.map +1 -1
- package/dist/api/schema.js +9 -9
- package/dist/api/schema.js.map +1 -1
- package/dist/api/settings-helpers.d.ts +14 -1
- package/dist/api/settings-helpers.d.ts.map +1 -1
- package/dist/api/settings-helpers.js +11 -2
- package/dist/api/settings-helpers.js.map +1 -1
- package/dist/api/types.d.ts +10 -0
- package/dist/api/types.d.ts.map +1 -1
- package/dist/auth/caching-auth-plugin.d.ts +8 -1
- package/dist/auth/caching-auth-plugin.d.ts.map +1 -1
- package/dist/auth/caching-auth-plugin.js +6 -1
- package/dist/auth/caching-auth-plugin.js.map +1 -1
- package/dist/auth/index.d.ts +1 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +1 -0
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/plugin.d.ts +35 -0
- package/dist/auth/plugin.d.ts.map +1 -1
- package/dist/auth/plugin.js +27 -1
- package/dist/auth/plugin.js.map +1 -1
- package/dist/authorization/helpers.d.ts +16 -0
- package/dist/authorization/helpers.d.ts.map +1 -1
- package/dist/authorization/helpers.js +18 -0
- package/dist/authorization/helpers.js.map +1 -1
- package/dist/authorization/index.d.ts +1 -1
- package/dist/authorization/index.d.ts.map +1 -1
- package/dist/authorization/index.js +1 -1
- package/dist/authorization/index.js.map +1 -1
- package/dist/build/generate-ai-content.d.ts.map +1 -1
- package/dist/build/generate-ai-content.js +7 -0
- package/dist/build/generate-ai-content.js.map +1 -1
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +2 -1
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/generate-ai-content.js +1150 -355
- package/dist/cli/init.d.ts +1 -1
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +40 -13
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/sync.d.ts.map +1 -1
- package/dist/cli/sync.js +88 -60
- package/dist/cli/sync.js.map +1 -1
- package/dist/cli/template-files/canopy.ts.template +2 -1
- package/dist/cli/template-files/edit-page.tsx.template +8 -1
- package/dist/cli/template-files/middleware.ts.template +18 -0
- package/dist/cli/templates.d.ts.map +1 -1
- package/dist/cli/templates.js +19 -4
- package/dist/cli/templates.js.map +1 -1
- package/dist/config/helpers.d.ts +9 -1
- package/dist/config/helpers.d.ts.map +1 -1
- package/dist/config/helpers.js +33 -75
- package/dist/config/helpers.js.map +1 -1
- package/dist/config/schemas/config.d.ts +8 -24
- package/dist/config/schemas/config.d.ts.map +1 -1
- package/dist/config/schemas/config.js +12 -5
- package/dist/config/schemas/config.js.map +1 -1
- package/dist/config/schemas/media.d.ts +1 -10
- package/dist/config/schemas/media.d.ts.map +1 -1
- package/dist/config/schemas/media.js +8 -6
- package/dist/config/schemas/media.js.map +1 -1
- package/dist/config/types.d.ts +6 -4
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config-test.d.ts +5 -3
- package/dist/config-test.d.ts.map +1 -1
- package/dist/config-test.js +9 -1
- package/dist/config-test.js.map +1 -1
- package/dist/content-id-index.d.ts +12 -6
- package/dist/content-id-index.d.ts.map +1 -1
- package/dist/content-id-index.js +12 -6
- package/dist/content-id-index.js.map +1 -1
- package/dist/content-index-generation.d.ts +30 -0
- package/dist/content-index-generation.d.ts.map +1 -0
- package/dist/content-index-generation.js +131 -0
- package/dist/content-index-generation.js.map +1 -0
- package/dist/content-index-registry.d.ts +38 -0
- package/dist/content-index-registry.d.ts.map +1 -0
- package/dist/content-index-registry.js +53 -0
- package/dist/content-index-registry.js.map +1 -0
- package/dist/content-listing.d.ts +3 -1
- package/dist/content-listing.d.ts.map +1 -1
- package/dist/content-listing.js +1 -0
- package/dist/content-listing.js.map +1 -1
- package/dist/content-store.d.ts +130 -6
- package/dist/content-store.d.ts.map +1 -1
- package/dist/content-store.js +343 -31
- package/dist/content-store.js.map +1 -1
- package/dist/editor/Editor.d.ts.map +1 -1
- package/dist/editor/Editor.js +27 -19
- package/dist/editor/Editor.js.map +1 -1
- package/dist/editor/FormRenderer.d.ts +7 -0
- package/dist/editor/FormRenderer.d.ts.map +1 -1
- package/dist/editor/FormRenderer.js +12 -3
- package/dist/editor/FormRenderer.js.map +1 -1
- package/dist/editor/components/EditorHeader.d.ts.map +1 -1
- package/dist/editor/components/EditorHeader.js +3 -4
- package/dist/editor/components/EditorHeader.js.map +1 -1
- package/dist/editor/components/EditorSidebar.d.ts.map +1 -1
- package/dist/editor/components/EditorSidebar.js +2 -4
- package/dist/editor/components/EditorSidebar.js.map +1 -1
- package/dist/editor/fields/ReferenceField.d.ts.map +1 -1
- package/dist/editor/fields/ReferenceField.js +19 -2
- package/dist/editor/fields/ReferenceField.js.map +1 -1
- package/dist/editor/group-manager/hooks/useGroupState.d.ts.map +1 -1
- package/dist/editor/group-manager/hooks/useGroupState.js +5 -2
- package/dist/editor/group-manager/hooks/useGroupState.js.map +1 -1
- package/dist/editor/hooks/useDraftManager.d.ts +15 -0
- package/dist/editor/hooks/useDraftManager.d.ts.map +1 -1
- package/dist/editor/hooks/useDraftManager.js +140 -1
- package/dist/editor/hooks/useDraftManager.js.map +1 -1
- package/dist/editor/hooks/useEntryManager.d.ts +9 -2
- package/dist/editor/hooks/useEntryManager.d.ts.map +1 -1
- package/dist/editor/hooks/useEntryManager.js +9 -3
- package/dist/editor/hooks/useEntryManager.js.map +1 -1
- package/dist/editor/hooks/useSchemaManager.d.ts +34 -10
- package/dist/editor/hooks/useSchemaManager.d.ts.map +1 -1
- package/dist/editor/hooks/useSchemaManager.js +22 -25
- package/dist/editor/hooks/useSchemaManager.js.map +1 -1
- package/dist/editor/schema-editor/CollectionEditor.d.ts +6 -5
- package/dist/editor/schema-editor/CollectionEditor.d.ts.map +1 -1
- package/dist/editor/schema-editor/CollectionEditor.js +37 -12
- package/dist/editor/schema-editor/CollectionEditor.js.map +1 -1
- package/dist/entry-link-resolver.d.ts.map +1 -1
- package/dist/entry-link-resolver.js +5 -0
- package/dist/entry-link-resolver.js.map +1 -1
- package/dist/git-manager.d.ts +38 -3
- package/dist/git-manager.d.ts.map +1 -1
- package/dist/git-manager.js +141 -33
- package/dist/git-manager.js.map +1 -1
- package/dist/github-service.d.ts +69 -3
- package/dist/github-service.d.ts.map +1 -1
- package/dist/github-service.js +149 -25
- package/dist/github-service.js.map +1 -1
- package/dist/http/handler.d.ts +1 -1
- package/dist/http/handler.d.ts.map +1 -1
- package/dist/http/handler.js +36 -7
- package/dist/http/handler.js.map +1 -1
- package/dist/http/router.d.ts +11 -0
- package/dist/http/router.d.ts.map +1 -1
- package/dist/http/router.js +80 -16
- package/dist/http/router.js.map +1 -1
- package/dist/paths/validation.d.ts.map +1 -1
- package/dist/paths/validation.js +12 -0
- package/dist/paths/validation.js.map +1 -1
- package/dist/schema/schema-store.d.ts +9 -0
- package/dist/schema/schema-store.d.ts.map +1 -1
- package/dist/schema/schema-store.js +57 -8
- package/dist/schema/schema-store.js.map +1 -1
- package/dist/services.d.ts +1 -0
- package/dist/services.d.ts.map +1 -1
- package/dist/services.js +9 -2
- package/dist/services.js.map +1 -1
- package/dist/static/index.d.ts +32 -0
- package/dist/static/index.d.ts.map +1 -1
- package/dist/static/index.js +90 -0
- package/dist/static/index.js.map +1 -1
- package/dist/sync-core.d.ts.map +1 -1
- package/dist/sync-core.js +22 -9
- package/dist/sync-core.js.map +1 -1
- package/dist/user.d.ts +0 -18
- package/dist/user.d.ts.map +1 -1
- package/dist/user.js +7 -22
- package/dist/user.js.map +1 -1
- package/dist/utils/error.d.ts +8 -0
- package/dist/utils/error.d.ts.map +1 -1
- package/dist/utils/error.js +35 -7
- package/dist/utils/error.js.map +1 -1
- package/dist/validation/deletion-checker.d.ts.map +1 -1
- package/dist/validation/deletion-checker.js +6 -5
- package/dist/validation/deletion-checker.js.map +1 -1
- package/dist/validation/entry-validator.d.ts +67 -0
- package/dist/validation/entry-validator.d.ts.map +1 -0
- package/dist/validation/entry-validator.js +277 -0
- package/dist/validation/entry-validator.js.map +1 -0
- package/dist/validation/field-traversal.d.ts +23 -1
- package/dist/validation/field-traversal.d.ts.map +1 -1
- package/dist/validation/field-traversal.js +29 -9
- package/dist/validation/field-traversal.js.map +1 -1
- package/dist/worker/cms-worker.d.ts +70 -6
- package/dist/worker/cms-worker.d.ts.map +1 -1
- package/dist/worker/cms-worker.js +199 -93
- package/dist/worker/cms-worker.js.map +1 -1
- package/package.json +2 -2
- package/dist/editor/components/EditorContext.d.ts +0 -35
- package/dist/editor/components/EditorContext.d.ts.map +0 -1
- package/dist/editor/components/EditorContext.js +0 -19
- package/dist/editor/components/EditorContext.js.map +0 -1
- package/dist/editor/env.d.ts +0 -5
- package/dist/editor/env.d.ts.map +0 -1
- package/dist/editor/env.js +0 -5
- package/dist/editor/env.js.map +0 -1
package/dist/user.d.ts
CHANGED
|
@@ -29,24 +29,6 @@ export type CanopyUser = AnonymousUser | AuthenticatedUser;
|
|
|
29
29
|
* Callers must explicitly pass this to indicate anonymous access is intended.
|
|
30
30
|
*/
|
|
31
31
|
export declare const ANONYMOUS_USER: AnonymousUser;
|
|
32
|
-
/**
|
|
33
|
-
* Type guard for anonymous user
|
|
34
|
-
*/
|
|
35
|
-
export declare const isAnonymousUser: (user: CanopyUser) => user is AnonymousUser;
|
|
36
|
-
/**
|
|
37
|
-
* Type guard for authenticated user
|
|
38
|
-
*/
|
|
39
|
-
export declare const isAuthenticatedUser: (user: CanopyUser) => user is AuthenticatedUser;
|
|
40
|
-
/**
|
|
41
|
-
* Create an authenticated user from auth provider data
|
|
42
|
-
*/
|
|
43
|
-
export declare const createAuthenticatedUser: (data: {
|
|
44
|
-
userId: CanopyUserId;
|
|
45
|
-
groups?: CanopyGroupId[];
|
|
46
|
-
email?: string;
|
|
47
|
-
name?: string;
|
|
48
|
-
avatarUrl?: string;
|
|
49
|
-
}) => AuthenticatedUser;
|
|
50
32
|
/**
|
|
51
33
|
* Convert authentication result to CanopyUser.
|
|
52
34
|
* Applies bootstrap admin groups, merges internal groups, and returns ANONYMOUS_USER if not authenticated.
|
package/dist/user.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user.d.ts","sourceRoot":"","sources":["../src/user.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAC1D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AAGxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAA;AAElE;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,WAAW,CAAA;IACjB,MAAM,EAAE,WAAW,CAAA;IACnB,MAAM,EAAE,SAAS,EAAE,CAAA;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,eAAe,CAAA;IACrB,MAAM,EAAE,YAAY,CAAA;IACpB,MAAM,EAAE,aAAa,EAAE,CAAA;IACvB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,aAAa,GAAG,iBAAiB,CAAA;AAE1D;;;GAGG;AACH,eAAO,MAAM,cAAc,EAAE,aAIV,CAAA;AAEnB
|
|
1
|
+
{"version":3,"file":"user.d.ts","sourceRoot":"","sources":["../src/user.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAC1D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AAGxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAA;AAElE;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,WAAW,CAAA;IACjB,MAAM,EAAE,WAAW,CAAA;IACnB,MAAM,EAAE,SAAS,EAAE,CAAA;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,eAAe,CAAA;IACrB,MAAM,EAAE,YAAY,CAAA;IACpB,MAAM,EAAE,aAAa,EAAE,CAAA;IACvB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,aAAa,GAAG,iBAAiB,CAAA;AAE1D;;;GAGG;AACH,eAAO,MAAM,cAAc,EAAE,aAIV,CAAA;AAEnB;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,oBAAoB,EAChC,iBAAiB,EAAE,GAAG,CAAC,MAAM,CAAC,EAC9B,cAAc,CAAC,EAAE,aAAa,EAAE,GAC/B,UAAU,CAkCZ"}
|
package/dist/user.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Import from client-safe subpaths to avoid pulling in server-only loader code
|
|
2
|
-
import { RESERVED_GROUPS } from './authorization/helpers.js';
|
|
2
|
+
import { RESERVED_GROUPS, stripReservedGroups } from './authorization/helpers.js';
|
|
3
3
|
/**
|
|
4
4
|
* Branded ANONYMOUS_USER constant - use this for intentional anonymous access.
|
|
5
5
|
* Callers must explicitly pass this to indicate anonymous access is intended.
|
|
@@ -9,25 +9,6 @@ export const ANONYMOUS_USER = Object.freeze({
|
|
|
9
9
|
userId: 'anonymous',
|
|
10
10
|
groups: [],
|
|
11
11
|
});
|
|
12
|
-
/**
|
|
13
|
-
* Type guard for anonymous user
|
|
14
|
-
*/
|
|
15
|
-
export const isAnonymousUser = (user) => user.type === 'anonymous';
|
|
16
|
-
/**
|
|
17
|
-
* Type guard for authenticated user
|
|
18
|
-
*/
|
|
19
|
-
export const isAuthenticatedUser = (user) => user.type === 'authenticated';
|
|
20
|
-
/**
|
|
21
|
-
* Create an authenticated user from auth provider data
|
|
22
|
-
*/
|
|
23
|
-
export const createAuthenticatedUser = (data) => ({
|
|
24
|
-
type: 'authenticated',
|
|
25
|
-
userId: data.userId,
|
|
26
|
-
groups: data.groups ?? [],
|
|
27
|
-
email: data.email,
|
|
28
|
-
name: data.name,
|
|
29
|
-
avatarUrl: data.avatarUrl,
|
|
30
|
-
});
|
|
31
12
|
/**
|
|
32
13
|
* Convert authentication result to CanopyUser.
|
|
33
14
|
* Applies bootstrap admin groups, merges internal groups, and returns ANONYMOUS_USER if not authenticated.
|
|
@@ -43,8 +24,12 @@ export function authResultToCanopyUser(authResult, bootstrapAdminIds, internalGr
|
|
|
43
24
|
if (!authResult.success || !authResult.user) {
|
|
44
25
|
return ANONYMOUS_USER;
|
|
45
26
|
}
|
|
46
|
-
// Step 1: Start with external groups from auth provider
|
|
47
|
-
|
|
27
|
+
// Step 1: Start with external groups from the auth provider.
|
|
28
|
+
// SECURITY (SEC-H1): reserved privileged group IDs (Admins/Reviewers) are
|
|
29
|
+
// stripped here so a provider-controlled group name can never grant
|
|
30
|
+
// CanopyCMS privilege. Reserved membership is only added below, from
|
|
31
|
+
// bootstrapAdminIds and Canopy-managed internal groups.
|
|
32
|
+
const groups = stripReservedGroups(authResult.user.externalGroups ?? []);
|
|
48
33
|
// Step 2: Add Admins group if user is in bootstrap admin list
|
|
49
34
|
if (bootstrapAdminIds.has(authResult.user.userId) && !groups.includes(RESERVED_GROUPS.ADMINS)) {
|
|
50
35
|
groups.push(RESERVED_GROUPS.ADMINS);
|
package/dist/user.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user.js","sourceRoot":"","sources":["../src/user.ts"],"names":[],"mappings":"AAEA,+EAA+E;AAC/E,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;
|
|
1
|
+
{"version":3,"file":"user.js","sourceRoot":"","sources":["../src/user.ts"],"names":[],"mappings":"AAEA,+EAA+E;AAC/E,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAA;AA6B9E;;;GAGG;AACH,MAAM,CAAC,MAAM,cAAc,GAAkB,MAAM,CAAC,MAAM,CAAC;IACzD,IAAI,EAAE,WAAW;IACjB,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,EAAW;CACpB,CAAkB,CAAA;AAEnB;;;;;;;;;;GAUG;AACH,MAAM,UAAU,sBAAsB,CACpC,UAAgC,EAChC,iBAA8B,EAC9B,cAAgC;IAEhC,IAAI,CAAC,UAAU,CAAC,OAAO,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;QAC5C,OAAO,cAAc,CAAA;IACvB,CAAC;IAED,6DAA6D;IAC7D,0EAA0E;IAC1E,oEAAoE;IACpE,qEAAqE;IACrE,wDAAwD;IACxD,MAAM,MAAM,GAAG,mBAAmB,CAAC,UAAU,CAAC,IAAI,CAAC,cAAc,IAAI,EAAE,CAAC,CAAA;IAExE,8DAA8D;IAC9D,IAAI,iBAAiB,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAC,MAAM,CAAC,EAAE,CAAC;QAC9F,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,CAAA;IACrC,CAAC;IAED,4DAA4D;IAC5D,IAAI,cAAc,EAAE,CAAC;QACnB,KAAK,MAAM,KAAK,IAAI,cAAc,EAAE,CAAC;YACnC,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC;gBACjF,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;YACvB,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO;QACL,IAAI,EAAE,eAAe;QACrB,MAAM,EAAE,UAAU,CAAC,IAAI,CAAC,MAAM;QAC9B,KAAK,EAAE,UAAU,CAAC,IAAI,CAAC,KAAK;QAC5B,IAAI,EAAE,UAAU,CAAC,IAAI,CAAC,IAAI;QAC1B,SAAS,EAAE,UAAU,CAAC,IAAI,CAAC,SAAS;QACpC,MAAM;KACP,CAAA;AACH,CAAC"}
|
package/dist/utils/error.d.ts
CHANGED
|
@@ -36,6 +36,14 @@ export declare function getErrorMessage(err: unknown): string;
|
|
|
36
36
|
* and not sensitive); absolute paths outside it are replaced with `<path>`.
|
|
37
37
|
*/
|
|
38
38
|
export declare function sanitizeErrorMessage(message: string): string;
|
|
39
|
+
/**
|
|
40
|
+
* Redact only credential material from a message, leaving filesystem paths
|
|
41
|
+
* intact. For server-side log lines that deliberately keep full path detail
|
|
42
|
+
* (server logs only, useful for debugging) but must never persist a live
|
|
43
|
+
* token — client-facing messages go through sanitizeErrorMessage instead,
|
|
44
|
+
* which calls this and then also redacts paths.
|
|
45
|
+
*/
|
|
46
|
+
export declare function redactCredentials(message: string): string;
|
|
39
47
|
/**
|
|
40
48
|
* Type guard to check if an error is a Node.js system error with a code property.
|
|
41
49
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../../src/utils/error.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAQpD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../../src/utils/error.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAQpD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAsC5D;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAYzD;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,MAAM,CAAC,cAAc,CAEtE;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAErD;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAEvD;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAEvD"}
|
package/dist/utils/error.js
CHANGED
|
@@ -44,12 +44,7 @@ export function getErrorMessage(err) {
|
|
|
44
44
|
* and not sensitive); absolute paths outside it are replaced with `<path>`.
|
|
45
45
|
*/
|
|
46
46
|
export function sanitizeErrorMessage(message) {
|
|
47
|
-
let result = message;
|
|
48
|
-
// Credentials in URLs: scheme://user:token@host or scheme://token@host.
|
|
49
|
-
// Anchored on the literal `://` (leaving the scheme untouched) — a `\w+`
|
|
50
|
-
// scheme prefix would backtrack polynomially on long word-character runs
|
|
51
|
-
// (CodeQL js/polynomial-redos).
|
|
52
|
-
result = result.replace(/(:\/\/)[^/\s@]+@/g, '$1***@');
|
|
47
|
+
let result = redactCredentials(message);
|
|
53
48
|
// Paths under the project root become relative (split/join avoids regex
|
|
54
49
|
// escaping issues with arbitrary cwd values). The bare-cwd replacement is
|
|
55
50
|
// anchored to a token boundary so sibling directories that merely share
|
|
@@ -59,6 +54,10 @@ export function sanitizeErrorMessage(message) {
|
|
|
59
54
|
if (cwd !== '/') {
|
|
60
55
|
result = result.split(`${cwd}/`).join('');
|
|
61
56
|
const cwdPattern = cwd.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
57
|
+
// cwdPattern is process.cwd() (server-controlled, not attacker/entry-content
|
|
58
|
+
// data) with regex metacharacters escaped by the literal regex above, so
|
|
59
|
+
// this cannot be used to inject an attacker-chosen pattern.
|
|
60
|
+
// eslint-disable-next-line security/detect-non-literal-regexp
|
|
62
61
|
result = result.replace(new RegExp(`${cwdPattern}(?=[\\s'"),:;]|$)`, 'g'), '.');
|
|
63
62
|
}
|
|
64
63
|
// Quoted absolute paths (git quotes most paths in its messages): redact
|
|
@@ -69,11 +68,40 @@ export function sanitizeErrorMessage(message) {
|
|
|
69
68
|
// limitation: an UNQUOTED path containing spaces is only redacted up to
|
|
70
69
|
// the first space — spaces are legal both inside paths and as message
|
|
71
70
|
// separators, so this is not generally solvable here.
|
|
72
|
-
|
|
71
|
+
//
|
|
72
|
+
// The repeated group `(?:[^/\s'")]+\/)+` looks nested-quantifier-shaped, but
|
|
73
|
+
// the inner character class excludes `/`, so each repetition can only end at
|
|
74
|
+
// a literal `/` boundary in the input: there is exactly one way to decompose
|
|
75
|
+
// any matched string into repetitions (no same-substring ambiguity), so this
|
|
76
|
+
// does not backtrack catastrophically. Verified empirically with inputs up
|
|
77
|
+
// to 400k chars (adversarial runs of non-slash chars, with and without
|
|
78
|
+
// trailing slashes): scaling stayed linear, not quadratic/exponential.
|
|
79
|
+
// eslint-disable-next-line security/detect-unsafe-regex
|
|
80
|
+
result = result.replace(/(^|[\s'"(=:,[])\/(?:[^/\s'")]+\/)+[^/\s'")]*/g, '$1<path>');
|
|
73
81
|
// Windows drive paths
|
|
74
82
|
result = result.replace(/[A-Za-z]:\\[^\s'")]+/g, '<path>');
|
|
75
83
|
return result;
|
|
76
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Redact only credential material from a message, leaving filesystem paths
|
|
87
|
+
* intact. For server-side log lines that deliberately keep full path detail
|
|
88
|
+
* (server logs only, useful for debugging) but must never persist a live
|
|
89
|
+
* token — client-facing messages go through sanitizeErrorMessage instead,
|
|
90
|
+
* which calls this and then also redacts paths.
|
|
91
|
+
*/
|
|
92
|
+
export function redactCredentials(message) {
|
|
93
|
+
let result = message;
|
|
94
|
+
// Credentials in URLs: scheme://user:token@host or scheme://token@host.
|
|
95
|
+
// Anchored on the literal `://` (leaving the scheme untouched) — a `\w+`
|
|
96
|
+
// scheme prefix would backtrack polynomially on long word-character runs
|
|
97
|
+
// (CodeQL js/polynomial-redos).
|
|
98
|
+
result = result.replace(/(:\/\/)[^/\s@]+@/g, '$1***@');
|
|
99
|
+
// Bare token shapes (defense-in-depth for messages that embed a token
|
|
100
|
+
// outside URL userinfo): GitHub token prefixes and Bearer values.
|
|
101
|
+
result = result.replace(/\b(?:gh[pousr]|github_pat)_[A-Za-z0-9_]{8,}/g, '***');
|
|
102
|
+
result = result.replace(/\b(Bearer\s+)[A-Za-z0-9._~+/=-]{8,}/g, '$1***');
|
|
103
|
+
return result;
|
|
104
|
+
}
|
|
77
105
|
/**
|
|
78
106
|
* Type guard to check if an error is a Node.js system error with a code property.
|
|
79
107
|
*
|
package/dist/utils/error.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error.js","sourceRoot":"","sources":["../../src/utils/error.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,eAAe,CAAC,GAAY;IAC1C,IAAI,GAAG,YAAY,KAAK,EAAE,CAAC;QACzB,OAAO,GAAG,CAAC,OAAO,CAAA;IACpB,CAAC;IACD,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC5B,OAAO,GAAG,CAAA;IACZ,CAAC;IACD,OAAO,MAAM,CAAC,GAAG,CAAC,CAAA;AACpB,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAe;IAClD,IAAI,MAAM,GAAG,
|
|
1
|
+
{"version":3,"file":"error.js","sourceRoot":"","sources":["../../src/utils/error.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,eAAe,CAAC,GAAY;IAC1C,IAAI,GAAG,YAAY,KAAK,EAAE,CAAC;QACzB,OAAO,GAAG,CAAC,OAAO,CAAA;IACpB,CAAC;IACD,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC5B,OAAO,GAAG,CAAA;IACZ,CAAC;IACD,OAAO,MAAM,CAAC,GAAG,CAAC,CAAA;AACpB,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAe;IAClD,IAAI,MAAM,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAA;IACvC,wEAAwE;IACxE,0EAA0E;IAC1E,wEAAwE;IACxE,qEAAqE;IACrE,yDAAyD;IACzD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAA;IACzB,IAAI,GAAG,KAAK,GAAG,EAAE,CAAC;QAChB,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QACzC,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAA;QAC7D,6EAA6E;QAC7E,yEAAyE;QACzE,4DAA4D;QAC5D,8DAA8D;QAC9D,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,GAAG,UAAU,mBAAmB,EAAE,GAAG,CAAC,EAAE,GAAG,CAAC,CAAA;IACjF,CAAC;IACD,wEAAwE;IACxE,0CAA0C;IAC1C,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,UAAU,CAAC,CAAA;IACnF,qEAAqE;IACrE,yEAAyE;IACzE,wEAAwE;IACxE,sEAAsE;IACtE,sDAAsD;IACtD,EAAE;IACF,6EAA6E;IAC7E,6EAA6E;IAC7E,6EAA6E;IAC7E,6EAA6E;IAC7E,2EAA2E;IAC3E,uEAAuE;IACvE,uEAAuE;IACvE,wDAAwD;IACxD,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,+CAA+C,EAAE,UAAU,CAAC,CAAA;IACpF,sBAAsB;IACtB,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,uBAAuB,EAAE,QAAQ,CAAC,CAAA;IAC1D,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAe;IAC/C,IAAI,MAAM,GAAG,OAAO,CAAA;IACpB,wEAAwE;IACxE,yEAAyE;IACzE,yEAAyE;IACzE,gCAAgC;IAChC,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,mBAAmB,EAAE,QAAQ,CAAC,CAAA;IACtD,sEAAsE;IACtE,kEAAkE;IAClE,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,8CAA8C,EAAE,KAAK,CAAC,CAAA;IAC9E,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,sCAAsC,EAAE,OAAO,CAAC,CAAA;IACxE,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,WAAW,CAAC,GAAY;IACtC,OAAO,GAAG,YAAY,KAAK,IAAI,MAAM,IAAI,GAAG,CAAA;AAC9C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,GAAY;IAC1C,OAAO,WAAW,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAA;AAClD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAY;IAC5C,OAAO,WAAW,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAA;AAClD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAY;IAC5C,OAAO,WAAW,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAA;AAClD,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deletion-checker.d.ts","sourceRoot":"","sources":["../../src/validation/deletion-checker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACzD,OAAO,KAAK,EACV,WAAW,EAIZ,MAAM,WAAW,CAAA;AAClB,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,IAAI,EAAqB,MAAM,UAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"deletion-checker.d.ts","sourceRoot":"","sources":["../../src/validation/deletion-checker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACzD,OAAO,KAAK,EACV,WAAW,EAIZ,MAAM,WAAW,CAAA;AAClB,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,IAAI,EAAqB,MAAM,UAAU,CAAA;AAGzE,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,UAAU,EAAE,WAAW,CAAA;IACvB,IAAI,EAAE,IAAI,CAAA;IACV,MAAM,EAAE,MAAM,EAAE,CAAA;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,OAAO,CAAA;IAClB,YAAY,EAAE,aAAa,EAAE,CAAA;CAC9B;AAED;;;;;;;;;;;;;;GAcG;AACH,qBAAa,eAAe;IAExB,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,WAAW;gBAFX,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,cAAc,EACvB,WAAW,EAAE,GAAG,CAAC,WAAW,EAAE;QAAE,MAAM,EAAE,WAAW,EAAE,CAAA;KAAE,CAAC;IAGlE;;;;;OAKG;IACG,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAQzD;;;;;OAKG;IACG,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAYhE;;OAEG;YACW,cAAc;IAkC5B;;;OAGG;IACH,OAAO,CAAC,YAAY;IA4FpB;;OAEG;IACH,OAAO,CAAC,uBAAuB;CAgChC"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveBlockItem } from './field-traversal.js';
|
|
1
2
|
/**
|
|
2
3
|
* DeletionChecker finds all references to an entry before deletion.
|
|
3
4
|
*
|
|
@@ -121,15 +122,15 @@ export class DeletionChecker {
|
|
|
121
122
|
}
|
|
122
123
|
}
|
|
123
124
|
else if (field.type === 'block') {
|
|
124
|
-
// Handle block fields
|
|
125
|
+
// Handle block fields — real block items are { template, value }, with
|
|
126
|
+
// nested field values under `value` (resolved by resolveBlockItem)
|
|
125
127
|
const blockField = field;
|
|
126
128
|
if (Array.isArray(value)) {
|
|
127
129
|
value.forEach((item, index) => {
|
|
128
130
|
if (typeof item === 'object' && item !== null) {
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
found.push(...this.findIdInData(item, targetId, blockDef.fields, `${fieldPath}[${index}]`));
|
|
131
|
+
const resolved = resolveBlockItem(blockField, item);
|
|
132
|
+
if (resolved) {
|
|
133
|
+
found.push(...this.findIdInData(resolved.data, targetId, resolved.fields, `${fieldPath}[${index}]`));
|
|
133
134
|
}
|
|
134
135
|
}
|
|
135
136
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deletion-checker.js","sourceRoot":"","sources":["../../src/validation/deletion-checker.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"deletion-checker.js","sourceRoot":"","sources":["../../src/validation/deletion-checker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure, isomorphic schema validation for entry data.
|
|
3
|
+
*
|
|
4
|
+
* This module is the single source of the schema-driven validation rules that
|
|
5
|
+
* run BOTH client-side (pre-save UX in the editor) and server-side (the
|
|
6
|
+
* authoritative write boundary in api/content.ts). It therefore must stay free
|
|
7
|
+
* of server-only imports (node:fs, ContentStore, ContentIdIndex, ...) — it may
|
|
8
|
+
* only depend on config types and other pure modules.
|
|
9
|
+
*
|
|
10
|
+
* What it checks (pure rules — no filesystem access):
|
|
11
|
+
* - required fields are present and non-empty
|
|
12
|
+
* - values match their field type (string/number/boolean/datetime/select/...)
|
|
13
|
+
* - `list` fields hold arrays; non-list fields hold single values
|
|
14
|
+
* - select values are one of the configured options
|
|
15
|
+
* - reference values are well-formed (id string or resolved `{ id }` object)
|
|
16
|
+
* and non-empty when required
|
|
17
|
+
* - block items resolve to a known template (via the shared `resolveBlockItem`
|
|
18
|
+
* from field-traversal, so `{ template, value }`-nested fields are validated)
|
|
19
|
+
*
|
|
20
|
+
* Reference EXISTENCE is intentionally NOT checked here: it requires the
|
|
21
|
+
* content ID index (filesystem) and is enforced server-side only, by running
|
|
22
|
+
* `ReferenceValidator` at the write boundary.
|
|
23
|
+
*/
|
|
24
|
+
import type { ContentFormat, EntrySchema } from '../config';
|
|
25
|
+
/**
|
|
26
|
+
* One per-field validation error. `fieldPath` uses the canonical CanopyCMS
|
|
27
|
+
* path format shared with field-traversal and the editor's canopy-path
|
|
28
|
+
* helpers: dot notation with bracketed indices (e.g. `blocks[0].title`).
|
|
29
|
+
*/
|
|
30
|
+
export interface EntryFieldError {
|
|
31
|
+
fieldPath: string;
|
|
32
|
+
message: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Extract the id from a reference value. The editor holds references either
|
|
36
|
+
* as an id string or as a resolved object `{ id, slug, collection, ... }`
|
|
37
|
+
* (content reads resolve references by default). Returns undefined for any
|
|
38
|
+
* other shape.
|
|
39
|
+
*/
|
|
40
|
+
export declare function referenceValueId(value: unknown): string | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* Merge an md/mdx body string into the data record under the schema's body
|
|
43
|
+
* field name (`isBody: true`, defaulting to `'body'`), so required/type checks
|
|
44
|
+
* apply to the body like any other field.
|
|
45
|
+
*/
|
|
46
|
+
export declare function mergeBodyIntoData(fields: EntrySchema, data: Record<string, unknown>, body: string | undefined): Record<string, unknown>;
|
|
47
|
+
/**
|
|
48
|
+
* Validate entry data against an entry schema. Pure — safe to run in the
|
|
49
|
+
* browser. Returns one error per offending field path; an empty array means
|
|
50
|
+
* the data is structurally valid.
|
|
51
|
+
*/
|
|
52
|
+
export declare function validateEntryData(fields: EntrySchema, data: Record<string, unknown>, pathPrefix?: string): EntryFieldError[];
|
|
53
|
+
/**
|
|
54
|
+
* Validate an editor FormValue (client-side entry point). For md/mdx formats
|
|
55
|
+
* the editor keeps the body under the literal `body` key; remap it to the
|
|
56
|
+
* schema's body field before validating so required/type checks see it.
|
|
57
|
+
*/
|
|
58
|
+
export declare function validateEntryFormValue(fields: EntrySchema, format: ContentFormat | undefined, value: Record<string, unknown>): EntryFieldError[];
|
|
59
|
+
/**
|
|
60
|
+
* Return a copy of `data` with resolved reference objects (`{ id, ... }`)
|
|
61
|
+
* collapsed back to their id strings, recursing through objects, lists, and
|
|
62
|
+
* block `{ template, value }` items. Used server-side so `ReferenceValidator`
|
|
63
|
+
* (which expects id strings) can check reference existence on editor payloads
|
|
64
|
+
* that may still carry resolved objects from a prior read.
|
|
65
|
+
*/
|
|
66
|
+
export declare function normalizeReferenceValues(fields: EntrySchema, data: Record<string, unknown>): Record<string, unknown>;
|
|
67
|
+
//# sourceMappingURL=entry-validator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry-validator.d.ts","sourceRoot":"","sources":["../../src/validation/entry-validator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAEV,aAAa,EACb,WAAW,EAMZ,MAAM,WAAW,CAAA;AAMlB;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;CAChB;AAWD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAInE;AAKD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,IAAI,EAAE,MAAM,GAAG,SAAS,GACvB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAGzB;AAyDD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,UAAU,SAAK,GACd,eAAe,EAAE,CAiGnB;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,WAAW,EACnB,MAAM,EAAE,aAAa,GAAG,SAAS,EACjC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,eAAe,EAAE,CASnB;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAqDzB"}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure, isomorphic schema validation for entry data.
|
|
3
|
+
*
|
|
4
|
+
* This module is the single source of the schema-driven validation rules that
|
|
5
|
+
* run BOTH client-side (pre-save UX in the editor) and server-side (the
|
|
6
|
+
* authoritative write boundary in api/content.ts). It therefore must stay free
|
|
7
|
+
* of server-only imports (node:fs, ContentStore, ContentIdIndex, ...) — it may
|
|
8
|
+
* only depend on config types and other pure modules.
|
|
9
|
+
*
|
|
10
|
+
* What it checks (pure rules — no filesystem access):
|
|
11
|
+
* - required fields are present and non-empty
|
|
12
|
+
* - values match their field type (string/number/boolean/datetime/select/...)
|
|
13
|
+
* - `list` fields hold arrays; non-list fields hold single values
|
|
14
|
+
* - select values are one of the configured options
|
|
15
|
+
* - reference values are well-formed (id string or resolved `{ id }` object)
|
|
16
|
+
* and non-empty when required
|
|
17
|
+
* - block items resolve to a known template (via the shared `resolveBlockItem`
|
|
18
|
+
* from field-traversal, so `{ template, value }`-nested fields are validated)
|
|
19
|
+
*
|
|
20
|
+
* Reference EXISTENCE is intentionally NOT checked here: it requires the
|
|
21
|
+
* content ID index (filesystem) and is enforced server-side only, by running
|
|
22
|
+
* `ReferenceValidator` at the write boundary.
|
|
23
|
+
*/
|
|
24
|
+
import { fieldTypes } from '../config/index.js';
|
|
25
|
+
import { resolveBlockItem } from './field-traversal.js';
|
|
26
|
+
import { findBodyFieldName } from '../utils/body-field.js';
|
|
27
|
+
import { isDataOnlyFormat } from '../utils/format.js';
|
|
28
|
+
const KNOWN_FIELD_TYPES = new Set(fieldTypes);
|
|
29
|
+
/** Field types whose value is a plain string. */
|
|
30
|
+
const STRING_FIELD_TYPES = new Set(['string', 'rich-text', 'markdown', 'mdx', 'image', 'code']);
|
|
31
|
+
function isPlainRecord(value) {
|
|
32
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Extract the id from a reference value. The editor holds references either
|
|
36
|
+
* as an id string or as a resolved object `{ id, slug, collection, ... }`
|
|
37
|
+
* (content reads resolve references by default). Returns undefined for any
|
|
38
|
+
* other shape.
|
|
39
|
+
*/
|
|
40
|
+
export function referenceValueId(value) {
|
|
41
|
+
if (typeof value === 'string')
|
|
42
|
+
return value;
|
|
43
|
+
if (isPlainRecord(value) && typeof value.id === 'string')
|
|
44
|
+
return value.id;
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
const selectOptionValues = (field) => (field.options ?? []).map((opt) => (typeof opt === 'string' ? opt : opt.value));
|
|
48
|
+
/**
|
|
49
|
+
* Merge an md/mdx body string into the data record under the schema's body
|
|
50
|
+
* field name (`isBody: true`, defaulting to `'body'`), so required/type checks
|
|
51
|
+
* apply to the body like any other field.
|
|
52
|
+
*/
|
|
53
|
+
export function mergeBodyIntoData(fields, data, body) {
|
|
54
|
+
if (body === undefined)
|
|
55
|
+
return data;
|
|
56
|
+
return { ...data, [findBodyFieldName(fields)]: body };
|
|
57
|
+
}
|
|
58
|
+
/** Validate a single scalar (non-object, non-block) value against its field type. */
|
|
59
|
+
function validateScalar(field, value, path) {
|
|
60
|
+
switch (field.type) {
|
|
61
|
+
case 'number':
|
|
62
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
63
|
+
return [{ fieldPath: path, message: 'Expected a number' }];
|
|
64
|
+
}
|
|
65
|
+
return [];
|
|
66
|
+
case 'boolean':
|
|
67
|
+
if (typeof value !== 'boolean') {
|
|
68
|
+
return [{ fieldPath: path, message: 'Expected true or false' }];
|
|
69
|
+
}
|
|
70
|
+
return [];
|
|
71
|
+
case 'datetime':
|
|
72
|
+
if (typeof value !== 'string' || (value !== '' && Number.isNaN(Date.parse(value)))) {
|
|
73
|
+
return [{ fieldPath: path, message: 'Expected a valid date/time string' }];
|
|
74
|
+
}
|
|
75
|
+
return [];
|
|
76
|
+
case 'select': {
|
|
77
|
+
if (typeof value !== 'string') {
|
|
78
|
+
return [{ fieldPath: path, message: 'Expected a selection' }];
|
|
79
|
+
}
|
|
80
|
+
const options = selectOptionValues(field);
|
|
81
|
+
if (value !== '' && !options.includes(value)) {
|
|
82
|
+
return [{ fieldPath: path, message: `Must be one of: ${options.join(', ')}` }];
|
|
83
|
+
}
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
case 'reference':
|
|
87
|
+
if (referenceValueId(value) === undefined) {
|
|
88
|
+
return [{ fieldPath: path, message: 'Expected a reference id' }];
|
|
89
|
+
}
|
|
90
|
+
return [];
|
|
91
|
+
default:
|
|
92
|
+
if (STRING_FIELD_TYPES.has(field.type)) {
|
|
93
|
+
if (typeof value !== 'string') {
|
|
94
|
+
return [{ fieldPath: path, message: 'Expected text' }];
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Custom (adopter-defined) field types: semantics unknown, skip type checks.
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/** True when a required field should be considered missing/empty. */
|
|
102
|
+
function isEmptyForRequired(field, value) {
|
|
103
|
+
if (value === undefined || value === null)
|
|
104
|
+
return true;
|
|
105
|
+
if (Array.isArray(value))
|
|
106
|
+
return value.length === 0;
|
|
107
|
+
if (typeof value === 'string') {
|
|
108
|
+
// Strings, selects, references, datetimes: empty string means "not filled in".
|
|
109
|
+
return value.trim() === '';
|
|
110
|
+
}
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Validate entry data against an entry schema. Pure — safe to run in the
|
|
115
|
+
* browser. Returns one error per offending field path; an empty array means
|
|
116
|
+
* the data is structurally valid.
|
|
117
|
+
*/
|
|
118
|
+
export function validateEntryData(fields, data, pathPrefix = '') {
|
|
119
|
+
const errors = [];
|
|
120
|
+
for (const field of fields) {
|
|
121
|
+
// Inline groups are transparent to the data — children live at this level.
|
|
122
|
+
if (field.type === 'group') {
|
|
123
|
+
errors.push(...validateEntryData(field.fields, data, pathPrefix));
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const path = pathPrefix ? `${pathPrefix}.${field.name}` : field.name;
|
|
127
|
+
const value = data[field.name];
|
|
128
|
+
const isRequired = 'required' in field && field.required === true;
|
|
129
|
+
if (isRequired && isEmptyForRequired(field, value)) {
|
|
130
|
+
const message = Array.isArray(value)
|
|
131
|
+
? 'At least one item is required'
|
|
132
|
+
: 'This field is required';
|
|
133
|
+
errors.push({ fieldPath: path, message });
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
// Optional and absent: nothing further to check.
|
|
137
|
+
if (value === undefined || value === null)
|
|
138
|
+
continue;
|
|
139
|
+
if (field.type === 'block') {
|
|
140
|
+
const blockField = field;
|
|
141
|
+
if (!Array.isArray(value)) {
|
|
142
|
+
errors.push({ fieldPath: path, message: 'Expected a list of blocks' });
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
value.forEach((item, index) => {
|
|
146
|
+
const itemPath = `${path}[${index}]`;
|
|
147
|
+
if (!isPlainRecord(item)) {
|
|
148
|
+
errors.push({ fieldPath: itemPath, message: 'Expected a block object' });
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const resolved = resolveBlockItem(blockField, item);
|
|
152
|
+
if (!resolved) {
|
|
153
|
+
const template = typeof item.template === 'string' ? item.template : undefined;
|
|
154
|
+
errors.push({
|
|
155
|
+
fieldPath: itemPath,
|
|
156
|
+
message: template ? `Unknown block template "${template}"` : 'Missing block template',
|
|
157
|
+
});
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
errors.push(...validateEntryData(resolved.fields, resolved.data, itemPath));
|
|
161
|
+
});
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (field.type === 'object') {
|
|
165
|
+
const objectField = field;
|
|
166
|
+
if (objectField.list) {
|
|
167
|
+
if (!Array.isArray(value)) {
|
|
168
|
+
errors.push({ fieldPath: path, message: 'Expected a list of items' });
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
value.forEach((item, index) => {
|
|
172
|
+
const itemPath = `${path}[${index}]`;
|
|
173
|
+
if (!isPlainRecord(item)) {
|
|
174
|
+
errors.push({ fieldPath: itemPath, message: 'Expected an object' });
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (objectField.fields) {
|
|
178
|
+
errors.push(...validateEntryData(objectField.fields, item, itemPath));
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
else if (!isPlainRecord(value)) {
|
|
183
|
+
errors.push({ fieldPath: path, message: 'Expected an object' });
|
|
184
|
+
}
|
|
185
|
+
else if (objectField.fields) {
|
|
186
|
+
errors.push(...validateEntryData(objectField.fields, value, path));
|
|
187
|
+
}
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
// Scalar (and list-of-scalar) fields.
|
|
191
|
+
if ('list' in field && field.list) {
|
|
192
|
+
if (!Array.isArray(value)) {
|
|
193
|
+
errors.push({ fieldPath: path, message: 'Expected a list of values' });
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
value.forEach((item, index) => {
|
|
197
|
+
errors.push(...validateScalar(field, item, `${path}[${index}]`));
|
|
198
|
+
});
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (Array.isArray(value) && KNOWN_FIELD_TYPES.has(field.type)) {
|
|
202
|
+
errors.push({ fieldPath: path, message: 'Expected a single value, not a list' });
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
errors.push(...validateScalar(field, value, path));
|
|
206
|
+
}
|
|
207
|
+
return errors;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Validate an editor FormValue (client-side entry point). For md/mdx formats
|
|
211
|
+
* the editor keeps the body under the literal `body` key; remap it to the
|
|
212
|
+
* schema's body field before validating so required/type checks see it.
|
|
213
|
+
*/
|
|
214
|
+
export function validateEntryFormValue(fields, format, value) {
|
|
215
|
+
if (format && !isDataOnlyFormat(format)) {
|
|
216
|
+
const { body, ...rest } = value;
|
|
217
|
+
return validateEntryData(fields, mergeBodyIntoData(fields, rest, typeof body === 'string' ? body : ''));
|
|
218
|
+
}
|
|
219
|
+
return validateEntryData(fields, value);
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Return a copy of `data` with resolved reference objects (`{ id, ... }`)
|
|
223
|
+
* collapsed back to their id strings, recursing through objects, lists, and
|
|
224
|
+
* block `{ template, value }` items. Used server-side so `ReferenceValidator`
|
|
225
|
+
* (which expects id strings) can check reference existence on editor payloads
|
|
226
|
+
* that may still carry resolved objects from a prior read.
|
|
227
|
+
*/
|
|
228
|
+
export function normalizeReferenceValues(fields, data) {
|
|
229
|
+
const result = { ...data };
|
|
230
|
+
for (const field of fields) {
|
|
231
|
+
if (field.type === 'group') {
|
|
232
|
+
Object.assign(result, normalizeReferenceValues(field.fields, result));
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
const value = result[field.name];
|
|
236
|
+
if (value === undefined || value === null)
|
|
237
|
+
continue;
|
|
238
|
+
if (field.type === 'reference') {
|
|
239
|
+
const refField = field;
|
|
240
|
+
if (refField.list && Array.isArray(value)) {
|
|
241
|
+
result[field.name] = value.map((item) => referenceValueId(item) ?? item);
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
result[field.name] = referenceValueId(value) ?? value;
|
|
245
|
+
}
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
if (field.type === 'object') {
|
|
249
|
+
const objectField = field;
|
|
250
|
+
if (!objectField.fields)
|
|
251
|
+
continue;
|
|
252
|
+
if (objectField.list && Array.isArray(value)) {
|
|
253
|
+
result[field.name] = value.map((item) => isPlainRecord(item) ? normalizeReferenceValues(objectField.fields, item) : item);
|
|
254
|
+
}
|
|
255
|
+
else if (isPlainRecord(value)) {
|
|
256
|
+
result[field.name] = normalizeReferenceValues(objectField.fields, value);
|
|
257
|
+
}
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
if (field.type === 'block' && Array.isArray(value)) {
|
|
261
|
+
const blockField = field;
|
|
262
|
+
result[field.name] = value.map((item) => {
|
|
263
|
+
if (!isPlainRecord(item))
|
|
264
|
+
return item;
|
|
265
|
+
const resolved = resolveBlockItem(blockField, item);
|
|
266
|
+
if (!resolved)
|
|
267
|
+
return item;
|
|
268
|
+
const normalized = normalizeReferenceValues(resolved.fields, resolved.data);
|
|
269
|
+
// Canonical `{ template, value }` shape keeps nested values under `value`;
|
|
270
|
+
// the inline `_type` shape keeps them on the item itself.
|
|
271
|
+
return isPlainRecord(item.value) ? { ...item, value: normalized } : normalized;
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return result;
|
|
276
|
+
}
|
|
277
|
+
//# sourceMappingURL=entry-validator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry-validator.js","sourceRoot":"","sources":["../../src/validation/entry-validator.ts"],"names":[],"mappings":"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"}
|