canopycms 0.0.0 → 0.0.1
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/package.json +2 -3
- package/dist/__integration__/fixtures/content-seeds.d.ts +0 -43
- package/dist/__integration__/fixtures/content-seeds.d.ts.map +0 -1
- package/dist/__integration__/fixtures/content-seeds.js +0 -99
- package/dist/__integration__/fixtures/content-seeds.js.map +0 -1
- package/dist/__integration__/fixtures/schemas.d.ts +0 -12
- package/dist/__integration__/fixtures/schemas.d.ts.map +0 -1
- package/dist/__integration__/fixtures/schemas.js +0 -65
- package/dist/__integration__/fixtures/schemas.js.map +0 -1
- package/dist/__integration__/test-utils/api-client.d.ts +0 -123
- package/dist/__integration__/test-utils/api-client.d.ts.map +0 -1
- package/dist/__integration__/test-utils/api-client.js +0 -118
- package/dist/__integration__/test-utils/api-client.js.map +0 -1
- package/dist/__integration__/test-utils/multi-user.d.ts +0 -25
- package/dist/__integration__/test-utils/multi-user.d.ts.map +0 -1
- package/dist/__integration__/test-utils/multi-user.js +0 -105
- package/dist/__integration__/test-utils/multi-user.js.map +0 -1
- package/dist/__integration__/test-utils/test-workspace.d.ts +0 -25
- package/dist/__integration__/test-utils/test-workspace.d.ts.map +0 -1
- package/dist/__integration__/test-utils/test-workspace.js +0 -102
- package/dist/__integration__/test-utils/test-workspace.js.map +0 -1
- package/dist/editor/BranchManager.stories.d.ts +0 -8
- package/dist/editor/BranchManager.stories.d.ts.map +0 -1
- package/dist/editor/BranchManager.stories.js +0 -74
- package/dist/editor/BranchManager.stories.js.map +0 -1
- package/dist/editor/CanopyEditor.stories.d.ts +0 -7
- package/dist/editor/CanopyEditor.stories.d.ts.map +0 -1
- package/dist/editor/CanopyEditor.stories.js +0 -99
- package/dist/editor/CanopyEditor.stories.js.map +0 -1
- package/dist/editor/CommentsPanel.stories.d.ts +0 -10
- package/dist/editor/CommentsPanel.stories.d.ts.map +0 -1
- package/dist/editor/CommentsPanel.stories.js +0 -175
- package/dist/editor/CommentsPanel.stories.js.map +0 -1
- package/dist/editor/Editor.stories.d.ts +0 -7
- package/dist/editor/Editor.stories.d.ts.map +0 -1
- package/dist/editor/Editor.stories.js +0 -95
- package/dist/editor/Editor.stories.js.map +0 -1
- package/dist/editor/EditorPanes.stories.d.ts +0 -7
- package/dist/editor/EditorPanes.stories.d.ts.map +0 -1
- package/dist/editor/EditorPanes.stories.js +0 -116
- package/dist/editor/EditorPanes.stories.js.map +0 -1
- package/dist/editor/EntryNavigator.stories.d.ts +0 -8
- package/dist/editor/EntryNavigator.stories.d.ts.map +0 -1
- package/dist/editor/EntryNavigator.stories.js +0 -42
- package/dist/editor/EntryNavigator.stories.js.map +0 -1
- package/dist/editor/FormRenderer.stories.d.ts +0 -7
- package/dist/editor/FormRenderer.stories.d.ts.map +0 -1
- package/dist/editor/FormRenderer.stories.js +0 -115
- package/dist/editor/FormRenderer.stories.js.map +0 -1
- package/dist/editor/GroupManager.stories.d.ts +0 -19
- package/dist/editor/GroupManager.stories.d.ts.map +0 -1
- package/dist/editor/GroupManager.stories.js +0 -265
- package/dist/editor/GroupManager.stories.js.map +0 -1
- package/dist/editor/PermissionManager.stories.d.ts +0 -20
- package/dist/editor/PermissionManager.stories.d.ts.map +0 -1
- package/dist/editor/PermissionManager.stories.js +0 -506
- package/dist/editor/PermissionManager.stories.js.map +0 -1
- package/dist/editor/comments/FieldWrapper.stories.d.ts +0 -10
- package/dist/editor/comments/FieldWrapper.stories.d.ts.map +0 -1
- package/dist/editor/comments/FieldWrapper.stories.js +0 -173
- package/dist/editor/comments/FieldWrapper.stories.js.map +0 -1
- package/dist/editor/fields/BlockField.stories.d.ts +0 -7
- package/dist/editor/fields/BlockField.stories.d.ts.map +0 -1
- package/dist/editor/fields/BlockField.stories.js +0 -50
- package/dist/editor/fields/BlockField.stories.js.map +0 -1
- package/dist/editor/fields/fields.stories.d.ts +0 -8
- package/dist/editor/fields/fields.stories.d.ts.map +0 -1
- package/dist/editor/fields/fields.stories.js +0 -34
- package/dist/editor/fields/fields.stories.js.map +0 -1
- package/dist/test-utils/api-test-helpers.d.ts +0 -238
- package/dist/test-utils/api-test-helpers.d.ts.map +0 -1
- package/dist/test-utils/api-test-helpers.js +0 -347
- package/dist/test-utils/api-test-helpers.js.map +0 -1
- package/dist/test-utils/console-spy.d.ts +0 -56
- package/dist/test-utils/console-spy.d.ts.map +0 -1
- package/dist/test-utils/console-spy.js +0 -81
- package/dist/test-utils/console-spy.js.map +0 -1
- package/dist/test-utils/git-helpers.d.ts +0 -21
- package/dist/test-utils/git-helpers.d.ts.map +0 -1
- package/dist/test-utils/git-helpers.js +0 -23
- package/dist/test-utils/git-helpers.js.map +0 -1
- package/dist/test-utils/index.d.ts +0 -5
- package/dist/test-utils/index.d.ts.map +0 -1
- package/dist/test-utils/index.js +0 -4
- package/dist/test-utils/index.js.map +0 -1
- package/src/__integration__/errors/invalid-content.test.ts +0 -238
- package/src/__integration__/errors/permission-denied.test.ts +0 -220
- package/src/__integration__/fixtures/content-seeds.ts +0 -105
- package/src/__integration__/fixtures/schemas.ts +0 -67
- package/src/__integration__/initialization/prod-sim-init.test.ts +0 -139
- package/src/__integration__/permissions/path-permissions.test.ts +0 -314
- package/src/__integration__/permissions/role-permissions.test.ts +0 -354
- package/src/__integration__/permissions/settings-branch-isolation.test.ts +0 -317
- package/src/__integration__/settings/groups-api.test.ts +0 -403
- package/src/__integration__/test-utils/api-client.ts +0 -167
- package/src/__integration__/test-utils/multi-user.ts +0 -129
- package/src/__integration__/test-utils/test-workspace.ts +0 -130
- package/src/__integration__/user/user-context.test.ts +0 -174
- package/src/__integration__/validation/input-validation.test.ts +0 -166
- package/src/__integration__/workflows/api-editing-workflow.test.ts +0 -244
- package/src/__integration__/workflows/conflict-resolution.test.ts +0 -259
- package/src/__integration__/workflows/editing-workflow.test.ts +0 -205
- package/src/__integration__/workflows/review-workflow.test.ts +0 -260
- package/src/ai/__tests__/build.integration.test.ts +0 -224
- package/src/ai/__tests__/generate.integration.test.ts +0 -495
- package/src/ai/__tests__/handler.integration.test.ts +0 -212
- package/src/ai/__tests__/json-to-markdown.test.ts +0 -553
- package/src/ai/generate.ts +0 -410
- package/src/ai/handler.ts +0 -123
- package/src/ai/index.ts +0 -26
- package/src/ai/json-to-markdown.ts +0 -424
- package/src/ai/resolve-branch.ts +0 -34
- package/src/ai/types.ts +0 -160
- package/src/api/AGENTS.md +0 -81
- package/src/api/__test__/mock-client.ts +0 -404
- package/src/api/assets.test.ts +0 -140
- package/src/api/assets.ts +0 -154
- package/src/api/branch-merge.test.ts +0 -163
- package/src/api/branch-merge.ts +0 -113
- package/src/api/branch-review.test.ts +0 -297
- package/src/api/branch-review.ts +0 -136
- package/src/api/branch-status.test.ts +0 -85
- package/src/api/branch-status.ts +0 -153
- package/src/api/branch-withdraw.test.ts +0 -146
- package/src/api/branch-withdraw.ts +0 -81
- package/src/api/branch-workflow.integration.test.ts +0 -578
- package/src/api/branch.test.ts +0 -620
- package/src/api/branch.ts +0 -492
- package/src/api/client.test.ts +0 -349
- package/src/api/client.ts +0 -506
- package/src/api/comments.test.ts +0 -285
- package/src/api/comments.ts +0 -210
- package/src/api/content.test.ts +0 -345
- package/src/api/content.ts +0 -454
- package/src/api/entries.test.ts +0 -1339
- package/src/api/entries.ts +0 -650
- package/src/api/github-sync.ts +0 -144
- package/src/api/groups.test.ts +0 -1013
- package/src/api/groups.ts +0 -375
- package/src/api/guards.test.ts +0 -533
- package/src/api/guards.ts +0 -271
- package/src/api/index.ts +0 -87
- package/src/api/permissions.test.ts +0 -766
- package/src/api/permissions.ts +0 -334
- package/src/api/reference-options.ts +0 -118
- package/src/api/resolve-references.ts +0 -107
- package/src/api/route-builder.ts +0 -289
- package/src/api/schema.test.ts +0 -840
- package/src/api/schema.ts +0 -936
- package/src/api/security.test.ts +0 -233
- package/src/api/settings-helpers.ts +0 -84
- package/src/api/types.ts +0 -40
- package/src/api/user.test.ts +0 -127
- package/src/api/user.ts +0 -42
- package/src/api/validators.test.ts +0 -275
- package/src/api/validators.ts +0 -176
- package/src/asset-store.test.ts +0 -37
- package/src/asset-store.ts +0 -110
- package/src/auth/cache.ts +0 -7
- package/src/auth/caching-auth-plugin.test.ts +0 -154
- package/src/auth/caching-auth-plugin.ts +0 -109
- package/src/auth/context-helpers.ts +0 -75
- package/src/auth/file-based-auth-cache.test.ts +0 -257
- package/src/auth/file-based-auth-cache.ts +0 -279
- package/src/auth/index.ts +0 -12
- package/src/auth/plugin.ts +0 -51
- package/src/auth/types.ts +0 -38
- package/src/authorization/__tests__/branch.test.ts +0 -260
- package/src/authorization/__tests__/content.test.ts +0 -142
- package/src/authorization/__tests__/path.test.ts +0 -133
- package/src/authorization/__tests__/permissions-loader.test.ts +0 -200
- package/src/authorization/branch.ts +0 -94
- package/src/authorization/content.ts +0 -93
- package/src/authorization/groups/index.ts +0 -11
- package/src/authorization/groups/loader.ts +0 -127
- package/src/authorization/groups/schema.ts +0 -48
- package/src/authorization/helpers.ts +0 -48
- package/src/authorization/index.ts +0 -84
- package/src/authorization/path.ts +0 -112
- package/src/authorization/permissions/index.ts +0 -11
- package/src/authorization/permissions/loader.ts +0 -116
- package/src/authorization/permissions/schema.ts +0 -66
- package/src/authorization/test-utils.ts +0 -15
- package/src/authorization/types.ts +0 -66
- package/src/authorization/validation.test.ts +0 -100
- package/src/authorization/validation.ts +0 -62
- package/src/branch-metadata.test.ts +0 -168
- package/src/branch-metadata.ts +0 -166
- package/src/branch-registry.test.ts +0 -248
- package/src/branch-registry.ts +0 -152
- package/src/branch-schema-cache.test.ts +0 -275
- package/src/branch-schema-cache.ts +0 -189
- package/src/branch-workspace.test.ts +0 -183
- package/src/branch-workspace.ts +0 -124
- package/src/build/generate-ai-content.ts +0 -78
- package/src/build/index.ts +0 -8
- package/src/build-mode.ts +0 -27
- package/src/cli/generate-ai-content.ts +0 -100
- package/src/cli/init.test.ts +0 -240
- package/src/cli/templates/Dockerfile.cms.template +0 -19
- package/src/cli/templates/canopy.ts.template +0 -55
- package/src/cli/templates/canopycms.config.ts.template +0 -11
- package/src/cli/templates/deploy-cms.yml.template +0 -27
- package/src/cli/templates/edit-page.tsx.template +0 -32
- package/src/cli/templates/route.ts.template +0 -12
- package/src/cli/templates/schemas.ts.template +0 -16
- package/src/cli/templates.ts +0 -47
- package/src/client.ts +0 -12
- package/src/comment-store.test.ts +0 -442
- package/src/comment-store.ts +0 -301
- package/src/config/__tests__/config.test.ts +0 -513
- package/src/config/flatten.ts +0 -174
- package/src/config/helpers.ts +0 -167
- package/src/config/index.ts +0 -86
- package/src/config/schemas/collection.ts +0 -67
- package/src/config/schemas/config.ts +0 -77
- package/src/config/schemas/field.ts +0 -108
- package/src/config/schemas/media.ts +0 -27
- package/src/config/schemas/permissions.ts +0 -21
- package/src/config/types.ts +0 -321
- package/src/config/validation.ts +0 -70
- package/src/config-test.ts +0 -65
- package/src/config.ts +0 -11
- package/src/content-id-index.test.ts +0 -512
- package/src/content-id-index.ts +0 -479
- package/src/content-reader.test.ts +0 -478
- package/src/content-reader.ts +0 -214
- package/src/content-store.test.ts +0 -1126
- package/src/content-store.ts +0 -793
- package/src/context.ts +0 -111
- package/src/editor/BranchManager.stories.tsx +0 -80
- package/src/editor/BranchManager.test.tsx +0 -324
- package/src/editor/BranchManager.tsx +0 -461
- package/src/editor/CanopyEditor.stories.tsx +0 -128
- package/src/editor/CanopyEditor.test.tsx +0 -81
- package/src/editor/CanopyEditor.tsx +0 -73
- package/src/editor/CanopyEditorPage.test.tsx +0 -59
- package/src/editor/CanopyEditorPage.tsx +0 -25
- package/src/editor/CommentsPanel.stories.tsx +0 -184
- package/src/editor/CommentsPanel.tsx +0 -338
- package/src/editor/Editor.integration.test.tsx +0 -227
- package/src/editor/Editor.stories.tsx +0 -119
- package/src/editor/Editor.tsx +0 -1221
- package/src/editor/EditorPanes.stories.tsx +0 -256
- package/src/editor/EditorPanes.test.tsx +0 -77
- package/src/editor/EditorPanes.tsx +0 -180
- package/src/editor/EntryNavigator.stories.tsx +0 -65
- package/src/editor/EntryNavigator.test.tsx +0 -598
- package/src/editor/EntryNavigator.tsx +0 -665
- package/src/editor/FormRenderer.stories.tsx +0 -212
- package/src/editor/FormRenderer.test.tsx +0 -194
- package/src/editor/FormRenderer.tsx +0 -432
- package/src/editor/GroupManager.stories.tsx +0 -301
- package/src/editor/GroupManager.test.tsx +0 -682
- package/src/editor/GroupManager.tsx +0 -9
- package/src/editor/PermissionManager.stories.tsx +0 -539
- package/src/editor/PermissionManager.test.tsx +0 -864
- package/src/editor/PermissionManager.tsx +0 -12
- package/src/editor/canopy-path.test.ts +0 -23
- package/src/editor/canopy-path.ts +0 -52
- package/src/editor/client-reference-resolver.ts +0 -118
- package/src/editor/comments/BranchComments.tsx +0 -93
- package/src/editor/comments/EntryComments.tsx +0 -94
- package/src/editor/comments/FieldWrapper.stories.tsx +0 -210
- package/src/editor/comments/FieldWrapper.tsx +0 -129
- package/src/editor/comments/InlineCommentThread.test.tsx +0 -384
- package/src/editor/comments/InlineCommentThread.tsx +0 -246
- package/src/editor/comments/ThreadCarousel.test.tsx +0 -393
- package/src/editor/comments/ThreadCarousel.tsx +0 -525
- package/src/editor/components/ConfirmDeleteModal.tsx +0 -49
- package/src/editor/components/EditorContext.tsx +0 -49
- package/src/editor/components/EditorFooter.tsx +0 -47
- package/src/editor/components/EditorHeader.tsx +0 -492
- package/src/editor/components/EditorSidebar.tsx +0 -193
- package/src/editor/components/EntryCreateModal.tsx +0 -193
- package/src/editor/components/RenameEntryModal.tsx +0 -152
- package/src/editor/components/UserBadge.test.tsx +0 -274
- package/src/editor/components/UserBadge.tsx +0 -240
- package/src/editor/components/index.ts +0 -6
- package/src/editor/context/ApiClientContext.tsx +0 -56
- package/src/editor/context/EditorStateContext.tsx +0 -221
- package/src/editor/context/index.ts +0 -40
- package/src/editor/editor-config.test.ts +0 -385
- package/src/editor/editor-config.ts +0 -94
- package/src/editor/editor-utils.test.ts +0 -772
- package/src/editor/editor-utils.ts +0 -303
- package/src/editor/env.ts +0 -4
- package/src/editor/fields/BlockField.stories.tsx +0 -79
- package/src/editor/fields/BlockField.tsx +0 -267
- package/src/editor/fields/CodeField.tsx +0 -41
- package/src/editor/fields/MarkdownField.tsx +0 -205
- package/src/editor/fields/ObjectField.tsx +0 -71
- package/src/editor/fields/ReferenceField.tsx +0 -138
- package/src/editor/fields/SelectField.tsx +0 -76
- package/src/editor/fields/TextField.tsx +0 -35
- package/src/editor/fields/ToggleField.tsx +0 -37
- package/src/editor/fields/fields.stories.tsx +0 -40
- package/src/editor/group-manager/ExternalGroupsTab.tsx +0 -114
- package/src/editor/group-manager/GroupCard.tsx +0 -102
- package/src/editor/group-manager/GroupForm.tsx +0 -66
- package/src/editor/group-manager/InternalGroupsTab.tsx +0 -147
- package/src/editor/group-manager/MemberList.tsx +0 -184
- package/src/editor/group-manager/hooks/useExternalGroupSearch.ts +0 -63
- package/src/editor/group-manager/hooks/useGroupState.ts +0 -134
- package/src/editor/group-manager/hooks/useUserSearch.ts +0 -84
- package/src/editor/group-manager/index.tsx +0 -210
- package/src/editor/group-manager/types.ts +0 -28
- package/src/editor/hooks/README.md +0 -26
- package/src/editor/hooks/__test__/test-utils.tsx +0 -183
- package/src/editor/hooks/index.ts +0 -23
- package/src/editor/hooks/useBranchActions.test.tsx +0 -267
- package/src/editor/hooks/useBranchActions.tsx +0 -121
- package/src/editor/hooks/useBranchManager.test.tsx +0 -391
- package/src/editor/hooks/useBranchManager.tsx +0 -326
- package/src/editor/hooks/useCommentSystem.test.ts +0 -615
- package/src/editor/hooks/useCommentSystem.ts +0 -347
- package/src/editor/hooks/useDraftManager.test.ts +0 -375
- package/src/editor/hooks/useDraftManager.ts +0 -259
- package/src/editor/hooks/useEditorLayout.test.ts +0 -147
- package/src/editor/hooks/useEditorLayout.ts +0 -67
- package/src/editor/hooks/useEntryManager.test.ts +0 -588
- package/src/editor/hooks/useEntryManager.ts +0 -387
- package/src/editor/hooks/useGroupManager.test.ts +0 -277
- package/src/editor/hooks/useGroupManager.ts +0 -139
- package/src/editor/hooks/usePermissionManager.test.ts +0 -211
- package/src/editor/hooks/usePermissionManager.ts +0 -113
- package/src/editor/hooks/useReferenceResolution.ts +0 -248
- package/src/editor/hooks/useSchemaManager.test.ts +0 -370
- package/src/editor/hooks/useSchemaManager.ts +0 -310
- package/src/editor/hooks/useUserContext.tsx +0 -57
- package/src/editor/hooks/useUserMetadata.test.ts +0 -191
- package/src/editor/hooks/useUserMetadata.ts +0 -71
- package/src/editor/permission-manager/GroupSelector.tsx +0 -73
- package/src/editor/permission-manager/PermissionEditor.tsx +0 -321
- package/src/editor/permission-manager/PermissionLevelBadge.tsx +0 -53
- package/src/editor/permission-manager/PermissionTree.tsx +0 -237
- package/src/editor/permission-manager/UserSelector.tsx +0 -95
- package/src/editor/permission-manager/constants.tsx +0 -18
- package/src/editor/permission-manager/hooks/useGroupsAndUsers.ts +0 -153
- package/src/editor/permission-manager/hooks/usePermissionTree.ts +0 -200
- package/src/editor/permission-manager/index.tsx +0 -294
- package/src/editor/permission-manager/types.ts +0 -58
- package/src/editor/permission-manager/utils.ts +0 -179
- package/src/editor/preview-bridge.test.tsx +0 -50
- package/src/editor/preview-bridge.tsx +0 -294
- package/src/editor/schema-editor/CollectionEditor.test.tsx +0 -238
- package/src/editor/schema-editor/CollectionEditor.tsx +0 -520
- package/src/editor/schema-editor/EntryTypeEditor.test.tsx +0 -215
- package/src/editor/schema-editor/EntryTypeEditor.tsx +0 -367
- package/src/editor/schema-editor/index.ts +0 -19
- package/src/editor/setup-test-dom.ts +0 -10
- package/src/editor/test-setup.ts +0 -33
- package/src/editor/theme.tsx +0 -119
- package/src/editor/utils/env.ts +0 -39
- package/src/entry-schema-registry.test.ts +0 -281
- package/src/entry-schema-registry.ts +0 -121
- package/src/entry-schema.ts +0 -84
- package/src/git-manager.test.ts +0 -552
- package/src/git-manager.ts +0 -667
- package/src/github-service.test.ts +0 -312
- package/src/github-service.ts +0 -295
- package/src/http/handler.test.ts +0 -275
- package/src/http/handler.ts +0 -280
- package/src/http/index.ts +0 -11
- package/src/http/router.ts +0 -164
- package/src/http/types.ts +0 -44
- package/src/id.test.ts +0 -48
- package/src/id.ts +0 -22
- package/src/index.ts +0 -26
- package/src/operating-mode/__tests__/strategies.test.ts +0 -511
- package/src/operating-mode/client-safe-strategy.ts +0 -184
- package/src/operating-mode/client-unsafe-strategy.ts +0 -303
- package/src/operating-mode/client.ts +0 -13
- package/src/operating-mode/index.ts +0 -34
- package/src/operating-mode/types.ts +0 -186
- package/src/paths/__tests__/branch.test.ts +0 -53
- package/src/paths/__tests__/normalize.test.ts +0 -141
- package/src/paths/__tests__/resolve.test.ts +0 -207
- package/src/paths/__tests__/validation.test.ts +0 -61
- package/src/paths/branch.ts +0 -115
- package/src/paths/index.ts +0 -73
- package/src/paths/normalize-server.ts +0 -40
- package/src/paths/normalize.ts +0 -107
- package/src/paths/resolve.ts +0 -61
- package/src/paths/test-utils.ts +0 -37
- package/src/paths/types.ts +0 -68
- package/src/paths/validation.test.ts +0 -480
- package/src/paths/validation.ts +0 -391
- package/src/reference-resolver.test.ts +0 -107
- package/src/reference-resolver.ts +0 -157
- package/src/schema/index.ts +0 -29
- package/src/schema/meta-loader.ts +0 -366
- package/src/schema/resolver.ts +0 -83
- package/src/schema/schema-store-types.ts +0 -56
- package/src/schema/schema-store.test.ts +0 -816
- package/src/schema/schema-store.ts +0 -795
- package/src/schema/types.ts +0 -33
- package/src/schema-meta-loader.test.ts +0 -447
- package/src/server.ts +0 -15
- package/src/services.test.ts +0 -559
- package/src/services.ts +0 -373
- package/src/settings-branch-utils.ts +0 -53
- package/src/settings-workspace.ts +0 -156
- package/src/task-queue/README.md +0 -144
- package/src/task-queue/index.ts +0 -14
- package/src/task-queue/task-queue.test.ts +0 -524
- package/src/task-queue/task-queue.ts +0 -514
- package/src/task-queue/types.ts +0 -41
- package/src/test-utils/api-test-helpers.ts +0 -445
- package/src/test-utils/console-spy.test.ts +0 -14
- package/src/test-utils/console-spy.ts +0 -125
- package/src/test-utils/git-helpers.ts +0 -31
- package/src/test-utils/index.ts +0 -4
- package/src/types.ts +0 -54
- package/src/user.ts +0 -118
- package/src/utils/debug.test.ts +0 -114
- package/src/utils/debug.ts +0 -127
- package/src/utils/error.test.ts +0 -92
- package/src/utils/error.ts +0 -83
- package/src/utils/format.ts +0 -12
- package/src/validation/__tests__/field-traversal.test.ts +0 -263
- package/src/validation/deletion-checker.ts +0 -234
- package/src/validation/field-traversal.ts +0 -146
- package/src/validation/reference-validator.ts +0 -168
- package/src/worker/cms-worker-rebase.test.ts +0 -473
- package/src/worker/cms-worker.ts +0 -777
- package/src/worker/integration.test.ts +0 -289
- package/src/worker/task-queue-config.ts +0 -25
- package/src/worker/task-queue.test.ts +0 -452
- package/src/worker/task-queue.ts +0 -58
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs/promises'
|
|
2
|
-
import os from 'node:os'
|
|
3
|
-
import path from 'node:path'
|
|
4
|
-
|
|
5
|
-
import { describe, expect, it } from 'vitest'
|
|
6
|
-
|
|
7
|
-
import { BranchRegistry } from './branch-registry'
|
|
8
|
-
import { getBranchMetadataFileManager } from './branch-metadata'
|
|
9
|
-
|
|
10
|
-
const tmpDir = async () => fs.mkdtemp(path.join(os.tmpdir(), 'canopycms-registry-'))
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Create a branch directory with a valid branch.json metadata file
|
|
14
|
-
*/
|
|
15
|
-
const createBranchWithMetadata = async (
|
|
16
|
-
root: string,
|
|
17
|
-
branchName: string,
|
|
18
|
-
status: 'editing' | 'submitted' = 'editing',
|
|
19
|
-
) => {
|
|
20
|
-
const branchDir = path.join(root, branchName)
|
|
21
|
-
const metaDir = path.join(branchDir, '.canopy-meta')
|
|
22
|
-
await fs.mkdir(metaDir, { recursive: true })
|
|
23
|
-
|
|
24
|
-
const metadata = getBranchMetadataFileManager(branchDir, root)
|
|
25
|
-
await metadata.save({
|
|
26
|
-
branch: {
|
|
27
|
-
name: branchName,
|
|
28
|
-
status,
|
|
29
|
-
createdBy: 'user-1',
|
|
30
|
-
},
|
|
31
|
-
})
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
describe('BranchRegistry', () => {
|
|
35
|
-
describe('list()', () => {
|
|
36
|
-
it('returns empty array when no branches exist', async () => {
|
|
37
|
-
const root = await tmpDir()
|
|
38
|
-
const registry = new BranchRegistry(root)
|
|
39
|
-
|
|
40
|
-
const branches = await registry.list()
|
|
41
|
-
expect(branches).toEqual([])
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
it('scans branch directories and returns branches', async () => {
|
|
45
|
-
const root = await tmpDir()
|
|
46
|
-
await createBranchWithMetadata(root, 'feature-a')
|
|
47
|
-
await createBranchWithMetadata(root, 'feature-b')
|
|
48
|
-
|
|
49
|
-
const registry = new BranchRegistry(root)
|
|
50
|
-
const branches = await registry.list()
|
|
51
|
-
|
|
52
|
-
expect(branches).toHaveLength(2)
|
|
53
|
-
expect(branches.map((b) => b.branch.name).sort()).toEqual(['feature-a', 'feature-b'])
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
it('skips directories without branch.json', async () => {
|
|
57
|
-
const root = await tmpDir()
|
|
58
|
-
await createBranchWithMetadata(root, 'feature-a')
|
|
59
|
-
// Create a directory without metadata
|
|
60
|
-
await fs.mkdir(path.join(root, 'empty-dir'), { recursive: true })
|
|
61
|
-
|
|
62
|
-
const registry = new BranchRegistry(root)
|
|
63
|
-
const branches = await registry.list()
|
|
64
|
-
|
|
65
|
-
expect(branches).toHaveLength(1)
|
|
66
|
-
expect(branches[0].branch.name).toBe('feature-a')
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
it('skips hidden directories like .canopycms', async () => {
|
|
70
|
-
const root = await tmpDir()
|
|
71
|
-
await createBranchWithMetadata(root, 'feature-a')
|
|
72
|
-
// Create .canopycms directory at root (registry storage)
|
|
73
|
-
await fs.mkdir(path.join(root, '.canopy-meta'), { recursive: true })
|
|
74
|
-
|
|
75
|
-
const registry = new BranchRegistry(root)
|
|
76
|
-
const branches = await registry.list()
|
|
77
|
-
|
|
78
|
-
expect(branches).toHaveLength(1)
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
it('creates cache file after first list()', async () => {
|
|
82
|
-
const root = await tmpDir()
|
|
83
|
-
await createBranchWithMetadata(root, 'feature-a')
|
|
84
|
-
|
|
85
|
-
const registry = new BranchRegistry(root)
|
|
86
|
-
await registry.list()
|
|
87
|
-
|
|
88
|
-
// Cache file should exist
|
|
89
|
-
const cacheFile = path.join(root, 'branches.json')
|
|
90
|
-
const exists = await fs
|
|
91
|
-
.stat(cacheFile)
|
|
92
|
-
.then(() => true)
|
|
93
|
-
.catch(() => false)
|
|
94
|
-
expect(exists).toBe(true)
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
it('uses cached result on subsequent calls', async () => {
|
|
98
|
-
const root = await tmpDir()
|
|
99
|
-
await createBranchWithMetadata(root, 'feature-a')
|
|
100
|
-
|
|
101
|
-
const registry = new BranchRegistry(root)
|
|
102
|
-
|
|
103
|
-
// First call regenerates
|
|
104
|
-
const first = await registry.list()
|
|
105
|
-
expect(first).toHaveLength(1)
|
|
106
|
-
|
|
107
|
-
// Add another branch directly by writing file (bypassing invalidation)
|
|
108
|
-
const branchDir = path.join(root, 'feature-b')
|
|
109
|
-
const metaDir = path.join(branchDir, '.canopy-meta')
|
|
110
|
-
await fs.mkdir(metaDir, { recursive: true })
|
|
111
|
-
await fs.writeFile(
|
|
112
|
-
path.join(metaDir, 'branch.json'),
|
|
113
|
-
JSON.stringify({
|
|
114
|
-
schemaVersion: 1,
|
|
115
|
-
branch: {
|
|
116
|
-
name: 'feature-b',
|
|
117
|
-
status: 'editing',
|
|
118
|
-
access: {},
|
|
119
|
-
createdBy: 'user-1',
|
|
120
|
-
createdAt: new Date().toISOString(),
|
|
121
|
-
updatedAt: new Date().toISOString(),
|
|
122
|
-
},
|
|
123
|
-
}),
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
// Second call should return cached result (still 1 branch)
|
|
127
|
-
const second = await registry.list()
|
|
128
|
-
expect(second).toHaveLength(1)
|
|
129
|
-
})
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
describe('get()', () => {
|
|
133
|
-
it('returns branch by name', async () => {
|
|
134
|
-
const root = await tmpDir()
|
|
135
|
-
await createBranchWithMetadata(root, 'feature-a')
|
|
136
|
-
|
|
137
|
-
const registry = new BranchRegistry(root)
|
|
138
|
-
const branch = await registry.get('feature-a')
|
|
139
|
-
|
|
140
|
-
expect(branch?.branch.name).toBe('feature-a')
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
it('returns undefined for non-existent branch', async () => {
|
|
144
|
-
const root = await tmpDir()
|
|
145
|
-
await createBranchWithMetadata(root, 'feature-a')
|
|
146
|
-
|
|
147
|
-
const registry = new BranchRegistry(root)
|
|
148
|
-
const branch = await registry.get('does-not-exist')
|
|
149
|
-
|
|
150
|
-
expect(branch).toBeUndefined()
|
|
151
|
-
})
|
|
152
|
-
})
|
|
153
|
-
|
|
154
|
-
describe('invalidate()', () => {
|
|
155
|
-
it('causes next list() to regenerate cache', async () => {
|
|
156
|
-
const root = await tmpDir()
|
|
157
|
-
await createBranchWithMetadata(root, 'feature-a')
|
|
158
|
-
|
|
159
|
-
const registry = new BranchRegistry(root)
|
|
160
|
-
|
|
161
|
-
// First call populates cache
|
|
162
|
-
const first = await registry.list()
|
|
163
|
-
expect(first).toHaveLength(1)
|
|
164
|
-
|
|
165
|
-
// Add another branch
|
|
166
|
-
await createBranchWithMetadata(root, 'feature-b')
|
|
167
|
-
|
|
168
|
-
// Without invalidation, cache would still show 1
|
|
169
|
-
// But with invalidation, it should regenerate
|
|
170
|
-
await registry.invalidate()
|
|
171
|
-
|
|
172
|
-
const after = await registry.list()
|
|
173
|
-
expect(after).toHaveLength(2)
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
it('is safe to call when no cache exists', async () => {
|
|
177
|
-
const root = await tmpDir()
|
|
178
|
-
const registry = new BranchRegistry(root)
|
|
179
|
-
|
|
180
|
-
// Should not throw
|
|
181
|
-
await registry.invalidate()
|
|
182
|
-
})
|
|
183
|
-
|
|
184
|
-
it('renames cache file to stale file', async () => {
|
|
185
|
-
const root = await tmpDir()
|
|
186
|
-
await createBranchWithMetadata(root, 'feature-a')
|
|
187
|
-
|
|
188
|
-
const registry = new BranchRegistry(root)
|
|
189
|
-
await registry.list() // Create cache
|
|
190
|
-
|
|
191
|
-
const cacheFile = path.join(root, 'branches.json')
|
|
192
|
-
const staleFile = path.join(root, 'branches.stale.json')
|
|
193
|
-
|
|
194
|
-
// Cache exists, stale doesn't
|
|
195
|
-
expect(
|
|
196
|
-
await fs
|
|
197
|
-
.stat(cacheFile)
|
|
198
|
-
.then(() => true)
|
|
199
|
-
.catch(() => false),
|
|
200
|
-
).toBe(true)
|
|
201
|
-
expect(
|
|
202
|
-
await fs
|
|
203
|
-
.stat(staleFile)
|
|
204
|
-
.then(() => true)
|
|
205
|
-
.catch(() => false),
|
|
206
|
-
).toBe(false)
|
|
207
|
-
|
|
208
|
-
await registry.invalidate()
|
|
209
|
-
|
|
210
|
-
// Now stale exists, cache doesn't
|
|
211
|
-
expect(
|
|
212
|
-
await fs
|
|
213
|
-
.stat(cacheFile)
|
|
214
|
-
.then(() => true)
|
|
215
|
-
.catch(() => false),
|
|
216
|
-
).toBe(false)
|
|
217
|
-
expect(
|
|
218
|
-
await fs
|
|
219
|
-
.stat(staleFile)
|
|
220
|
-
.then(() => true)
|
|
221
|
-
.catch(() => false),
|
|
222
|
-
).toBe(true)
|
|
223
|
-
})
|
|
224
|
-
})
|
|
225
|
-
|
|
226
|
-
describe('cache integrity', () => {
|
|
227
|
-
it('includes workspace paths in branch state', async () => {
|
|
228
|
-
const root = await tmpDir()
|
|
229
|
-
await createBranchWithMetadata(root, 'feature-a')
|
|
230
|
-
|
|
231
|
-
const registry = new BranchRegistry(root)
|
|
232
|
-
const branches = await registry.list()
|
|
233
|
-
|
|
234
|
-
expect(branches[0].branchRoot).toBe(path.join(root, 'feature-a'))
|
|
235
|
-
expect(branches[0].baseRoot).toBe(root)
|
|
236
|
-
})
|
|
237
|
-
|
|
238
|
-
it('reflects status from branch.json', async () => {
|
|
239
|
-
const root = await tmpDir()
|
|
240
|
-
await createBranchWithMetadata(root, 'feature-a', 'submitted')
|
|
241
|
-
|
|
242
|
-
const registry = new BranchRegistry(root)
|
|
243
|
-
const branches = await registry.list()
|
|
244
|
-
|
|
245
|
-
expect(branches[0].branch.status).toBe('submitted')
|
|
246
|
-
})
|
|
247
|
-
})
|
|
248
|
-
})
|
package/src/branch-registry.ts
DELETED
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs/promises'
|
|
2
|
-
import path from 'node:path'
|
|
3
|
-
|
|
4
|
-
import type { BranchContext } from './types'
|
|
5
|
-
import { BranchMetadataFileManager } from './branch-metadata'
|
|
6
|
-
import { isNotFoundError } from './utils/error'
|
|
7
|
-
|
|
8
|
-
// Registry files are stored directly in the branches root (not in a subdirectory)
|
|
9
|
-
const REGISTRY_FILE = 'branches.json'
|
|
10
|
-
const REGISTRY_STALE_FILE = 'branches.stale.json'
|
|
11
|
-
const REGISTRY_TEMP_FILE = 'branches.tmp.json'
|
|
12
|
-
const REGISTRY_VERSION = 1
|
|
13
|
-
|
|
14
|
-
export interface BranchRegistrySnapshot {
|
|
15
|
-
version: number
|
|
16
|
-
branches: BranchContext[]
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* BranchRegistry is a read-only cache for fast branch listing.
|
|
21
|
-
* Individual branch.json files are the source of truth.
|
|
22
|
-
*
|
|
23
|
-
* Design:
|
|
24
|
-
* - list() returns cached data if fresh, regenerates from branch.json files if stale
|
|
25
|
-
* - invalidate() marks cache as stale (called when branch state changes)
|
|
26
|
-
* - Concurrent regeneration is safe (all processes produce identical output)
|
|
27
|
-
*/
|
|
28
|
-
export class BranchRegistry {
|
|
29
|
-
private readonly root: string
|
|
30
|
-
private readonly registryPath: string
|
|
31
|
-
private readonly stalePath: string
|
|
32
|
-
private readonly tempPath: string
|
|
33
|
-
|
|
34
|
-
constructor(root: string) {
|
|
35
|
-
this.root = path.resolve(root)
|
|
36
|
-
this.registryPath = path.join(this.root, REGISTRY_FILE)
|
|
37
|
-
this.stalePath = path.join(this.root, REGISTRY_STALE_FILE)
|
|
38
|
-
this.tempPath = path.join(this.root, REGISTRY_TEMP_FILE)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Returns all branches. Uses cache if fresh, regenerates if stale.
|
|
43
|
-
*/
|
|
44
|
-
async list(): Promise<BranchContext[]> {
|
|
45
|
-
try {
|
|
46
|
-
const raw = await fs.readFile(this.registryPath, 'utf8')
|
|
47
|
-
const parsed = JSON.parse(raw) as BranchRegistrySnapshot
|
|
48
|
-
if (!parsed.version || !Array.isArray(parsed.branches)) {
|
|
49
|
-
// Invalid cache, regenerate
|
|
50
|
-
return await this.regenerate()
|
|
51
|
-
}
|
|
52
|
-
return parsed.branches
|
|
53
|
-
} catch (err: unknown) {
|
|
54
|
-
if (isNotFoundError(err)) {
|
|
55
|
-
// Cache missing or stale, regenerate
|
|
56
|
-
return await this.regenerate()
|
|
57
|
-
}
|
|
58
|
-
throw err
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Returns a single branch by name. Uses cache if available.
|
|
64
|
-
*/
|
|
65
|
-
async get(name: string): Promise<BranchContext | undefined> {
|
|
66
|
-
const branches = await this.list()
|
|
67
|
-
return branches.find((b) => b.branch.name === name)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Marks the cache as stale. Next list() call will regenerate.
|
|
72
|
-
* Uses atomic rename for safety.
|
|
73
|
-
*/
|
|
74
|
-
async invalidate(): Promise<void> {
|
|
75
|
-
try {
|
|
76
|
-
await fs.rename(this.registryPath, this.stalePath)
|
|
77
|
-
} catch (err: unknown) {
|
|
78
|
-
// ENOENT means already stale or never existed, which is fine
|
|
79
|
-
if (!isNotFoundError(err)) {
|
|
80
|
-
throw err
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Scans branch directories and rebuilds the cache.
|
|
87
|
-
* Concurrent calls are safe - all produce identical content.
|
|
88
|
-
*/
|
|
89
|
-
private async regenerate(): Promise<BranchContext[]> {
|
|
90
|
-
const branches = await this.scanBranchDirectories()
|
|
91
|
-
|
|
92
|
-
// Write to unique temp file first, then atomic rename
|
|
93
|
-
// Use random suffix to avoid conflicts between concurrent regenerations
|
|
94
|
-
const uniqueTempPath = `${this.tempPath}.${Date.now()}.${Math.random().toString(36).slice(2)}`
|
|
95
|
-
await fs.mkdir(this.root, { recursive: true })
|
|
96
|
-
const snapshot: BranchRegistrySnapshot = {
|
|
97
|
-
version: REGISTRY_VERSION,
|
|
98
|
-
branches,
|
|
99
|
-
}
|
|
100
|
-
await fs.writeFile(uniqueTempPath, JSON.stringify(snapshot, null, 2) + '\n', 'utf8')
|
|
101
|
-
|
|
102
|
-
try {
|
|
103
|
-
await fs.rename(uniqueTempPath, this.registryPath)
|
|
104
|
-
} catch (err: unknown) {
|
|
105
|
-
// Clean up temp file if rename fails
|
|
106
|
-
await fs.unlink(uniqueTempPath).catch(() => {})
|
|
107
|
-
throw err
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Clean up stale file (ignore errors)
|
|
111
|
-
await fs.unlink(this.stalePath).catch(() => {})
|
|
112
|
-
|
|
113
|
-
return branches
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Scans the root directory for branch subdirectories with valid branch.json files.
|
|
118
|
-
*/
|
|
119
|
-
private async scanBranchDirectories(): Promise<BranchContext[]> {
|
|
120
|
-
const branches: BranchContext[] = []
|
|
121
|
-
|
|
122
|
-
try {
|
|
123
|
-
const entries = await fs.readdir(this.root, { withFileTypes: true })
|
|
124
|
-
|
|
125
|
-
for (const entry of entries) {
|
|
126
|
-
// Skip non-directories and hidden directories (like .canopy-meta, .canopy-prod-sim)
|
|
127
|
-
if (!entry.isDirectory() || entry.name.startsWith('.')) {
|
|
128
|
-
continue
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const branchRoot = path.join(this.root, entry.name)
|
|
132
|
-
const meta = await BranchMetadataFileManager.loadOnly(branchRoot)
|
|
133
|
-
|
|
134
|
-
if (meta) {
|
|
135
|
-
branches.push({
|
|
136
|
-
branch: meta.branch,
|
|
137
|
-
branchRoot,
|
|
138
|
-
baseRoot: this.root,
|
|
139
|
-
})
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
} catch (err: unknown) {
|
|
143
|
-
// If root doesn't exist yet, return empty list
|
|
144
|
-
if (isNotFoundError(err)) {
|
|
145
|
-
return []
|
|
146
|
-
}
|
|
147
|
-
throw err
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return branches
|
|
151
|
-
}
|
|
152
|
-
}
|
|
@@ -1,275 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, beforeEach, afterEach } from 'vitest'
|
|
2
|
-
import fs from 'node:fs/promises'
|
|
3
|
-
import path from 'node:path'
|
|
4
|
-
import os from 'node:os'
|
|
5
|
-
import { BranchSchemaCache } from './branch-schema-cache'
|
|
6
|
-
import type { FieldConfig } from './config'
|
|
7
|
-
|
|
8
|
-
describe('BranchSchemaCache', () => {
|
|
9
|
-
let tempDir: string
|
|
10
|
-
let branchRoot: string
|
|
11
|
-
|
|
12
|
-
beforeEach(async () => {
|
|
13
|
-
// Create temp directory for testing
|
|
14
|
-
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'schema-cache-test-'))
|
|
15
|
-
branchRoot = path.join(tempDir, 'branch-workspace')
|
|
16
|
-
await fs.mkdir(branchRoot, { recursive: true })
|
|
17
|
-
|
|
18
|
-
// Create content directory structure
|
|
19
|
-
const contentRoot = path.join(branchRoot, 'content')
|
|
20
|
-
await fs.mkdir(contentRoot, { recursive: true })
|
|
21
|
-
|
|
22
|
-
// Create a simple .collection.json file
|
|
23
|
-
await fs.writeFile(
|
|
24
|
-
path.join(contentRoot, '.collection.json'),
|
|
25
|
-
JSON.stringify({
|
|
26
|
-
label: 'Root',
|
|
27
|
-
entries: [
|
|
28
|
-
{
|
|
29
|
-
name: 'page',
|
|
30
|
-
format: 'md',
|
|
31
|
-
schema: 'pageSchema',
|
|
32
|
-
},
|
|
33
|
-
],
|
|
34
|
-
order: [],
|
|
35
|
-
}),
|
|
36
|
-
'utf-8',
|
|
37
|
-
)
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
afterEach(async () => {
|
|
41
|
-
// Clean up temp directory
|
|
42
|
-
await fs.rm(tempDir, { recursive: true, force: true })
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
describe('prod-sim mode', () => {
|
|
46
|
-
it('should load schema from .collection.json files on first access (cache miss)', async () => {
|
|
47
|
-
const registry = new BranchSchemaCache('prod-sim')
|
|
48
|
-
const entrySchemaRegistry: Record<string, readonly FieldConfig[]> = {
|
|
49
|
-
pageSchema: [{ name: 'title', type: 'string', label: 'Title' }],
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const result = await registry.getSchema(branchRoot, entrySchemaRegistry)
|
|
53
|
-
|
|
54
|
-
expect(result.schema).toBeDefined()
|
|
55
|
-
expect(result.flatSchema).toBeDefined()
|
|
56
|
-
expect(result.schema.entries).toBeDefined()
|
|
57
|
-
expect(result.schema.entries?.length).toBe(1)
|
|
58
|
-
expect(result.schema.entries?.[0].name).toBe('page')
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
it('should use cache on second access (cache hit)', async () => {
|
|
62
|
-
const registry = new BranchSchemaCache('prod-sim')
|
|
63
|
-
const entrySchemaRegistry: Record<string, readonly FieldConfig[]> = {
|
|
64
|
-
pageSchema: [{ name: 'title', type: 'string', label: 'Title' }],
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// First access - cache miss
|
|
68
|
-
const result1 = await registry.getSchema(branchRoot, entrySchemaRegistry)
|
|
69
|
-
|
|
70
|
-
// Second access - should be faster (cache hit)
|
|
71
|
-
const start2 = Date.now()
|
|
72
|
-
const result2 = await registry.getSchema(branchRoot, entrySchemaRegistry)
|
|
73
|
-
const duration2 = Date.now() - start2
|
|
74
|
-
|
|
75
|
-
// Results should be the same
|
|
76
|
-
expect(result2.schema).toEqual(result1.schema)
|
|
77
|
-
expect(result2.flatSchema).toEqual(result1.flatSchema)
|
|
78
|
-
|
|
79
|
-
// Second access (cache hit) should be very fast (no filesystem I/O)
|
|
80
|
-
expect(duration2).toBeLessThan(10)
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
it('should write cache file to .canopy-meta/schema-cache.json', async () => {
|
|
84
|
-
const registry = new BranchSchemaCache('prod-sim')
|
|
85
|
-
const entrySchemaRegistry: Record<string, readonly FieldConfig[]> = {
|
|
86
|
-
pageSchema: [{ name: 'title', type: 'string', label: 'Title' }],
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
await registry.getSchema(branchRoot, entrySchemaRegistry)
|
|
90
|
-
|
|
91
|
-
const cachePath = path.join(branchRoot, '.canopy-meta', 'schema-cache.json')
|
|
92
|
-
const cacheExists = await fs
|
|
93
|
-
.access(cachePath)
|
|
94
|
-
.then(() => true)
|
|
95
|
-
.catch(() => false)
|
|
96
|
-
|
|
97
|
-
expect(cacheExists).toBe(true)
|
|
98
|
-
|
|
99
|
-
// Verify cache structure
|
|
100
|
-
const cacheContent = await fs.readFile(cachePath, 'utf-8')
|
|
101
|
-
const cache = JSON.parse(cacheContent)
|
|
102
|
-
expect(cache.version).toBe(2)
|
|
103
|
-
expect(cache.schema).toBeDefined()
|
|
104
|
-
expect(cache.flatSchema).toBeDefined()
|
|
105
|
-
expect(cache.cachedAt).toBeDefined()
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
it('should invalidate cache when invalidate() is called', async () => {
|
|
109
|
-
const registry = new BranchSchemaCache('prod-sim')
|
|
110
|
-
const entrySchemaRegistry: Record<string, readonly FieldConfig[]> = {
|
|
111
|
-
pageSchema: [{ name: 'title', type: 'string', label: 'Title' }],
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Load schema (creates cache)
|
|
115
|
-
await registry.getSchema(branchRoot, entrySchemaRegistry)
|
|
116
|
-
|
|
117
|
-
// Invalidate cache (creates .stale marker)
|
|
118
|
-
await registry.invalidate(branchRoot)
|
|
119
|
-
|
|
120
|
-
const stalePath = path.join(branchRoot, '.canopy-meta', 'schema-cache.stale')
|
|
121
|
-
const staleExists = await fs
|
|
122
|
-
.access(stalePath)
|
|
123
|
-
.then(() => true)
|
|
124
|
-
.catch(() => false)
|
|
125
|
-
|
|
126
|
-
expect(staleExists).toBe(true)
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
it('should regenerate cache when .stale marker exists', async () => {
|
|
130
|
-
const registry = new BranchSchemaCache('prod-sim')
|
|
131
|
-
const entrySchemaRegistry: Record<string, readonly FieldConfig[]> = {
|
|
132
|
-
pageSchema: [{ name: 'title', type: 'string', label: 'Title' }],
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// First load (creates cache)
|
|
136
|
-
await registry.getSchema(branchRoot, entrySchemaRegistry)
|
|
137
|
-
const cachePath = path.join(branchRoot, '.canopy-meta', 'schema-cache.json')
|
|
138
|
-
const cache1Stat = await fs.stat(cachePath)
|
|
139
|
-
|
|
140
|
-
// Wait a bit to ensure modification time is different
|
|
141
|
-
await new Promise((resolve) => setTimeout(resolve, 10))
|
|
142
|
-
|
|
143
|
-
// Invalidate cache
|
|
144
|
-
await registry.invalidate(branchRoot)
|
|
145
|
-
|
|
146
|
-
// Second load (should regenerate because of .stale marker)
|
|
147
|
-
await registry.getSchema(branchRoot, entrySchemaRegistry)
|
|
148
|
-
const cache2Stat = await fs.stat(cachePath)
|
|
149
|
-
|
|
150
|
-
// Cache file should have been regenerated (newer modification time)
|
|
151
|
-
expect(cache2Stat.mtimeMs).toBeGreaterThan(cache1Stat.mtimeMs)
|
|
152
|
-
|
|
153
|
-
// Stale marker should be removed
|
|
154
|
-
const stalePath = path.join(branchRoot, '.canopy-meta', 'schema-cache.stale')
|
|
155
|
-
const staleExists = await fs
|
|
156
|
-
.access(stalePath)
|
|
157
|
-
.then(() => true)
|
|
158
|
-
.catch(() => false)
|
|
159
|
-
expect(staleExists).toBe(false)
|
|
160
|
-
})
|
|
161
|
-
|
|
162
|
-
it('should handle missing cache file gracefully', async () => {
|
|
163
|
-
const registry = new BranchSchemaCache('prod-sim')
|
|
164
|
-
const entrySchemaRegistry: Record<string, readonly FieldConfig[]> = {
|
|
165
|
-
pageSchema: [{ name: 'title', type: 'string', label: 'Title' }],
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// First load without any cache
|
|
169
|
-
const result = await registry.getSchema(branchRoot, entrySchemaRegistry)
|
|
170
|
-
|
|
171
|
-
expect(result.schema).toBeDefined()
|
|
172
|
-
expect(result.flatSchema).toBeDefined()
|
|
173
|
-
})
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
describe('dev mode', () => {
|
|
177
|
-
it('should use in-memory cache (no file I/O)', async () => {
|
|
178
|
-
const registry = new BranchSchemaCache('dev')
|
|
179
|
-
const entrySchemaRegistry: Record<string, readonly FieldConfig[]> = {
|
|
180
|
-
pageSchema: [{ name: 'title', type: 'string', label: 'Title' }],
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Load schema
|
|
184
|
-
await registry.getSchema(branchRoot, entrySchemaRegistry)
|
|
185
|
-
|
|
186
|
-
// No cache file should be created in dev mode
|
|
187
|
-
const cachePath = path.join(branchRoot, '.canopy-meta', 'schema-cache.json')
|
|
188
|
-
const cacheExists = await fs
|
|
189
|
-
.access(cachePath)
|
|
190
|
-
.then(() => true)
|
|
191
|
-
.catch(() => false)
|
|
192
|
-
|
|
193
|
-
expect(cacheExists).toBe(false)
|
|
194
|
-
})
|
|
195
|
-
|
|
196
|
-
it('should use singleton in-memory cache', async () => {
|
|
197
|
-
const registry = new BranchSchemaCache('dev')
|
|
198
|
-
const entrySchemaRegistry: Record<string, readonly FieldConfig[]> = {
|
|
199
|
-
pageSchema: [{ name: 'title', type: 'string', label: 'Title' }],
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// First access
|
|
203
|
-
const result1 = await registry.getSchema(branchRoot, entrySchemaRegistry)
|
|
204
|
-
|
|
205
|
-
// Second access (should use in-memory cache)
|
|
206
|
-
const result2 = await registry.getSchema(branchRoot, entrySchemaRegistry)
|
|
207
|
-
|
|
208
|
-
// Results should be the exact same object (reference equality)
|
|
209
|
-
expect(result2).toBe(result1)
|
|
210
|
-
})
|
|
211
|
-
|
|
212
|
-
it('should clear in-memory cache when invalidate() is called', async () => {
|
|
213
|
-
const registry = new BranchSchemaCache('dev')
|
|
214
|
-
const entrySchemaRegistry: Record<string, readonly FieldConfig[]> = {
|
|
215
|
-
pageSchema: [{ name: 'title', type: 'string', label: 'Title' }],
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// Load schema
|
|
219
|
-
const result1 = await registry.getSchema(branchRoot, entrySchemaRegistry)
|
|
220
|
-
|
|
221
|
-
// Invalidate
|
|
222
|
-
await registry.invalidate(branchRoot)
|
|
223
|
-
|
|
224
|
-
// Load again (should be a new object)
|
|
225
|
-
const result2 = await registry.getSchema(branchRoot, entrySchemaRegistry)
|
|
226
|
-
|
|
227
|
-
// Should not be the same reference (cache was cleared)
|
|
228
|
-
expect(result2).not.toBe(result1)
|
|
229
|
-
|
|
230
|
-
// But content should be the same
|
|
231
|
-
expect(result2.schema).toEqual(result1.schema)
|
|
232
|
-
})
|
|
233
|
-
|
|
234
|
-
it('should not create .stale marker in dev mode', async () => {
|
|
235
|
-
const registry = new BranchSchemaCache('dev')
|
|
236
|
-
const entrySchemaRegistry: Record<string, readonly FieldConfig[]> = {
|
|
237
|
-
pageSchema: [{ name: 'title', type: 'string', label: 'Title' }],
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// Load and invalidate
|
|
241
|
-
await registry.getSchema(branchRoot, entrySchemaRegistry)
|
|
242
|
-
await registry.invalidate(branchRoot)
|
|
243
|
-
|
|
244
|
-
// No .stale marker should exist
|
|
245
|
-
const stalePath = path.join(branchRoot, '.canopy-meta', 'schema-cache.stale')
|
|
246
|
-
const staleExists = await fs
|
|
247
|
-
.access(stalePath)
|
|
248
|
-
.then(() => true)
|
|
249
|
-
.catch(() => false)
|
|
250
|
-
|
|
251
|
-
expect(staleExists).toBe(false)
|
|
252
|
-
})
|
|
253
|
-
})
|
|
254
|
-
|
|
255
|
-
describe('clearAll', () => {
|
|
256
|
-
it('should clear in-memory cache in dev mode', async () => {
|
|
257
|
-
const registry = new BranchSchemaCache('dev')
|
|
258
|
-
const entrySchemaRegistry: Record<string, readonly FieldConfig[]> = {
|
|
259
|
-
pageSchema: [{ name: 'title', type: 'string', label: 'Title' }],
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// Load schema
|
|
263
|
-
const result1 = await registry.getSchema(branchRoot, entrySchemaRegistry)
|
|
264
|
-
|
|
265
|
-
// Clear all
|
|
266
|
-
await registry.clearAll()
|
|
267
|
-
|
|
268
|
-
// Load again
|
|
269
|
-
const result2 = await registry.getSchema(branchRoot, entrySchemaRegistry)
|
|
270
|
-
|
|
271
|
-
// Should not be the same reference
|
|
272
|
-
expect(result2).not.toBe(result1)
|
|
273
|
-
})
|
|
274
|
-
})
|
|
275
|
-
})
|