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
package/src/git-manager.ts
DELETED
|
@@ -1,667 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs/promises'
|
|
2
|
-
import path from 'node:path'
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
simpleGit,
|
|
6
|
-
type ConfigListSummary,
|
|
7
|
-
type SimpleGit,
|
|
8
|
-
type SimpleGitOptions,
|
|
9
|
-
type StatusResult,
|
|
10
|
-
} from 'simple-git'
|
|
11
|
-
|
|
12
|
-
import type { OperatingMode } from './operating-mode'
|
|
13
|
-
import { createDebugLogger } from './utils/debug'
|
|
14
|
-
import { isNotFoundError } from './utils/error'
|
|
15
|
-
|
|
16
|
-
const log = createDebugLogger({ prefix: 'GitManager' })
|
|
17
|
-
|
|
18
|
-
// In-memory lock to prevent concurrent remote.git initialization
|
|
19
|
-
// Maps remotePath -> Promise<void> to serialize access
|
|
20
|
-
const remoteInitLocks = new Map<string, Promise<void>>()
|
|
21
|
-
|
|
22
|
-
export interface GitManagerOptions {
|
|
23
|
-
repoPath: string
|
|
24
|
-
baseBranch?: string
|
|
25
|
-
remote?: string
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export type GitStatus = Pick<StatusResult, 'files' | 'ahead' | 'behind' | 'current' | 'tracking'>
|
|
29
|
-
|
|
30
|
-
export interface ResolveRemoteUrlOptions {
|
|
31
|
-
mode: OperatingMode
|
|
32
|
-
remoteUrl?: string
|
|
33
|
-
defaultRemoteUrl?: string
|
|
34
|
-
baseBranch: string
|
|
35
|
-
sourceRoot?: string
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export interface InitializeWorkspaceOptions {
|
|
39
|
-
workspacePath: string
|
|
40
|
-
branchName: string
|
|
41
|
-
mode: OperatingMode
|
|
42
|
-
baseBranch?: string
|
|
43
|
-
sourceRoot?: string
|
|
44
|
-
defaultRemoteUrl?: string
|
|
45
|
-
remoteUrl?: string
|
|
46
|
-
remoteName?: string
|
|
47
|
-
branchType: 'content' | 'orphan' // Determines checkout vs createOrphan
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export class GitManager {
|
|
51
|
-
private readonly git: SimpleGit
|
|
52
|
-
private readonly repoPath: string
|
|
53
|
-
private readonly baseBranch: string
|
|
54
|
-
private readonly remote: string
|
|
55
|
-
|
|
56
|
-
constructor(options: GitManagerOptions, gitOptions?: Partial<SimpleGitOptions>) {
|
|
57
|
-
this.repoPath = path.resolve(options.repoPath)
|
|
58
|
-
this.baseBranch = options.baseBranch ?? 'main'
|
|
59
|
-
this.remote = options.remote ?? 'origin'
|
|
60
|
-
this.git = simpleGit({ baseDir: this.repoPath, ...gitOptions })
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
static async cloneRepo(
|
|
64
|
-
remoteUrl: string,
|
|
65
|
-
targetPath: string,
|
|
66
|
-
baseBranch = 'main',
|
|
67
|
-
): Promise<void> {
|
|
68
|
-
log.debug('git', 'Cloning repository', {
|
|
69
|
-
remoteUrl,
|
|
70
|
-
targetPath,
|
|
71
|
-
baseBranch,
|
|
72
|
-
})
|
|
73
|
-
const git = simpleGit()
|
|
74
|
-
await git.clone(remoteUrl, targetPath, ['--branch', baseBranch, '--single-branch'])
|
|
75
|
-
log.debug('git', 'Clone complete')
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Initializes a local bare git repository to simulate a remote for prod-sim mode.
|
|
80
|
-
*
|
|
81
|
-
* This is idempotent - if the remote already exists, it will not be recreated.
|
|
82
|
-
*
|
|
83
|
-
* The remote is seeded with the current state of the baseBranch (e.g., 'main').
|
|
84
|
-
* If you need to change the baseBranch or reset the simulation, delete
|
|
85
|
-
* `.canopycms/remote.git` and `.canopycms/branches` and restart.
|
|
86
|
-
*
|
|
87
|
-
* @throws Error if not a git repo, no commits, or baseBranch doesn't exist
|
|
88
|
-
*/
|
|
89
|
-
static async ensureLocalSimulatedRemote(options: {
|
|
90
|
-
remotePath: string
|
|
91
|
-
sourcePath: string
|
|
92
|
-
baseBranch: string
|
|
93
|
-
subdirectory?: string
|
|
94
|
-
}): Promise<void> {
|
|
95
|
-
// Serialize access per remote path to prevent race conditions
|
|
96
|
-
// when multiple requests try to initialize the same remote simultaneously
|
|
97
|
-
const existingLock = remoteInitLocks.get(options.remotePath)
|
|
98
|
-
if (existingLock) {
|
|
99
|
-
log.debug('git', 'Waiting for existing remote initialization', {
|
|
100
|
-
remotePath: options.remotePath,
|
|
101
|
-
})
|
|
102
|
-
await existingLock
|
|
103
|
-
// After waiting, verify the remote was created successfully
|
|
104
|
-
// If not, fall through to try again (the lock was cleaned up)
|
|
105
|
-
try {
|
|
106
|
-
const stat = await fs.stat(options.remotePath)
|
|
107
|
-
if (stat.isDirectory()) {
|
|
108
|
-
log.debug('git', 'Remote exists after waiting for lock')
|
|
109
|
-
return
|
|
110
|
-
}
|
|
111
|
-
} catch (err: unknown) {
|
|
112
|
-
if (!isNotFoundError(err)) throw err
|
|
113
|
-
// Remote doesn't exist, fall through to create it
|
|
114
|
-
log.debug('git', 'Remote does not exist after lock, will retry initialization')
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Create new lock promise
|
|
119
|
-
const lockPromise = log.timed('git', 'ensureLocalSimulatedRemote', async () => {
|
|
120
|
-
try {
|
|
121
|
-
log.debug('git', 'Initializing local simulated remote', {
|
|
122
|
-
remotePath: options.remotePath,
|
|
123
|
-
baseBranch: options.baseBranch,
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
// Check if already exists (idempotent)
|
|
127
|
-
try {
|
|
128
|
-
const stat = await fs.stat(options.remotePath)
|
|
129
|
-
if (stat.isDirectory()) {
|
|
130
|
-
log.debug('git', 'Remote already exists, skipping')
|
|
131
|
-
return
|
|
132
|
-
}
|
|
133
|
-
} catch (err: unknown) {
|
|
134
|
-
if (!isNotFoundError(err)) throw err
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Find the actual git root directory
|
|
138
|
-
// git subtree requires being run from the toplevel of the working tree
|
|
139
|
-
let gitRoot = options.sourcePath
|
|
140
|
-
try {
|
|
141
|
-
const sourceGit = simpleGit({ baseDir: options.sourcePath })
|
|
142
|
-
const result = await sourceGit.raw(['rev-parse', '--show-toplevel'])
|
|
143
|
-
gitRoot = result.trim()
|
|
144
|
-
} catch {
|
|
145
|
-
// If we can't find git root, fall back to sourcePath
|
|
146
|
-
gitRoot = options.sourcePath
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const sourceGit = simpleGit({ baseDir: gitRoot })
|
|
150
|
-
|
|
151
|
-
// Verify it's a git repo
|
|
152
|
-
try {
|
|
153
|
-
await sourceGit.status()
|
|
154
|
-
} catch {
|
|
155
|
-
throw new Error(
|
|
156
|
-
'Cannot initialize local simulated remote: current directory is not a git repository. ' +
|
|
157
|
-
'Please initialize git or provide an explicit remoteUrl.',
|
|
158
|
-
)
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Verify it has commits
|
|
162
|
-
let hasCommits = false
|
|
163
|
-
try {
|
|
164
|
-
const log = await sourceGit.log(['-1'])
|
|
165
|
-
hasCommits = log.total > 0
|
|
166
|
-
} catch {
|
|
167
|
-
// Log command fails if no commits exist
|
|
168
|
-
hasCommits = false
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if (!hasCommits) {
|
|
172
|
-
throw new Error(
|
|
173
|
-
'Cannot initialize local simulated remote: repository has no commits. ' +
|
|
174
|
-
'Please make an initial commit or provide an explicit remoteUrl.',
|
|
175
|
-
)
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Verify baseBranch exists
|
|
179
|
-
const branches = await sourceGit.branchLocal()
|
|
180
|
-
if (!branches.all.includes(options.baseBranch)) {
|
|
181
|
-
throw new Error(
|
|
182
|
-
`Cannot initialize local simulated remote: base branch '${options.baseBranch}' does not exist locally. ` +
|
|
183
|
-
`Please checkout '${options.baseBranch}' first or provide an explicit remoteUrl.`,
|
|
184
|
-
)
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Create bare remote
|
|
188
|
-
log.debug('git', 'Creating bare remote repository')
|
|
189
|
-
await fs.mkdir(path.dirname(options.remotePath), { recursive: true })
|
|
190
|
-
await simpleGit().raw([
|
|
191
|
-
'init',
|
|
192
|
-
'--bare',
|
|
193
|
-
`--initial-branch=${options.baseBranch}`,
|
|
194
|
-
options.remotePath,
|
|
195
|
-
])
|
|
196
|
-
|
|
197
|
-
// Push baseBranch to remote (not current HEAD)
|
|
198
|
-
const tempRemoteName = `__canopycms_init_${Date.now()}__`
|
|
199
|
-
try {
|
|
200
|
-
await sourceGit.addRemote(tempRemoteName, options.remotePath)
|
|
201
|
-
|
|
202
|
-
if (options.subdirectory) {
|
|
203
|
-
// For subdirectory pushes, use git subtree split
|
|
204
|
-
// This creates a synthetic history with only the subdirectory content
|
|
205
|
-
const splitBranch = `__canopycms_split_${Date.now()}__`
|
|
206
|
-
try {
|
|
207
|
-
await sourceGit.raw([
|
|
208
|
-
'subtree',
|
|
209
|
-
'split',
|
|
210
|
-
'--prefix',
|
|
211
|
-
options.subdirectory,
|
|
212
|
-
'-b',
|
|
213
|
-
splitBranch,
|
|
214
|
-
])
|
|
215
|
-
await sourceGit.push(tempRemoteName, `${splitBranch}:${options.baseBranch}`)
|
|
216
|
-
await sourceGit.raw(['branch', '-D', splitBranch])
|
|
217
|
-
} catch (err) {
|
|
218
|
-
// Clean up split branch if it exists
|
|
219
|
-
try {
|
|
220
|
-
await sourceGit.raw(['branch', '-D', splitBranch])
|
|
221
|
-
} catch {
|
|
222
|
-
// ignore
|
|
223
|
-
}
|
|
224
|
-
throw err
|
|
225
|
-
}
|
|
226
|
-
} else {
|
|
227
|
-
// Normal push of entire repo
|
|
228
|
-
await sourceGit.push(tempRemoteName, `${options.baseBranch}:${options.baseBranch}`)
|
|
229
|
-
}
|
|
230
|
-
} finally {
|
|
231
|
-
try {
|
|
232
|
-
await sourceGit.removeRemote(tempRemoteName)
|
|
233
|
-
} catch {
|
|
234
|
-
// ignore cleanup errors
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
log.debug('git', 'Remote initialization complete')
|
|
239
|
-
} finally {
|
|
240
|
-
// Always clean up the lock when done (success or failure)
|
|
241
|
-
remoteInitLocks.delete(options.remotePath)
|
|
242
|
-
}
|
|
243
|
-
})
|
|
244
|
-
|
|
245
|
-
// Store the lock promise
|
|
246
|
-
remoteInitLocks.set(options.remotePath, lockPromise)
|
|
247
|
-
|
|
248
|
-
// Wait for initialization to complete
|
|
249
|
-
await lockPromise
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
/**
|
|
253
|
-
* Find the git root directory
|
|
254
|
-
* @returns Path to git root, or cwd if not in a git repo
|
|
255
|
-
*/
|
|
256
|
-
static async findGitRoot(): Promise<string> {
|
|
257
|
-
let gitRoot = process.cwd()
|
|
258
|
-
try {
|
|
259
|
-
const git = simpleGit({ baseDir: process.cwd() })
|
|
260
|
-
const result = await git.raw(['rev-parse', '--show-toplevel'])
|
|
261
|
-
gitRoot = result.trim()
|
|
262
|
-
} catch {
|
|
263
|
-
// Fall back to cwd if not in a git repo
|
|
264
|
-
}
|
|
265
|
-
return gitRoot
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* Validate that a git repository exists at the given path
|
|
270
|
-
* @param repoPath - Path to check for .git directory
|
|
271
|
-
* @throws Error if git repo doesn't exist
|
|
272
|
-
*/
|
|
273
|
-
static async validateGitRepoExists(repoPath: string): Promise<void> {
|
|
274
|
-
try {
|
|
275
|
-
const stat = await fs.stat(path.join(repoPath, '.git'))
|
|
276
|
-
if (!stat.isDirectory()) {
|
|
277
|
-
throw new Error(`Expected git repo at ${repoPath}`)
|
|
278
|
-
}
|
|
279
|
-
} catch (err: unknown) {
|
|
280
|
-
if (isNotFoundError(err)) {
|
|
281
|
-
throw new Error(`Expected git repo at ${repoPath}`)
|
|
282
|
-
}
|
|
283
|
-
throw err
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* Resolves the remote URL for git operations following the priority:
|
|
289
|
-
* 1. Explicit remoteUrl parameter
|
|
290
|
-
* 2. Config defaultRemoteUrl
|
|
291
|
-
* 3. Environment variable (mode-specific)
|
|
292
|
-
* 4. Auto-initialized local remote (for prod-sim mode)
|
|
293
|
-
* 5. undefined (for dev mode)
|
|
294
|
-
*
|
|
295
|
-
* Uses strategy flags to determine behavior, GitManager executes the logic.
|
|
296
|
-
*
|
|
297
|
-
* @param options.sourceRoot - Optional source directory for monorepos. When provided,
|
|
298
|
-
* this directory (relative to git root) is used as the source for the simulated remote.
|
|
299
|
-
* Defaults to process.cwd().
|
|
300
|
-
*
|
|
301
|
-
* @returns Remote URL or undefined if no remote is needed
|
|
302
|
-
*/
|
|
303
|
-
static async resolveRemoteUrl(options: ResolveRemoteUrlOptions): Promise<string | undefined> {
|
|
304
|
-
// Dynamic import: operating-mode contains Node-only code; deferring the
|
|
305
|
-
// import keeps git-manager loadable in non-Node evaluation contexts
|
|
306
|
-
const { operatingStrategy } = await import('./operating-mode')
|
|
307
|
-
const strategy = operatingStrategy(options.mode)
|
|
308
|
-
const config = strategy.getRemoteUrlConfig()
|
|
309
|
-
|
|
310
|
-
// Centralized priority chain (no duplication across strategies)
|
|
311
|
-
if (options.remoteUrl) return options.remoteUrl
|
|
312
|
-
if (options.defaultRemoteUrl) return options.defaultRemoteUrl
|
|
313
|
-
if (process.env[config.envVarName]) return process.env[config.envVarName]
|
|
314
|
-
|
|
315
|
-
// Auto-detect: check if a pre-existing remote.git exists at the expected path
|
|
316
|
-
// (e.g., created by EC2 worker on EFS in prod mode)
|
|
317
|
-
if (config.autoDetectRemotePath) {
|
|
318
|
-
try {
|
|
319
|
-
const stat = await fs.stat(config.autoDetectRemotePath)
|
|
320
|
-
if (stat.isDirectory()) {
|
|
321
|
-
log.debug('git', 'Auto-detected local remote', {
|
|
322
|
-
path: config.autoDetectRemotePath,
|
|
323
|
-
})
|
|
324
|
-
return config.autoDetectRemotePath
|
|
325
|
-
}
|
|
326
|
-
} catch {
|
|
327
|
-
// Path doesn't exist — fall through to next resolution step
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// Mode-specific behavior: auto-init local remote
|
|
332
|
-
if (config.shouldAutoInitLocal) {
|
|
333
|
-
const gitRoot = await this.findGitRoot()
|
|
334
|
-
const sourceRoot = options.sourceRoot
|
|
335
|
-
const sourcePath = sourceRoot ? path.resolve(gitRoot, sourceRoot) : gitRoot
|
|
336
|
-
const localRemotePath = path.join(sourcePath, config.defaultRemotePath)
|
|
337
|
-
|
|
338
|
-
await this.ensureLocalSimulatedRemote({
|
|
339
|
-
remotePath: localRemotePath,
|
|
340
|
-
sourcePath: gitRoot,
|
|
341
|
-
baseBranch: options.baseBranch,
|
|
342
|
-
subdirectory: sourceRoot,
|
|
343
|
-
})
|
|
344
|
-
|
|
345
|
-
return localRemotePath
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
return undefined
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
/**
|
|
352
|
-
* Ensures a git workspace is initialized and ready for use.
|
|
353
|
-
* Handles cloning, remote configuration, and branch checkout/creation.
|
|
354
|
-
*
|
|
355
|
-
* This centralizes the common initialization sequence used by both BranchWorkspaceManager
|
|
356
|
-
* and SettingsWorkspaceManager.
|
|
357
|
-
*
|
|
358
|
-
* Note: Does NOT configure git author - that should be done before commits, not during init.
|
|
359
|
-
*
|
|
360
|
-
* @returns Configured GitManager instance for the workspace
|
|
361
|
-
*/
|
|
362
|
-
static async initializeWorkspace(options: InitializeWorkspaceOptions): Promise<GitManager> {
|
|
363
|
-
const baseBranch = options.baseBranch ?? 'main'
|
|
364
|
-
const remoteName = options.remoteName ?? 'origin'
|
|
365
|
-
|
|
366
|
-
// 1. Check if git already initialized
|
|
367
|
-
let repoExists = false
|
|
368
|
-
try {
|
|
369
|
-
const stat = await fs.stat(path.join(options.workspacePath, '.git'))
|
|
370
|
-
repoExists = stat.isDirectory()
|
|
371
|
-
} catch (err: unknown) {
|
|
372
|
-
if (!isNotFoundError(err)) throw err
|
|
373
|
-
// ENOENT means repo doesn't exist, continue to clone
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
// 2. Clone if needed
|
|
377
|
-
let justCloned = false
|
|
378
|
-
if (!repoExists) {
|
|
379
|
-
// Resolve remote URL only when we need to clone
|
|
380
|
-
const remoteUrl = await GitManager.resolveRemoteUrl({
|
|
381
|
-
mode: options.mode,
|
|
382
|
-
remoteUrl: options.remoteUrl,
|
|
383
|
-
defaultRemoteUrl: options.defaultRemoteUrl,
|
|
384
|
-
baseBranch,
|
|
385
|
-
sourceRoot: options.sourceRoot,
|
|
386
|
-
})
|
|
387
|
-
|
|
388
|
-
// Require remoteUrl for cloning
|
|
389
|
-
if (!remoteUrl) {
|
|
390
|
-
throw new Error(
|
|
391
|
-
'CanopyCMS: defaultRemoteUrl (or CANOPYCMS_REMOTE_URL) is required to initialize workspace',
|
|
392
|
-
)
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
// Clone repository (automatically configures 'origin' remote)
|
|
396
|
-
await GitManager.cloneRepo(remoteUrl, options.workspacePath, baseBranch)
|
|
397
|
-
justCloned = true
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
// 3. Create GitManager instance
|
|
401
|
-
const git = new GitManager({
|
|
402
|
-
repoPath: options.workspacePath,
|
|
403
|
-
baseBranch,
|
|
404
|
-
remote: remoteName,
|
|
405
|
-
})
|
|
406
|
-
|
|
407
|
-
// 4. Configure git remote only if we didn't just clone
|
|
408
|
-
// (clone already sets up the 'origin' remote)
|
|
409
|
-
if (!justCloned) {
|
|
410
|
-
const remoteUrl = await GitManager.resolveRemoteUrl({
|
|
411
|
-
mode: options.mode,
|
|
412
|
-
remoteUrl: options.remoteUrl,
|
|
413
|
-
defaultRemoteUrl: options.defaultRemoteUrl,
|
|
414
|
-
baseBranch,
|
|
415
|
-
sourceRoot: options.sourceRoot,
|
|
416
|
-
})
|
|
417
|
-
if (remoteUrl) {
|
|
418
|
-
await git.ensureRemote(remoteUrl)
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
// 5. Checkout or create branch based on type
|
|
423
|
-
if (options.branchType === 'orphan') {
|
|
424
|
-
await git.createOrphanSettingsBranch(options.branchName, {})
|
|
425
|
-
} else {
|
|
426
|
-
await git.checkoutBranch(options.branchName)
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
// 6. Mark this as a CanopyCMS-managed workspace
|
|
430
|
-
await git.git.addConfig('canopycms.managed', 'true')
|
|
431
|
-
log.debug('git', 'Marked workspace as CanopyCMS-managed', {
|
|
432
|
-
workspacePath: options.workspacePath,
|
|
433
|
-
})
|
|
434
|
-
|
|
435
|
-
return git
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
async status(): Promise<GitStatus> {
|
|
439
|
-
const s = await this.git.status()
|
|
440
|
-
return {
|
|
441
|
-
files: s.files,
|
|
442
|
-
ahead: s.ahead,
|
|
443
|
-
behind: s.behind,
|
|
444
|
-
current: s.current,
|
|
445
|
-
tracking: s.tracking,
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
async checkoutBranch(branch: string): Promise<void> {
|
|
450
|
-
const branches = await this.git.branch()
|
|
451
|
-
if (branches.all.includes(branch)) {
|
|
452
|
-
await this.git.checkout(branch)
|
|
453
|
-
return
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
const remoteRef = `${this.remote}/${this.baseBranch}`
|
|
457
|
-
try {
|
|
458
|
-
await this.git.fetch(this.remote, this.baseBranch)
|
|
459
|
-
} catch {
|
|
460
|
-
// Best-effort; will fall back to local base branch below if fetch fails
|
|
461
|
-
}
|
|
462
|
-
try {
|
|
463
|
-
await this.git.checkoutBranch(branch, remoteRef)
|
|
464
|
-
return
|
|
465
|
-
} catch {
|
|
466
|
-
const baseExists = branches.all.includes(this.baseBranch)
|
|
467
|
-
if (baseExists) {
|
|
468
|
-
await this.git.checkout(['-B', branch, this.baseBranch])
|
|
469
|
-
return
|
|
470
|
-
}
|
|
471
|
-
await this.git.checkoutLocalBranch(branch)
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
async pullBase(): Promise<void> {
|
|
476
|
-
await this.git.fetch(this.remote, this.baseBranch)
|
|
477
|
-
await this.git.merge([`${this.remote}/${this.baseBranch}`])
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
async pullCurrentBranch(): Promise<void> {
|
|
481
|
-
const branches = await this.git.branch()
|
|
482
|
-
const currentBranch = branches.current
|
|
483
|
-
await this.git.fetch(this.remote, currentBranch)
|
|
484
|
-
await this.git.merge([`${this.remote}/${currentBranch}`])
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
async rebaseOntoBase(): Promise<void> {
|
|
488
|
-
await this.git.fetch(this.remote, this.baseBranch)
|
|
489
|
-
await this.git.rebase([`${this.remote}/${this.baseBranch}`])
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
async add(files: string | string[]): Promise<void> {
|
|
493
|
-
const fileArray = Array.isArray(files) ? files : [files]
|
|
494
|
-
await this.git.add(fileArray)
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
async commit(message: string): Promise<void> {
|
|
498
|
-
await this.git.commit(message)
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
async push(branch?: string): Promise<void> {
|
|
502
|
-
const target = branch ?? (await this.git.revparse(['--abbrev-ref', 'HEAD']))
|
|
503
|
-
// Use explicit refspec (local:remote) so push works for new branches
|
|
504
|
-
// that don't yet exist in the remote (e.g., orphan settings branches).
|
|
505
|
-
await this.git.push(this.remote, `${target}:${target}`, ['--set-upstream'])
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
async ensureAuthor(author: { name: string; email: string }): Promise<void> {
|
|
509
|
-
const config = (await this.git.listConfig()) as ConfigListSummary
|
|
510
|
-
|
|
511
|
-
// Verify this is a CanopyCMS-managed workspace before setting author
|
|
512
|
-
const isManaged = config.all['canopycms.managed'] === 'true'
|
|
513
|
-
if (!isManaged) {
|
|
514
|
-
throw new Error(
|
|
515
|
-
`Cannot set git bot author in non-managed repository (${this.repoPath}). ` +
|
|
516
|
-
`Bot identity should only be set in CanopyCMS branch clones or test workspaces. ` +
|
|
517
|
-
`If this is a test workspace, add "git config canopycms.managed true" to mark it as managed.`,
|
|
518
|
-
)
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
// Set author identity
|
|
522
|
-
const currentName = config.all['user.name']
|
|
523
|
-
const currentEmail = config.all['user.email']
|
|
524
|
-
if (currentName !== author.name) {
|
|
525
|
-
await this.git.addConfig('user.name', author.name)
|
|
526
|
-
}
|
|
527
|
-
if (currentEmail !== author.email) {
|
|
528
|
-
await this.git.addConfig('user.email', author.email)
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
async ensureRemote(remoteUrl: string): Promise<void> {
|
|
533
|
-
const remotes = await this.git.getRemotes(true)
|
|
534
|
-
const existing = remotes.find((r) => r.name === this.remote)
|
|
535
|
-
if (!existing) {
|
|
536
|
-
await this.git.addRemote(this.remote, remoteUrl)
|
|
537
|
-
return
|
|
538
|
-
}
|
|
539
|
-
const currentUrl = existing.refs.push ?? existing.refs.fetch
|
|
540
|
-
if (currentUrl && currentUrl !== remoteUrl) {
|
|
541
|
-
await this.git.remote(['set-url', this.remote, remoteUrl])
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
/**
|
|
546
|
-
* Check if working directory has uncommitted changes
|
|
547
|
-
*/
|
|
548
|
-
async hasUncommittedChanges(): Promise<boolean> {
|
|
549
|
-
const status = await this.status()
|
|
550
|
-
return status.files.length > 0
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
/**
|
|
554
|
-
* Get list of uncommitted file paths
|
|
555
|
-
*/
|
|
556
|
-
async getUncommittedFiles(): Promise<string[]> {
|
|
557
|
-
const status = await this.status()
|
|
558
|
-
return status.files.map((f) => f.path)
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
/**
|
|
562
|
-
* Force push (use with caution - for PR updates only)
|
|
563
|
-
* Uses --force-with-lease for safer force pushes
|
|
564
|
-
*/
|
|
565
|
-
async forcePush(branch?: string): Promise<void> {
|
|
566
|
-
const target = branch ?? (await this.git.revparse(['--abbrev-ref', 'HEAD']))
|
|
567
|
-
await this.git.push(this.remote, target, ['--force-with-lease'])
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
/**
|
|
571
|
-
* Get remote URL for current repo
|
|
572
|
-
*/
|
|
573
|
-
async getRemoteUrl(): Promise<string | undefined> {
|
|
574
|
-
const remotes = await this.git.getRemotes(true)
|
|
575
|
-
const remote = remotes.find((r) => r.name === this.remote)
|
|
576
|
-
return remote?.refs.push || remote?.refs.fetch
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
/**
|
|
580
|
-
* Add a pattern to .git/info/exclude to prevent it from being committed/pushed.
|
|
581
|
-
* This is used to exclude .canopy-meta/ from content branch workspaces.
|
|
582
|
-
*
|
|
583
|
-
* .git/info/exclude is a per-repository gitignore that never gets committed.
|
|
584
|
-
* Perfect for runtime metadata that should never leave the workspace.
|
|
585
|
-
*
|
|
586
|
-
* This is idempotent - if the pattern already exists, it won't be added again.
|
|
587
|
-
*/
|
|
588
|
-
async ensureGitExclude(pattern: string): Promise<void> {
|
|
589
|
-
const excludePath = path.join(this.repoPath, '.git', 'info', 'exclude')
|
|
590
|
-
|
|
591
|
-
// Ensure .git/info directory exists
|
|
592
|
-
await fs.mkdir(path.dirname(excludePath), { recursive: true })
|
|
593
|
-
|
|
594
|
-
// Read existing exclude file (create if doesn't exist)
|
|
595
|
-
let content = ''
|
|
596
|
-
try {
|
|
597
|
-
content = await fs.readFile(excludePath, 'utf-8')
|
|
598
|
-
} catch (err: unknown) {
|
|
599
|
-
if (!isNotFoundError(err)) throw err
|
|
600
|
-
// File doesn't exist, will create it
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
// Check if pattern already exists (avoid duplicates)
|
|
604
|
-
const lines = content.split('\n')
|
|
605
|
-
if (lines.some((line) => line.trim() === pattern)) {
|
|
606
|
-
log.debug('git', 'Pattern already in .git/info/exclude', { pattern })
|
|
607
|
-
return
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
// Add pattern (with newline if file is not empty and doesn't end with one)
|
|
611
|
-
const needsLeadingNewline = content.length > 0 && !content.endsWith('\n')
|
|
612
|
-
const newContent = content + (needsLeadingNewline ? '\n' : '') + pattern + '\n'
|
|
613
|
-
|
|
614
|
-
await fs.writeFile(excludePath, newContent, 'utf-8')
|
|
615
|
-
log.debug('git', 'Added pattern to .git/info/exclude', { pattern })
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
/**
|
|
619
|
-
* Create an orphan branch for settings (permissions/groups).
|
|
620
|
-
*
|
|
621
|
-
* Orphan branches have no shared history with other branches - they start fresh.
|
|
622
|
-
* This is perfect for deployment-specific settings that shouldn't pollute content history.
|
|
623
|
-
*
|
|
624
|
-
* The branch contains only settings files in .canopy-meta/ (groups.json, permissions.json).
|
|
625
|
-
*
|
|
626
|
-
* @param branchName - Name of the orphan branch (e.g., 'canopycms-settings-prod')
|
|
627
|
-
* @param initialFiles - Files to commit to the new branch (e.g., { 'permissions.json': '{}', 'groups.json': '{}' })
|
|
628
|
-
*/
|
|
629
|
-
async createOrphanSettingsBranch(
|
|
630
|
-
branchName: string,
|
|
631
|
-
initialFiles: Record<string, string>,
|
|
632
|
-
): Promise<void> {
|
|
633
|
-
log.debug('git', 'Creating orphan settings branch', { branchName })
|
|
634
|
-
|
|
635
|
-
// Check if branch already exists
|
|
636
|
-
const branches = await this.git.branch()
|
|
637
|
-
if (branches.all.includes(branchName)) {
|
|
638
|
-
log.debug('git', 'Orphan branch already exists', { branchName })
|
|
639
|
-
// Checkout the existing branch
|
|
640
|
-
await this.git.checkout(branchName)
|
|
641
|
-
return
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
// Create orphan branch (--orphan creates a branch with no parent/history)
|
|
645
|
-
await this.git.raw(['checkout', '--orphan', branchName])
|
|
646
|
-
|
|
647
|
-
// Remove all files from index (orphan checkout keeps working tree)
|
|
648
|
-
try {
|
|
649
|
-
await this.git.raw(['rm', '-rf', '.'])
|
|
650
|
-
} catch {
|
|
651
|
-
// Ignore errors (might fail if index is already empty)
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
// Write initial files
|
|
655
|
-
for (const [filePath, content] of Object.entries(initialFiles)) {
|
|
656
|
-
const absolutePath = path.join(this.repoPath, filePath)
|
|
657
|
-
await fs.mkdir(path.dirname(absolutePath), { recursive: true })
|
|
658
|
-
await fs.writeFile(absolutePath, content, 'utf-8')
|
|
659
|
-
await this.git.add(filePath)
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
// Commit initial files
|
|
663
|
-
await this.git.commit('Initialize settings branch', ['--allow-empty'])
|
|
664
|
-
|
|
665
|
-
log.debug('git', 'Orphan settings branch created', { branchName })
|
|
666
|
-
}
|
|
667
|
-
}
|