canopycms 0.0.0 → 0.0.2
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/auth/plugin.d.ts +8 -0
- package/dist/auth/plugin.d.ts.map +1 -1
- package/dist/build-mode.d.ts +15 -5
- package/dist/build-mode.d.ts.map +1 -1
- package/dist/build-mode.js +18 -8
- package/dist/build-mode.js.map +1 -1
- package/dist/cli/init.d.ts +2 -2
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +37 -36
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/template-files/ai-config.ts.template +21 -0
- package/dist/cli/template-files/ai-route.ts.template +10 -0
- package/dist/cli/template-files/canopy.ts.template +24 -0
- package/dist/cli/templates.d.ts +5 -1
- package/dist/cli/templates.d.ts.map +1 -1
- package/dist/cli/templates.js +9 -2
- package/dist/cli/templates.js.map +1 -1
- package/dist/config/schemas/config.d.ts +4 -0
- package/dist/config/schemas/config.d.ts.map +1 -1
- package/dist/config/schemas/config.js +2 -0
- package/dist/config/schemas/config.js.map +1 -1
- package/dist/config/types.d.ts +5 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/content-reader.js +2 -2
- package/dist/content-reader.js.map +1 -1
- package/dist/context.js +5 -5
- package/dist/context.js.map +1 -1
- package/dist/operating-mode/client-unsafe-strategy.d.ts.map +1 -1
- package/dist/operating-mode/client-unsafe-strategy.js +15 -18
- package/dist/operating-mode/client-unsafe-strategy.js.map +1 -1
- package/dist/operating-mode/types.d.ts +8 -0
- package/dist/operating-mode/types.d.ts.map +1 -1
- package/dist/server.d.ts +2 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +2 -0
- package/dist/server.js.map +1 -1
- package/package.json +5 -4
- package/src/cli/init.ts +43 -38
- 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/canopy.ts.template +0 -55
- 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
- /package/{src/cli/templates → dist/cli/template-files}/Dockerfile.cms.template +0 -0
- /package/{src/cli/templates → dist/cli/template-files}/canopycms.config.ts.template +0 -0
- /package/{src/cli/templates → dist/cli/template-files}/deploy-cms.yml.template +0 -0
- /package/{src/cli/templates → dist/cli/template-files}/edit-page.tsx.template +0 -0
- /package/{src/cli/templates → dist/cli/template-files}/route.ts.template +0 -0
- /package/{src/cli/templates → dist/cli/template-files}/schemas.ts.template +0 -0
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
import type { ContentIdIndex } from '../content-id-index'
|
|
2
|
-
import type { FieldConfig, ReferenceFieldConfig } from '../config'
|
|
3
|
-
import { isValidId } from '../id'
|
|
4
|
-
import { findFieldsByType } from './field-traversal'
|
|
5
|
-
|
|
6
|
-
export interface ValidationError {
|
|
7
|
-
field: string
|
|
8
|
-
fieldPath: string
|
|
9
|
-
id: string
|
|
10
|
-
error: string
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface ValidationResult {
|
|
14
|
-
valid: boolean
|
|
15
|
-
errors: ValidationError[]
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* ReferenceValidator validates that referenced content IDs exist and match collection constraints.
|
|
20
|
-
*
|
|
21
|
-
* This class provides validation for:
|
|
22
|
-
* - ID format validation (valid short UUID)
|
|
23
|
-
* - ID existence validation (entry actually exists)
|
|
24
|
-
* - Collection constraint validation (entry is in allowed collections)
|
|
25
|
-
*
|
|
26
|
-
* Usage:
|
|
27
|
-
* const validator = new ReferenceValidator(idIndex, schema)
|
|
28
|
-
* const result = await validator.validate(entryData)
|
|
29
|
-
* if (!result.valid) {
|
|
30
|
-
* console.error('Validation errors:', result.errors)
|
|
31
|
-
* }
|
|
32
|
-
*/
|
|
33
|
-
export class ReferenceValidator {
|
|
34
|
-
constructor(
|
|
35
|
-
private idIndex: ContentIdIndex,
|
|
36
|
-
private schema: readonly FieldConfig[],
|
|
37
|
-
) {}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Validate all reference fields in the provided data.
|
|
41
|
-
*
|
|
42
|
-
* @param data - The entry data to validate
|
|
43
|
-
* @returns Validation result with any errors found
|
|
44
|
-
*/
|
|
45
|
-
async validate(data: Record<string, unknown>): Promise<ValidationResult> {
|
|
46
|
-
const errors: ValidationError[] = []
|
|
47
|
-
// Use shared field traversal to find all reference fields
|
|
48
|
-
const refContexts = findFieldsByType(this.schema, data, 'reference')
|
|
49
|
-
const refs = refContexts.map((ctx) => ({
|
|
50
|
-
field: ctx.field as ReferenceFieldConfig,
|
|
51
|
-
value: ctx.value as string | string[],
|
|
52
|
-
path: ctx.path,
|
|
53
|
-
}))
|
|
54
|
-
|
|
55
|
-
for (const { field, value, path } of refs) {
|
|
56
|
-
const ids = Array.isArray(value) ? value : [value]
|
|
57
|
-
|
|
58
|
-
for (const id of ids) {
|
|
59
|
-
// Skip null/undefined values (they're handled by required validation)
|
|
60
|
-
if (id == null) continue
|
|
61
|
-
|
|
62
|
-
// Validate ID format
|
|
63
|
-
if (typeof id !== 'string' || !isValidId(id)) {
|
|
64
|
-
errors.push({
|
|
65
|
-
field: field.name,
|
|
66
|
-
fieldPath: path,
|
|
67
|
-
id: String(id),
|
|
68
|
-
error: 'Invalid content ID format',
|
|
69
|
-
})
|
|
70
|
-
continue
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Validate ID exists
|
|
74
|
-
const location = this.idIndex.findById(id)
|
|
75
|
-
if (!location) {
|
|
76
|
-
errors.push({
|
|
77
|
-
field: field.name,
|
|
78
|
-
fieldPath: path,
|
|
79
|
-
id,
|
|
80
|
-
error: 'Referenced entry does not exist',
|
|
81
|
-
})
|
|
82
|
-
continue
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Validate location is an entry (not a collection)
|
|
86
|
-
if (location.type !== 'entry') {
|
|
87
|
-
errors.push({
|
|
88
|
-
field: field.name,
|
|
89
|
-
fieldPath: path,
|
|
90
|
-
id,
|
|
91
|
-
error: 'ID points to a collection, not an entry',
|
|
92
|
-
})
|
|
93
|
-
continue
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Validate collection constraint
|
|
97
|
-
if (field.collections && field.collections.length > 0) {
|
|
98
|
-
const allowed = field.collections.some((col: string) => {
|
|
99
|
-
// Exact match or nested collection match
|
|
100
|
-
return location.collection === col || location.collection?.startsWith(col + '/')
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
if (!allowed) {
|
|
104
|
-
errors.push({
|
|
105
|
-
field: field.name,
|
|
106
|
-
fieldPath: path,
|
|
107
|
-
id,
|
|
108
|
-
error: `Entry is in collection "${location.collection}", but only [${field.collections.join(', ')}] are allowed`,
|
|
109
|
-
})
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return { valid: errors.length === 0, errors }
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Validate a single reference ID.
|
|
120
|
-
* Useful for validating user input in real-time.
|
|
121
|
-
*/
|
|
122
|
-
async validateSingle(id: string, field: ReferenceFieldConfig): Promise<ValidationError | null> {
|
|
123
|
-
if (!isValidId(id)) {
|
|
124
|
-
return {
|
|
125
|
-
field: field.name,
|
|
126
|
-
fieldPath: field.name,
|
|
127
|
-
id,
|
|
128
|
-
error: 'Invalid content ID format',
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const location = this.idIndex.findById(id)
|
|
133
|
-
if (!location) {
|
|
134
|
-
return {
|
|
135
|
-
field: field.name,
|
|
136
|
-
fieldPath: field.name,
|
|
137
|
-
id,
|
|
138
|
-
error: 'Referenced entry does not exist',
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (location.type !== 'entry') {
|
|
143
|
-
return {
|
|
144
|
-
field: field.name,
|
|
145
|
-
fieldPath: field.name,
|
|
146
|
-
id,
|
|
147
|
-
error: 'ID points to a collection, not an entry',
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (field.collections && field.collections.length > 0) {
|
|
152
|
-
const allowed = field.collections.some((col: string) => {
|
|
153
|
-
return location.collection === col || location.collection?.startsWith(col + '/')
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
if (!allowed) {
|
|
157
|
-
return {
|
|
158
|
-
field: field.name,
|
|
159
|
-
fieldPath: field.name,
|
|
160
|
-
id,
|
|
161
|
-
error: `Entry is in collection "${location.collection}", but only [${field.collections.join(', ')}] are allowed`,
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return null
|
|
167
|
-
}
|
|
168
|
-
}
|
|
@@ -1,473 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for CmsWorker.rebaseActiveBranches()
|
|
3
|
-
*
|
|
4
|
-
* Uses real git operations against temp directories to verify:
|
|
5
|
-
* - Branches in review (submitted/approved) are not rebased
|
|
6
|
-
* - Branches with uncommitted changes (dirty working tree) are not rebased
|
|
7
|
-
* - Already-in-sync branches get their stale conflict state cleared
|
|
8
|
-
* - Clean rebases mark the branch as clean
|
|
9
|
-
* - Conflicting files keep the branch version (--theirs during rebase), ContentIds recorded in conflictFiles
|
|
10
|
-
* - Non-entry files (no embedded ContentId) are excluded from conflictFiles
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import fs from 'node:fs/promises'
|
|
14
|
-
import os from 'node:os'
|
|
15
|
-
import path from 'node:path'
|
|
16
|
-
|
|
17
|
-
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
18
|
-
import { simpleGit, type SimpleGit } from 'simple-git'
|
|
19
|
-
|
|
20
|
-
import { BranchMetadataFileManager } from '../branch-metadata'
|
|
21
|
-
import { ROOT_COLLECTION_ID } from '../paths/types'
|
|
22
|
-
import { initTestRepo, mockConsole } from '../test-utils'
|
|
23
|
-
import { CmsWorker } from './cms-worker'
|
|
24
|
-
|
|
25
|
-
// ---------------------------------------------------------------------------
|
|
26
|
-
// Helpers
|
|
27
|
-
// ---------------------------------------------------------------------------
|
|
28
|
-
|
|
29
|
-
/** Instantiate a CmsWorker with minimal config for testing rebase logic only. */
|
|
30
|
-
const makeWorker = (workspacePath: string, baseBranch = 'main') =>
|
|
31
|
-
new CmsWorker({
|
|
32
|
-
workspacePath,
|
|
33
|
-
githubOwner: 'test-owner',
|
|
34
|
-
githubRepo: 'test-repo',
|
|
35
|
-
githubToken: 'fake-token',
|
|
36
|
-
baseBranch,
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
/** Invoke the private rebaseActiveBranches() method. */
|
|
40
|
-
const runRebase = (worker: CmsWorker): Promise<void> =>
|
|
41
|
-
(worker as unknown as { rebaseActiveBranches(): Promise<void> }).rebaseActiveBranches()
|
|
42
|
-
|
|
43
|
-
/** Write branch metadata for a workspace. */
|
|
44
|
-
const writeMeta = async (
|
|
45
|
-
branchPath: string,
|
|
46
|
-
contentBranchesPath: string,
|
|
47
|
-
data: Record<string, unknown>,
|
|
48
|
-
) => {
|
|
49
|
-
const meta = BranchMetadataFileManager.get(branchPath, contentBranchesPath)
|
|
50
|
-
await meta.save({
|
|
51
|
-
branch: {
|
|
52
|
-
name: path.basename(branchPath),
|
|
53
|
-
status: 'editing' as const,
|
|
54
|
-
access: {},
|
|
55
|
-
createdBy: 'test',
|
|
56
|
-
...data,
|
|
57
|
-
},
|
|
58
|
-
})
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** Read saved branch metadata. */
|
|
62
|
-
const readMeta = (branchPath: string) =>
|
|
63
|
-
BranchMetadataFileManager.loadOnly(branchPath).then((f) => f?.branch)
|
|
64
|
-
|
|
65
|
-
// ---------------------------------------------------------------------------
|
|
66
|
-
// Test workspace factory
|
|
67
|
-
// ---------------------------------------------------------------------------
|
|
68
|
-
|
|
69
|
-
interface BranchSetup {
|
|
70
|
-
branchPath: string
|
|
71
|
-
contentBranchesPath: string
|
|
72
|
-
branchGit: SimpleGit
|
|
73
|
-
/** Add a commit to the origin remote (makes the branch workspace "behind"). */
|
|
74
|
-
pushToRemote: (files: Record<string, string>, message?: string) => Promise<void>
|
|
75
|
-
/** Commit changes in the branch workspace. */
|
|
76
|
-
commitToBranch: (files: Record<string, string>, message?: string) => Promise<void>
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Creates a local git setup: a "remote" repo and a branch-workspace clone.
|
|
81
|
-
* The branch workspace's feature branch tracks origin/<baseBranch>.
|
|
82
|
-
*/
|
|
83
|
-
async function createBranchSetup(
|
|
84
|
-
tmpDir: string,
|
|
85
|
-
branchName: string,
|
|
86
|
-
opts: { baseBranch?: string; initialFiles?: Record<string, string> } = {},
|
|
87
|
-
): Promise<BranchSetup> {
|
|
88
|
-
const { baseBranch = 'main', initialFiles = { '.gitkeep': '' } } = opts
|
|
89
|
-
|
|
90
|
-
const remotePath = path.join(tmpDir, 'remote')
|
|
91
|
-
const contentBranchesPath = path.join(tmpDir, 'content-branches')
|
|
92
|
-
const branchPath = path.join(contentBranchesPath, branchName)
|
|
93
|
-
|
|
94
|
-
// --- Set up remote repo ---
|
|
95
|
-
await fs.mkdir(remotePath)
|
|
96
|
-
const remoteGit = await initTestRepo(remotePath)
|
|
97
|
-
await remoteGit.raw(['branch', '-M', baseBranch])
|
|
98
|
-
|
|
99
|
-
for (const [name, content] of Object.entries(initialFiles)) {
|
|
100
|
-
const fullPath = path.join(remotePath, name)
|
|
101
|
-
await fs.mkdir(path.dirname(fullPath), { recursive: true })
|
|
102
|
-
await fs.writeFile(fullPath, content)
|
|
103
|
-
}
|
|
104
|
-
await remoteGit.add(['.'])
|
|
105
|
-
await remoteGit.commit('initial commit')
|
|
106
|
-
|
|
107
|
-
// --- Clone remote to branch workspace ---
|
|
108
|
-
await fs.mkdir(contentBranchesPath, { recursive: true })
|
|
109
|
-
await simpleGit().clone(remotePath, branchPath)
|
|
110
|
-
|
|
111
|
-
const branchGit = simpleGit({ baseDir: branchPath })
|
|
112
|
-
await branchGit.addConfig('user.name', 'Test Bot')
|
|
113
|
-
await branchGit.addConfig('user.email', 'test@canopycms.test')
|
|
114
|
-
// Prevent interactive editor prompts during `rebase --continue`
|
|
115
|
-
await branchGit.addConfig('core.editor', 'true')
|
|
116
|
-
|
|
117
|
-
// Exclude .canopy-meta/ from git tracking (matches production setup via ensureGitExclude)
|
|
118
|
-
const excludeFile = path.join(branchPath, '.git', 'info', 'exclude')
|
|
119
|
-
await fs.mkdir(path.dirname(excludeFile), { recursive: true })
|
|
120
|
-
await fs.appendFile(excludeFile, '\n.canopy-meta/\n')
|
|
121
|
-
|
|
122
|
-
// Check out a feature branch (distinct from baseBranch) that tracks origin/<baseBranch>
|
|
123
|
-
await branchGit.checkoutBranch(branchName, `origin/${baseBranch}`)
|
|
124
|
-
await branchGit.raw(['branch', `--set-upstream-to=origin/${baseBranch}`, branchName])
|
|
125
|
-
|
|
126
|
-
const pushToRemote = async (files: Record<string, string>, message = 'remote commit') => {
|
|
127
|
-
for (const [name, content] of Object.entries(files)) {
|
|
128
|
-
const fullPath = path.join(remotePath, name)
|
|
129
|
-
await fs.mkdir(path.dirname(fullPath), { recursive: true })
|
|
130
|
-
await fs.writeFile(fullPath, content)
|
|
131
|
-
}
|
|
132
|
-
await remoteGit.add(['.'])
|
|
133
|
-
await remoteGit.commit(message)
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const commitToBranch = async (files: Record<string, string>, message = 'branch commit') => {
|
|
137
|
-
for (const [name, content] of Object.entries(files)) {
|
|
138
|
-
const fullPath = path.join(branchPath, name)
|
|
139
|
-
await fs.mkdir(path.dirname(fullPath), { recursive: true })
|
|
140
|
-
await fs.writeFile(fullPath, content)
|
|
141
|
-
}
|
|
142
|
-
await branchGit.add(['.'])
|
|
143
|
-
await branchGit.commit(message)
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return {
|
|
147
|
-
branchPath,
|
|
148
|
-
contentBranchesPath,
|
|
149
|
-
branchGit,
|
|
150
|
-
pushToRemote,
|
|
151
|
-
commitToBranch,
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// ---------------------------------------------------------------------------
|
|
156
|
-
// Tests
|
|
157
|
-
// ---------------------------------------------------------------------------
|
|
158
|
-
|
|
159
|
-
describe('CmsWorker rebaseActiveBranches', () => {
|
|
160
|
-
let tmpDir: string
|
|
161
|
-
|
|
162
|
-
beforeEach(async () => {
|
|
163
|
-
mockConsole()
|
|
164
|
-
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'canopy-rebase-test-'))
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
afterEach(async () => {
|
|
168
|
-
await fs.rm(tmpDir, { recursive: true, force: true })
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
// -------------------------------------------------------------------------
|
|
172
|
-
// Skipping
|
|
173
|
-
// -------------------------------------------------------------------------
|
|
174
|
-
|
|
175
|
-
describe('skipping', () => {
|
|
176
|
-
it('does not rebase a submitted branch and leaves metadata unchanged', async () => {
|
|
177
|
-
const setup = await createBranchSetup(tmpDir, 'my-feature')
|
|
178
|
-
await setup.pushToRemote({ 'new-file.txt': 'from main' })
|
|
179
|
-
await writeMeta(setup.branchPath, setup.contentBranchesPath, {
|
|
180
|
-
status: 'submitted',
|
|
181
|
-
conflictStatus: 'conflicts-detected',
|
|
182
|
-
})
|
|
183
|
-
|
|
184
|
-
const worker = makeWorker(tmpDir)
|
|
185
|
-
await runRebase(worker)
|
|
186
|
-
|
|
187
|
-
// Metadata should be unchanged (the worker skipped entirely)
|
|
188
|
-
const meta = await readMeta(setup.branchPath)
|
|
189
|
-
expect(meta?.status).toBe('submitted')
|
|
190
|
-
expect(meta?.conflictStatus).toBe('conflicts-detected')
|
|
191
|
-
|
|
192
|
-
// Branch workspace should still be behind (fetch was not called)
|
|
193
|
-
await setup.branchGit.fetch('origin', 'main')
|
|
194
|
-
const status = await setup.branchGit.status()
|
|
195
|
-
expect(status.behind).toBeGreaterThan(0)
|
|
196
|
-
})
|
|
197
|
-
|
|
198
|
-
it('does not rebase an approved branch and leaves metadata unchanged', async () => {
|
|
199
|
-
const setup = await createBranchSetup(tmpDir, 'my-feature')
|
|
200
|
-
await setup.pushToRemote({ 'new-file.txt': 'from main' })
|
|
201
|
-
await writeMeta(setup.branchPath, setup.contentBranchesPath, {
|
|
202
|
-
status: 'approved',
|
|
203
|
-
conflictStatus: 'clean',
|
|
204
|
-
})
|
|
205
|
-
|
|
206
|
-
const worker = makeWorker(tmpDir)
|
|
207
|
-
await runRebase(worker)
|
|
208
|
-
|
|
209
|
-
const meta = await readMeta(setup.branchPath)
|
|
210
|
-
expect(meta?.status).toBe('approved')
|
|
211
|
-
expect(meta?.conflictStatus).toBe('clean')
|
|
212
|
-
|
|
213
|
-
await setup.branchGit.fetch('origin', 'main')
|
|
214
|
-
const status = await setup.branchGit.status()
|
|
215
|
-
expect(status.behind).toBeGreaterThan(0)
|
|
216
|
-
})
|
|
217
|
-
|
|
218
|
-
it('skips a branch with uncommitted changes and leaves metadata unchanged', async () => {
|
|
219
|
-
const setup = await createBranchSetup(tmpDir, 'my-feature')
|
|
220
|
-
await setup.pushToRemote({ 'new-file.txt': 'from main' })
|
|
221
|
-
await writeMeta(setup.branchPath, setup.contentBranchesPath, {
|
|
222
|
-
conflictStatus: 'conflicts-detected',
|
|
223
|
-
})
|
|
224
|
-
|
|
225
|
-
// Make the workspace dirty (uncommitted file)
|
|
226
|
-
await fs.writeFile(path.join(setup.branchPath, 'unsaved-edit.txt'), 'editor draft')
|
|
227
|
-
|
|
228
|
-
const worker = makeWorker(tmpDir)
|
|
229
|
-
await runRebase(worker)
|
|
230
|
-
|
|
231
|
-
// Metadata unchanged
|
|
232
|
-
const meta = await readMeta(setup.branchPath)
|
|
233
|
-
expect(meta?.conflictStatus).toBe('conflicts-detected')
|
|
234
|
-
|
|
235
|
-
// Dirty file still present
|
|
236
|
-
await expect(
|
|
237
|
-
fs.readFile(path.join(setup.branchPath, 'unsaved-edit.txt'), 'utf8'),
|
|
238
|
-
).resolves.toBe('editor draft')
|
|
239
|
-
|
|
240
|
-
// Branch still behind (no rebase happened)
|
|
241
|
-
await setup.branchGit.fetch('origin', 'main')
|
|
242
|
-
const status = await setup.branchGit.status()
|
|
243
|
-
expect(status.behind).toBeGreaterThan(0)
|
|
244
|
-
})
|
|
245
|
-
})
|
|
246
|
-
|
|
247
|
-
// -------------------------------------------------------------------------
|
|
248
|
-
// Already in sync
|
|
249
|
-
// -------------------------------------------------------------------------
|
|
250
|
-
|
|
251
|
-
describe('already in sync', () => {
|
|
252
|
-
it('marks conflictStatus clean when branch is already up to date', async () => {
|
|
253
|
-
const setup = await createBranchSetup(tmpDir, 'my-feature')
|
|
254
|
-
// No new commits pushed to remote: branch is already in sync
|
|
255
|
-
await writeMeta(setup.branchPath, setup.contentBranchesPath, {})
|
|
256
|
-
|
|
257
|
-
const worker = makeWorker(tmpDir)
|
|
258
|
-
await runRebase(worker)
|
|
259
|
-
|
|
260
|
-
const meta = await readMeta(setup.branchPath)
|
|
261
|
-
expect(meta?.conflictStatus).toBe('clean')
|
|
262
|
-
expect(meta?.conflictFiles).toEqual([])
|
|
263
|
-
})
|
|
264
|
-
|
|
265
|
-
it('clears stale conflictFiles when branch catches up without new conflicts', async () => {
|
|
266
|
-
const setup = await createBranchSetup(tmpDir, 'my-feature')
|
|
267
|
-
// Write stale conflict state
|
|
268
|
-
await writeMeta(setup.branchPath, setup.contentBranchesPath, {
|
|
269
|
-
conflictStatus: 'conflicts-detected',
|
|
270
|
-
conflictFiles: ['staleContentId123'],
|
|
271
|
-
})
|
|
272
|
-
|
|
273
|
-
const worker = makeWorker(tmpDir)
|
|
274
|
-
await runRebase(worker)
|
|
275
|
-
|
|
276
|
-
const meta = await readMeta(setup.branchPath)
|
|
277
|
-
expect(meta?.conflictStatus).toBe('clean')
|
|
278
|
-
expect(meta?.conflictFiles).toEqual([])
|
|
279
|
-
})
|
|
280
|
-
})
|
|
281
|
-
|
|
282
|
-
// -------------------------------------------------------------------------
|
|
283
|
-
// Clean rebase
|
|
284
|
-
// -------------------------------------------------------------------------
|
|
285
|
-
|
|
286
|
-
describe('clean rebase', () => {
|
|
287
|
-
it('rebases a behind branch and marks it clean when no conflicts', async () => {
|
|
288
|
-
const setup = await createBranchSetup(tmpDir, 'my-feature')
|
|
289
|
-
// Branch workspace has a commit of its own
|
|
290
|
-
await setup.commitToBranch({ 'branch-content.txt': 'branch work' })
|
|
291
|
-
// Remote advances with a non-conflicting file
|
|
292
|
-
await setup.pushToRemote({ 'main-update.txt': 'new from main' })
|
|
293
|
-
await writeMeta(setup.branchPath, setup.contentBranchesPath, {})
|
|
294
|
-
|
|
295
|
-
const worker = makeWorker(tmpDir)
|
|
296
|
-
await runRebase(worker)
|
|
297
|
-
|
|
298
|
-
// Branch should now be in sync
|
|
299
|
-
const status = await setup.branchGit.status()
|
|
300
|
-
expect(status.behind).toBe(0)
|
|
301
|
-
|
|
302
|
-
// Main's file should be present in the workspace
|
|
303
|
-
const mainContent = await fs.readFile(path.join(setup.branchPath, 'main-update.txt'), 'utf8')
|
|
304
|
-
expect(mainContent).toBe('new from main')
|
|
305
|
-
|
|
306
|
-
// Metadata should reflect clean state
|
|
307
|
-
const meta = await readMeta(setup.branchPath)
|
|
308
|
-
expect(meta?.conflictStatus).toBe('clean')
|
|
309
|
-
expect(meta?.conflictFiles).toEqual([])
|
|
310
|
-
})
|
|
311
|
-
})
|
|
312
|
-
|
|
313
|
-
// -------------------------------------------------------------------------
|
|
314
|
-
// Conflict handling
|
|
315
|
-
// -------------------------------------------------------------------------
|
|
316
|
-
|
|
317
|
-
describe('conflict handling', () => {
|
|
318
|
-
// Filename with embedded ContentId: TESTENTRYabc (12-char Base58)
|
|
319
|
-
const ENTRY_FILE = 'page.about.TESTENTRYabc.json'
|
|
320
|
-
const ENTRY_ID = 'TESTENTRYabc'
|
|
321
|
-
|
|
322
|
-
it('applies --theirs for conflicting entry files, keeps branch version, records ContentId', async () => {
|
|
323
|
-
const setup = await createBranchSetup(tmpDir, 'my-feature', {
|
|
324
|
-
initialFiles: { [ENTRY_FILE]: '{"title":"base content"}' },
|
|
325
|
-
})
|
|
326
|
-
|
|
327
|
-
// Branch commits its version of the entry
|
|
328
|
-
await setup.commitToBranch(
|
|
329
|
-
{ [ENTRY_FILE]: '{"title":"branch version"}' },
|
|
330
|
-
'branch: update entry',
|
|
331
|
-
)
|
|
332
|
-
// Remote advances with a conflicting version of the same entry
|
|
333
|
-
await setup.pushToRemote(
|
|
334
|
-
{ [ENTRY_FILE]: '{"title":"main version"}' },
|
|
335
|
-
'main: update same entry',
|
|
336
|
-
)
|
|
337
|
-
|
|
338
|
-
await writeMeta(setup.branchPath, setup.contentBranchesPath, {})
|
|
339
|
-
|
|
340
|
-
const worker = makeWorker(tmpDir)
|
|
341
|
-
await runRebase(worker)
|
|
342
|
-
|
|
343
|
-
// Branch should be in sync after rebase
|
|
344
|
-
const status = await setup.branchGit.status()
|
|
345
|
-
expect(status.behind).toBe(0)
|
|
346
|
-
|
|
347
|
-
// Branch version should be preserved (--theirs during rebase)
|
|
348
|
-
const fileContent = await fs.readFile(path.join(setup.branchPath, ENTRY_FILE), 'utf8')
|
|
349
|
-
expect(fileContent).toBe('{"title":"branch version"}')
|
|
350
|
-
|
|
351
|
-
// Metadata should record the conflict
|
|
352
|
-
const meta = await readMeta(setup.branchPath)
|
|
353
|
-
expect(meta?.conflictStatus).toBe('conflicts-detected')
|
|
354
|
-
expect(meta?.conflictFiles).toContain(ENTRY_ID)
|
|
355
|
-
})
|
|
356
|
-
|
|
357
|
-
it('excludes non-entry files from conflictFiles (conflictStatus stays clean)', async () => {
|
|
358
|
-
// README.md has no embedded ContentId — should be filtered out
|
|
359
|
-
const setup = await createBranchSetup(tmpDir, 'my-feature', {
|
|
360
|
-
initialFiles: { 'README.md': '# Base' },
|
|
361
|
-
})
|
|
362
|
-
|
|
363
|
-
// Both branch and remote modify README.md (conflict, but no ContentId)
|
|
364
|
-
await setup.commitToBranch({ 'README.md': '# Branch heading' }, 'branch: edit readme')
|
|
365
|
-
await setup.pushToRemote({ 'README.md': '# Main heading' }, 'main: edit readme')
|
|
366
|
-
|
|
367
|
-
await writeMeta(setup.branchPath, setup.contentBranchesPath, {})
|
|
368
|
-
|
|
369
|
-
const worker = makeWorker(tmpDir)
|
|
370
|
-
await runRebase(worker)
|
|
371
|
-
|
|
372
|
-
const meta = await readMeta(setup.branchPath)
|
|
373
|
-
// No entry ContentIds were involved, so conflict is invisible to the editor
|
|
374
|
-
expect(meta?.conflictStatus).toBe('clean')
|
|
375
|
-
expect(meta?.conflictFiles).toEqual([])
|
|
376
|
-
})
|
|
377
|
-
|
|
378
|
-
it('records parent collection ContentId when .collection.json conflicts in a subcollection', async () => {
|
|
379
|
-
// Subcollection directory has an embedded ID: posts.cNbR5xFm2Kpd
|
|
380
|
-
const COLLECTION_DIR = 'content/posts.cNbR5xFm2Kpd'
|
|
381
|
-
const COLLECTION_ID = 'cNbR5xFm2Kpd'
|
|
382
|
-
const META_FILE = `${COLLECTION_DIR}/.collection.json`
|
|
383
|
-
|
|
384
|
-
const setup = await createBranchSetup(tmpDir, 'my-feature', {
|
|
385
|
-
initialFiles: { [META_FILE]: '{"name":"posts","order":[]}' },
|
|
386
|
-
})
|
|
387
|
-
|
|
388
|
-
await setup.commitToBranch(
|
|
389
|
-
{ [META_FILE]: '{"name":"posts","order":["branch-order"]}' },
|
|
390
|
-
'branch: reorder collection',
|
|
391
|
-
)
|
|
392
|
-
await setup.pushToRemote(
|
|
393
|
-
{ [META_FILE]: '{"name":"posts","order":["main-order"]}' },
|
|
394
|
-
'main: reorder collection',
|
|
395
|
-
)
|
|
396
|
-
|
|
397
|
-
await writeMeta(setup.branchPath, setup.contentBranchesPath, {})
|
|
398
|
-
|
|
399
|
-
const worker = makeWorker(tmpDir)
|
|
400
|
-
await runRebase(worker)
|
|
401
|
-
|
|
402
|
-
const meta = await readMeta(setup.branchPath)
|
|
403
|
-
expect(meta?.conflictStatus).toBe('conflicts-detected')
|
|
404
|
-
expect(meta?.conflictFiles).toContain(COLLECTION_ID)
|
|
405
|
-
})
|
|
406
|
-
|
|
407
|
-
it('records ROOT_COLLECTION_ID when root .collection.json conflicts', async () => {
|
|
408
|
-
// Root content/.collection.json — parent dir "content" has no embedded ID
|
|
409
|
-
const META_FILE = 'content/.collection.json'
|
|
410
|
-
|
|
411
|
-
const setup = await createBranchSetup(tmpDir, 'my-feature', {
|
|
412
|
-
initialFiles: { [META_FILE]: '{"entries":[]}' },
|
|
413
|
-
})
|
|
414
|
-
|
|
415
|
-
await setup.commitToBranch(
|
|
416
|
-
{ [META_FILE]: '{"entries":[],"order":["branch"]}' },
|
|
417
|
-
'branch: update root schema',
|
|
418
|
-
)
|
|
419
|
-
await setup.pushToRemote(
|
|
420
|
-
{ [META_FILE]: '{"entries":[],"order":["main"]}' },
|
|
421
|
-
'main: update root schema',
|
|
422
|
-
)
|
|
423
|
-
|
|
424
|
-
await writeMeta(setup.branchPath, setup.contentBranchesPath, {})
|
|
425
|
-
|
|
426
|
-
const worker = makeWorker(tmpDir)
|
|
427
|
-
await runRebase(worker)
|
|
428
|
-
|
|
429
|
-
const meta = await readMeta(setup.branchPath)
|
|
430
|
-
expect(meta?.conflictStatus).toBe('conflicts-detected')
|
|
431
|
-
expect(meta?.conflictFiles).toContain(ROOT_COLLECTION_ID)
|
|
432
|
-
})
|
|
433
|
-
|
|
434
|
-
it('records both entry and collection ContentIds for mixed conflicts', async () => {
|
|
435
|
-
const COLLECTION_DIR = 'content/posts.cNbR5xFm2Kpd'
|
|
436
|
-
const COLLECTION_ID = 'cNbR5xFm2Kpd'
|
|
437
|
-
const META_FILE = `${COLLECTION_DIR}/.collection.json`
|
|
438
|
-
const ENTRY_FILE = `${COLLECTION_DIR}/page.about.TESTENTRYabc.json`
|
|
439
|
-
|
|
440
|
-
const setup = await createBranchSetup(tmpDir, 'my-feature', {
|
|
441
|
-
initialFiles: {
|
|
442
|
-
[META_FILE]: '{"name":"posts","order":[]}',
|
|
443
|
-
[ENTRY_FILE]: '{"title":"base"}',
|
|
444
|
-
},
|
|
445
|
-
})
|
|
446
|
-
|
|
447
|
-
await setup.commitToBranch(
|
|
448
|
-
{
|
|
449
|
-
[META_FILE]: '{"name":"posts","order":["branch"]}',
|
|
450
|
-
[ENTRY_FILE]: '{"title":"branch version"}',
|
|
451
|
-
},
|
|
452
|
-
'branch: update both',
|
|
453
|
-
)
|
|
454
|
-
await setup.pushToRemote(
|
|
455
|
-
{
|
|
456
|
-
[META_FILE]: '{"name":"posts","order":["main"]}',
|
|
457
|
-
[ENTRY_FILE]: '{"title":"main version"}',
|
|
458
|
-
},
|
|
459
|
-
'main: update both',
|
|
460
|
-
)
|
|
461
|
-
|
|
462
|
-
await writeMeta(setup.branchPath, setup.contentBranchesPath, {})
|
|
463
|
-
|
|
464
|
-
const worker = makeWorker(tmpDir)
|
|
465
|
-
await runRebase(worker)
|
|
466
|
-
|
|
467
|
-
const meta = await readMeta(setup.branchPath)
|
|
468
|
-
expect(meta?.conflictStatus).toBe('conflicts-detected')
|
|
469
|
-
expect(meta?.conflictFiles).toContain(COLLECTION_ID)
|
|
470
|
-
expect(meta?.conflictFiles).toContain('TESTENTRYabc')
|
|
471
|
-
})
|
|
472
|
-
})
|
|
473
|
-
})
|