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/services.ts
DELETED
|
@@ -1,373 +0,0 @@
|
|
|
1
|
-
import type { CanopyConfig } from './config'
|
|
2
|
-
import type { EntrySchemaRegistry } from './schema/types'
|
|
3
|
-
import { getConfigDefaults } from './config'
|
|
4
|
-
import type { BranchContext } from './types'
|
|
5
|
-
import type { CanopyUser } from './user'
|
|
6
|
-
import {
|
|
7
|
-
createCheckPathAccess,
|
|
8
|
-
createCheckBranchAccess,
|
|
9
|
-
createCheckContentAccess,
|
|
10
|
-
loadPathPermissions,
|
|
11
|
-
} from './authorization'
|
|
12
|
-
import { GitManager } from './git-manager'
|
|
13
|
-
import { BranchRegistry } from './branch-registry'
|
|
14
|
-
import { SettingsWorkspaceManager } from './settings-workspace'
|
|
15
|
-
import { getDefaultBranchBase } from './paths'
|
|
16
|
-
import { createGitHubService, type GitHubService } from './github-service'
|
|
17
|
-
import { operatingStrategy } from './operating-mode'
|
|
18
|
-
import { BranchSchemaCache } from './branch-schema-cache'
|
|
19
|
-
import { enqueueTask } from './worker/task-queue'
|
|
20
|
-
import { getTaskQueueDir } from './worker/task-queue-config'
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Parse bootstrap admin IDs from environment variable.
|
|
24
|
-
* These users are always treated as Admins regardless of group membership.
|
|
25
|
-
*/
|
|
26
|
-
export const getBootstrapAdminIds = (): Set<string> => {
|
|
27
|
-
const envVar = process.env.CANOPY_BOOTSTRAP_ADMIN_IDS
|
|
28
|
-
if (!envVar) return new Set()
|
|
29
|
-
return new Set(
|
|
30
|
-
envVar
|
|
31
|
-
.split(',')
|
|
32
|
-
.map((id) => id.trim())
|
|
33
|
-
.filter(Boolean),
|
|
34
|
-
)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface CanopyServices {
|
|
38
|
-
config: CanopyConfig
|
|
39
|
-
/** Entry schema registry mapping entry schema names to field definitions */
|
|
40
|
-
entrySchemaRegistry: EntrySchemaRegistry
|
|
41
|
-
/** Per-branch schema cache */
|
|
42
|
-
branchSchemaCache: import('./branch-schema-cache').BranchSchemaCache
|
|
43
|
-
checkBranchAccess: (
|
|
44
|
-
context: BranchContext,
|
|
45
|
-
user: CanopyUser,
|
|
46
|
-
) => ReturnType<ReturnType<typeof createCheckBranchAccess>>
|
|
47
|
-
checkPathAccess: ReturnType<typeof createCheckPathAccess>
|
|
48
|
-
checkContentAccess: ReturnType<typeof createCheckContentAccess>
|
|
49
|
-
createGitManagerFor: (
|
|
50
|
-
repoPath: string,
|
|
51
|
-
opts?: { baseBranch?: string; remote?: string },
|
|
52
|
-
) => GitManager
|
|
53
|
-
registry?: BranchRegistry
|
|
54
|
-
githubService?: GitHubService
|
|
55
|
-
/** Bootstrap admin user IDs that are always treated as Admins */
|
|
56
|
-
bootstrapAdminIds: Set<string>
|
|
57
|
-
/** Commit files to git with automatic author handling */
|
|
58
|
-
commitFiles: (options: {
|
|
59
|
-
context: BranchContext
|
|
60
|
-
files: string | string[]
|
|
61
|
-
message: string
|
|
62
|
-
}) => Promise<void>
|
|
63
|
-
/** Submit branch: commit all changes and push to remote */
|
|
64
|
-
submitBranch: (options: { context: BranchContext; message?: string }) => Promise<void>
|
|
65
|
-
/** Commit to settings branch (for permissions/groups), with optional PR creation */
|
|
66
|
-
commitToSettingsBranch: (options: {
|
|
67
|
-
branchRoot: string
|
|
68
|
-
files: string | string[]
|
|
69
|
-
message: string
|
|
70
|
-
createPR?: boolean
|
|
71
|
-
}) => Promise<{
|
|
72
|
-
committed: boolean
|
|
73
|
-
pushed: boolean
|
|
74
|
-
prUrl?: string
|
|
75
|
-
error?: string
|
|
76
|
-
syncStatus?: 'pending-sync' | 'synced' | 'sync-failed'
|
|
77
|
-
}>
|
|
78
|
-
/** Get the root path for settings storage (ensures workspace exists) */
|
|
79
|
-
getSettingsBranchRoot: () => Promise<string>
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Options for createCanopyServices and createTestCanopyServices.
|
|
84
|
-
*/
|
|
85
|
-
export interface CreateCanopyServicesOptions {
|
|
86
|
-
/**
|
|
87
|
-
* Entry schema registry for resolving .collection.json references.
|
|
88
|
-
* Maps entry schema names to field definitions.
|
|
89
|
-
*/
|
|
90
|
-
entrySchemaRegistry?: EntrySchemaRegistry
|
|
91
|
-
/**
|
|
92
|
-
* Test-only: Custom branch schema cache.
|
|
93
|
-
* When provided, bypasses the default BranchSchemaCache creation.
|
|
94
|
-
* @internal
|
|
95
|
-
*/
|
|
96
|
-
branchSchemaCache?: BranchSchemaCache
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Create reusable helpers from a validated CanopyConfig.
|
|
101
|
-
* Intended to be called once at startup and injected where needed
|
|
102
|
-
* (e.g., request handlers, loaders).
|
|
103
|
-
*
|
|
104
|
-
* Schema is now loaded per-branch via BranchSchemaCache, not at startup.
|
|
105
|
-
*
|
|
106
|
-
* @param config - Validated Canopy configuration
|
|
107
|
-
* @param options - Optional settings including entry schema registry
|
|
108
|
-
*/
|
|
109
|
-
export const createCanopyServices = async (
|
|
110
|
-
config: CanopyConfig,
|
|
111
|
-
options: CreateCanopyServicesOptions = {},
|
|
112
|
-
): Promise<CanopyServices> => {
|
|
113
|
-
return _createCanopyServicesInternal(config, options)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Create services for testing.
|
|
118
|
-
* Schema is loaded per-branch via BranchSchemaCache.
|
|
119
|
-
*
|
|
120
|
-
* @param config - Validated Canopy configuration
|
|
121
|
-
* @param options - Optional settings including entry schema registry
|
|
122
|
-
*/
|
|
123
|
-
export const createTestCanopyServices = async (
|
|
124
|
-
config: CanopyConfig,
|
|
125
|
-
options: CreateCanopyServicesOptions = {},
|
|
126
|
-
): Promise<CanopyServices> => {
|
|
127
|
-
return _createCanopyServicesInternal(config, options)
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Internal implementation shared by both production and test functions.
|
|
132
|
-
* Not exported - use createCanopyServices() or createTestCanopyServices().
|
|
133
|
-
*/
|
|
134
|
-
async function _createCanopyServicesInternal(
|
|
135
|
-
config: CanopyConfig,
|
|
136
|
-
options: CreateCanopyServicesOptions,
|
|
137
|
-
): Promise<CanopyServices> {
|
|
138
|
-
// Validate mode-specific requirements (e.g., prod requires git bot credentials for GitHub)
|
|
139
|
-
const strategy = operatingStrategy(config.mode)
|
|
140
|
-
strategy.validateConfig(config)
|
|
141
|
-
|
|
142
|
-
// Load bootstrap admin IDs from environment
|
|
143
|
-
const bootstrapAdminIds = getBootstrapAdminIds()
|
|
144
|
-
|
|
145
|
-
// Create per-branch schema cache (or use provided one for testing)
|
|
146
|
-
const branchSchemaCache = options.branchSchemaCache ?? new BranchSchemaCache(config.mode)
|
|
147
|
-
|
|
148
|
-
const checkBranchAccess = createCheckBranchAccess(config.defaultBranchAccess ?? 'deny')
|
|
149
|
-
// Path permissions are loaded dynamically from settings branch or .canopy-dev/permissions.json at request time.
|
|
150
|
-
// At the service level, we bind with empty rules for direct path checks.
|
|
151
|
-
const checkPathAccess = createCheckPathAccess([], config.defaultPathAccess ?? 'deny')
|
|
152
|
-
// Content access loads permissions dynamically from settings root
|
|
153
|
-
// In prod/prod-sim modes, permissions are loaded from settings branch (orphan git branch)
|
|
154
|
-
// In dev mode, permissions are in .canopy-dev/settings/
|
|
155
|
-
const getSettingsBranchRoot = async (): Promise<string> => {
|
|
156
|
-
const strategy = operatingStrategy(config.mode)
|
|
157
|
-
|
|
158
|
-
// Only applicable in modes that use separate settings branch
|
|
159
|
-
if (!strategy.usesSeparateSettingsBranch()) {
|
|
160
|
-
throw new Error(
|
|
161
|
-
'getSettingsBranchRoot called in mode that does not use separate settings branch',
|
|
162
|
-
)
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const settingsRoot = strategy.getSettingsRoot()
|
|
166
|
-
const branchName = strategy.getSettingsBranchName(config)
|
|
167
|
-
|
|
168
|
-
// Use SettingsWorkspaceManager to ensure git workspace for settings
|
|
169
|
-
// This is Lambda-safe because the lock is in-memory per process
|
|
170
|
-
const manager = new SettingsWorkspaceManager(config)
|
|
171
|
-
await manager.ensureGitWorkspace({
|
|
172
|
-
settingsRoot,
|
|
173
|
-
branchName,
|
|
174
|
-
mode: config.mode,
|
|
175
|
-
remoteUrl: config.defaultRemoteUrl,
|
|
176
|
-
})
|
|
177
|
-
|
|
178
|
-
return settingsRoot
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const checkContentAccess = createCheckContentAccess({
|
|
182
|
-
checkBranchAccess,
|
|
183
|
-
loadPathPermissions,
|
|
184
|
-
defaultPathAccess: config.defaultPathAccess ?? 'deny',
|
|
185
|
-
mode: config.mode,
|
|
186
|
-
getSettingsBranchRoot,
|
|
187
|
-
})
|
|
188
|
-
const configDefaults = getConfigDefaults()
|
|
189
|
-
const createGitManagerFor = (repoPath: string, opts?: { baseBranch?: string; remote?: string }) =>
|
|
190
|
-
new GitManager({
|
|
191
|
-
repoPath,
|
|
192
|
-
baseBranch: opts?.baseBranch ?? config.defaultBaseBranch ?? configDefaults.baseBranch,
|
|
193
|
-
remote: opts?.remote ?? config.defaultRemoteName ?? configDefaults.remoteName,
|
|
194
|
-
})
|
|
195
|
-
|
|
196
|
-
const commitFiles = async (options: {
|
|
197
|
-
context: BranchContext
|
|
198
|
-
files: string | string[]
|
|
199
|
-
message: string
|
|
200
|
-
}): Promise<void> => {
|
|
201
|
-
const git = createGitManagerFor(options.context.branchRoot)
|
|
202
|
-
await git.ensureAuthor({
|
|
203
|
-
name: config.gitBotAuthorName,
|
|
204
|
-
email: config.gitBotAuthorEmail,
|
|
205
|
-
})
|
|
206
|
-
await git.add(options.files)
|
|
207
|
-
await git.commit(options.message)
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const submitBranch = async (options: {
|
|
211
|
-
context: BranchContext
|
|
212
|
-
message?: string
|
|
213
|
-
}): Promise<void> => {
|
|
214
|
-
const git = createGitManagerFor(options.context.branchRoot)
|
|
215
|
-
await git.ensureAuthor({
|
|
216
|
-
name: config.gitBotAuthorName,
|
|
217
|
-
email: config.gitBotAuthorEmail,
|
|
218
|
-
})
|
|
219
|
-
await git.checkoutBranch(options.context.branch.name)
|
|
220
|
-
const status = await git.status()
|
|
221
|
-
if (status.files.length > 0) {
|
|
222
|
-
await git.add('.')
|
|
223
|
-
await git.commit(options.message ?? `Submit ${options.context.branch.name}`)
|
|
224
|
-
await git.push(options.context.branch.name)
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// Create GitHub service if applicable (only for modes that support pull requests)
|
|
229
|
-
// Must be initialized before closures that reference it (commitToSettingsBranch)
|
|
230
|
-
let githubService: GitHubService | undefined
|
|
231
|
-
if (operatingStrategy(config.mode).supportsPullRequests()) {
|
|
232
|
-
const remoteUrl = config.defaultRemoteUrl
|
|
233
|
-
if (remoteUrl) {
|
|
234
|
-
try {
|
|
235
|
-
const service = createGitHubService(config, remoteUrl)
|
|
236
|
-
if (service) {
|
|
237
|
-
githubService = service
|
|
238
|
-
}
|
|
239
|
-
} catch (err) {
|
|
240
|
-
console.warn('CanopyCMS: Failed to initialize GitHub service:', err)
|
|
241
|
-
// Continue without GitHub integration
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
const commitToSettingsBranch = async (options: {
|
|
247
|
-
branchRoot: string
|
|
248
|
-
files: string | string[]
|
|
249
|
-
message: string
|
|
250
|
-
createPR?: boolean
|
|
251
|
-
}): Promise<{
|
|
252
|
-
committed: boolean
|
|
253
|
-
pushed: boolean
|
|
254
|
-
prUrl?: string
|
|
255
|
-
error?: string
|
|
256
|
-
syncStatus?: 'pending-sync' | 'synced' | 'sync-failed'
|
|
257
|
-
}> => {
|
|
258
|
-
const mode = config.mode
|
|
259
|
-
|
|
260
|
-
// Check if this mode supports git operations
|
|
261
|
-
if (!operatingStrategy(mode).shouldCommit()) {
|
|
262
|
-
return { committed: false, pushed: false }
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
const settingsBranch = operatingStrategy(config.mode).getSettingsBranchName(config)
|
|
266
|
-
const git = createGitManagerFor(options.branchRoot)
|
|
267
|
-
|
|
268
|
-
try {
|
|
269
|
-
// Pull latest changes from remote settings branch (not base branch!)
|
|
270
|
-
// Settings branches are orphan branches and should never merge from main
|
|
271
|
-
// Note: BranchWorkspaceManager already ensured we're on the settings branch
|
|
272
|
-
try {
|
|
273
|
-
await git.pullCurrentBranch()
|
|
274
|
-
} catch {
|
|
275
|
-
// First push, no remote branch yet, or no changes to pull
|
|
276
|
-
console.info('No remote settings branch changes to pull (this is normal for first commit)')
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// Commit
|
|
280
|
-
await git.ensureAuthor({
|
|
281
|
-
name: config.gitBotAuthorName,
|
|
282
|
-
email: config.gitBotAuthorEmail,
|
|
283
|
-
})
|
|
284
|
-
await git.add(options.files)
|
|
285
|
-
await git.commit(options.message)
|
|
286
|
-
|
|
287
|
-
// Push to local remote (remote.git on EFS in prod, origin in other modes)
|
|
288
|
-
try {
|
|
289
|
-
await git.push()
|
|
290
|
-
} catch (error) {
|
|
291
|
-
return {
|
|
292
|
-
committed: true,
|
|
293
|
-
pushed: false,
|
|
294
|
-
error: error instanceof Error ? error.message : 'Push failed',
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// Create or update PR — dual-path like content branches (api/github-sync.ts)
|
|
299
|
-
if (options.createPR !== false) {
|
|
300
|
-
// Direct path: githubService available (has internet)
|
|
301
|
-
if (githubService) {
|
|
302
|
-
let prUrl: string | undefined
|
|
303
|
-
try {
|
|
304
|
-
prUrl = await githubService.createOrUpdatePR({
|
|
305
|
-
head: settingsBranch,
|
|
306
|
-
base: config.defaultBaseBranch ?? 'main',
|
|
307
|
-
title: 'Update permissions and groups',
|
|
308
|
-
body: 'Automated PR for permission and group changes. Changes are already active in the CMS and will be persisted when this PR is merged.',
|
|
309
|
-
})
|
|
310
|
-
} catch (err) {
|
|
311
|
-
console.warn('Failed to create/update PR:', err)
|
|
312
|
-
return { committed: true, pushed: true, syncStatus: 'sync-failed' }
|
|
313
|
-
}
|
|
314
|
-
return { committed: true, pushed: true, prUrl, syncStatus: 'synced' }
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// Async path: queue task for worker (prod Lambda has no internet)
|
|
318
|
-
const taskDir = getTaskQueueDir(config)
|
|
319
|
-
if (taskDir) {
|
|
320
|
-
try {
|
|
321
|
-
await enqueueTask(taskDir, {
|
|
322
|
-
action: 'push-and-create-or-update-pr',
|
|
323
|
-
payload: {
|
|
324
|
-
branch: settingsBranch,
|
|
325
|
-
baseBranch: config.defaultBaseBranch ?? 'main',
|
|
326
|
-
title: 'Update permissions and groups',
|
|
327
|
-
body: 'Automated PR for permission and group changes. Changes are already active in the CMS and will be persisted when this PR is merged.',
|
|
328
|
-
},
|
|
329
|
-
})
|
|
330
|
-
return { committed: true, pushed: true, syncStatus: 'pending-sync' }
|
|
331
|
-
} catch (err) {
|
|
332
|
-
console.warn('Failed to enqueue settings PR task:', err)
|
|
333
|
-
return { committed: true, pushed: true, syncStatus: 'sync-failed' }
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
return { committed: true, pushed: true }
|
|
339
|
-
} catch (error) {
|
|
340
|
-
return {
|
|
341
|
-
committed: false,
|
|
342
|
-
pushed: false,
|
|
343
|
-
error: error instanceof Error ? error.message : 'Unknown error',
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
const operatingMode = config.mode
|
|
349
|
-
const modeStrategy = operatingStrategy(operatingMode)
|
|
350
|
-
|
|
351
|
-
// Create branch registry only in branching modes
|
|
352
|
-
// Settings are now in separate directory, no filtering needed
|
|
353
|
-
const registry = modeStrategy.supportsBranching()
|
|
354
|
-
? new BranchRegistry(getDefaultBranchBase(operatingMode))
|
|
355
|
-
: undefined
|
|
356
|
-
|
|
357
|
-
return {
|
|
358
|
-
config,
|
|
359
|
-
entrySchemaRegistry: options.entrySchemaRegistry ?? {},
|
|
360
|
-
branchSchemaCache,
|
|
361
|
-
checkBranchAccess,
|
|
362
|
-
checkPathAccess,
|
|
363
|
-
checkContentAccess,
|
|
364
|
-
createGitManagerFor,
|
|
365
|
-
registry,
|
|
366
|
-
githubService,
|
|
367
|
-
bootstrapAdminIds,
|
|
368
|
-
commitFiles,
|
|
369
|
-
submitBranch,
|
|
370
|
-
commitToSettingsBranch,
|
|
371
|
-
getSettingsBranchRoot,
|
|
372
|
-
}
|
|
373
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { operatingStrategy } from './operating-mode'
|
|
2
|
-
import type { OperatingMode } from './operating-mode'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Check if a branch name is the settings branch for the current configuration
|
|
6
|
-
*/
|
|
7
|
-
export const isSettingsBranch = (
|
|
8
|
-
branchName: string,
|
|
9
|
-
config: {
|
|
10
|
-
mode: OperatingMode
|
|
11
|
-
settingsBranch?: string
|
|
12
|
-
deploymentName?: string
|
|
13
|
-
defaultBaseBranch?: string
|
|
14
|
-
},
|
|
15
|
-
): boolean => {
|
|
16
|
-
const strategy = operatingStrategy(config.mode)
|
|
17
|
-
|
|
18
|
-
// Dev mode doesn't have separate settings branch
|
|
19
|
-
if (!strategy.usesSeparateSettingsBranch()) {
|
|
20
|
-
return false
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const settingsBranchName = strategy.getSettingsBranchName({
|
|
24
|
-
settingsBranch: config.settingsBranch,
|
|
25
|
-
deploymentName: config.deploymentName,
|
|
26
|
-
defaultBaseBranch: config.defaultBaseBranch,
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
return branchName === settingsBranchName
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Get the settings branch name for the current configuration
|
|
34
|
-
* Returns null if operating mode doesn't use separate settings branch (dev mode)
|
|
35
|
-
*/
|
|
36
|
-
export const getSettingsBranchName = (config: {
|
|
37
|
-
mode: OperatingMode
|
|
38
|
-
settingsBranch?: string
|
|
39
|
-
deploymentName?: string
|
|
40
|
-
defaultBaseBranch?: string
|
|
41
|
-
}): string | null => {
|
|
42
|
-
const strategy = operatingStrategy(config.mode)
|
|
43
|
-
|
|
44
|
-
if (!strategy.usesSeparateSettingsBranch()) {
|
|
45
|
-
return null
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return strategy.getSettingsBranchName({
|
|
49
|
-
settingsBranch: config.settingsBranch,
|
|
50
|
-
deploymentName: config.deploymentName,
|
|
51
|
-
defaultBaseBranch: config.defaultBaseBranch,
|
|
52
|
-
})
|
|
53
|
-
}
|
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs/promises'
|
|
2
|
-
import path from 'node:path'
|
|
3
|
-
import type { CanopyConfig } from './config'
|
|
4
|
-
import type { OperatingMode } from './operating-mode'
|
|
5
|
-
import { GitManager } from './git-manager'
|
|
6
|
-
import { createDebugLogger } from './utils/debug'
|
|
7
|
-
import { isFileExistsError } from './utils/error'
|
|
8
|
-
|
|
9
|
-
const log = createDebugLogger({ prefix: 'SettingsWorkspace' })
|
|
10
|
-
|
|
11
|
-
// In-memory lock to prevent concurrent workspace initialization within the same process.
|
|
12
|
-
// Settings only need one lock (not per-branch like content branches).
|
|
13
|
-
let settingsInitLock: Promise<void> | null = null
|
|
14
|
-
|
|
15
|
-
// Stale lock threshold — init should complete well within this window
|
|
16
|
-
const LOCK_STALE_MS = 30_000
|
|
17
|
-
|
|
18
|
-
export interface EnsureSettingsWorkspaceOptions {
|
|
19
|
-
settingsRoot: string
|
|
20
|
-
branchName: string
|
|
21
|
-
mode: OperatingMode
|
|
22
|
-
remoteUrl?: string
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Acquire a file-based lock for cross-process synchronization.
|
|
27
|
-
* Uses O_CREAT|O_EXCL (wx flag) for atomic file creation.
|
|
28
|
-
* Stale locks (older than LOCK_STALE_MS) are cleaned up automatically.
|
|
29
|
-
*
|
|
30
|
-
* Returns true if lock was acquired, false if another process holds it.
|
|
31
|
-
*/
|
|
32
|
-
async function acquireFileLock(lockPath: string): Promise<boolean> {
|
|
33
|
-
const lockContent = JSON.stringify({
|
|
34
|
-
pid: process.pid,
|
|
35
|
-
timestamp: new Date().toISOString(),
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
await fs.mkdir(path.dirname(lockPath), { recursive: true })
|
|
39
|
-
|
|
40
|
-
try {
|
|
41
|
-
const handle = await fs.open(lockPath, 'wx')
|
|
42
|
-
await handle.writeFile(lockContent, 'utf-8')
|
|
43
|
-
await handle.close()
|
|
44
|
-
return true
|
|
45
|
-
} catch (err) {
|
|
46
|
-
if (!isFileExistsError(err)) throw err
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Lock file exists — check if stale
|
|
50
|
-
try {
|
|
51
|
-
const stat = await fs.stat(lockPath)
|
|
52
|
-
const ageMs = Date.now() - stat.mtimeMs
|
|
53
|
-
if (ageMs < LOCK_STALE_MS) {
|
|
54
|
-
// Lock is fresh — another process is initializing
|
|
55
|
-
return false
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Stale lock — another process likely crashed during init
|
|
59
|
-
log.debug('workspace', 'Removing stale settings init lock', { ageMs })
|
|
60
|
-
await fs.unlink(lockPath).catch(() => {})
|
|
61
|
-
} catch {
|
|
62
|
-
// Lock file vanished between check and stat — try again
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Retry lock acquisition after stale cleanup
|
|
66
|
-
try {
|
|
67
|
-
const handle = await fs.open(lockPath, 'wx')
|
|
68
|
-
await handle.writeFile(lockContent, 'utf-8')
|
|
69
|
-
await handle.close()
|
|
70
|
-
return true
|
|
71
|
-
} catch (retryErr) {
|
|
72
|
-
if (isFileExistsError(retryErr)) return false
|
|
73
|
-
throw retryErr
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
async function releaseFileLock(lockPath: string): Promise<void> {
|
|
78
|
-
await fs.unlink(lockPath).catch(() => {})
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Manages settings filesystem workspace and git operations.
|
|
83
|
-
*
|
|
84
|
-
* Settings are stored separately from content branches:
|
|
85
|
-
* - prod/prod-sim: Orphan git branches (no shared history with content)
|
|
86
|
-
* - dev: Regular directory (no git)
|
|
87
|
-
*
|
|
88
|
-
* Unlike BranchWorkspaceManager, this does not:
|
|
89
|
-
* - Create or manage metadata files
|
|
90
|
-
* - Interact with the branch registry
|
|
91
|
-
* - Check for special cases (settings are always settings)
|
|
92
|
-
*
|
|
93
|
-
* Uses two layers of locking:
|
|
94
|
-
* - In-memory Promise lock for within-process serialization (Lambda request lifecycle)
|
|
95
|
-
* - File-based lock for cross-process synchronization (multiple Lambda instances on EFS)
|
|
96
|
-
*/
|
|
97
|
-
export class SettingsWorkspaceManager {
|
|
98
|
-
private readonly config: CanopyConfig
|
|
99
|
-
|
|
100
|
-
constructor(config: CanopyConfig) {
|
|
101
|
-
this.config = config
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
async ensureGitWorkspace(options: EnsureSettingsWorkspaceOptions): Promise<void> {
|
|
105
|
-
return log.timed('workspace', 'ensureGitWorkspace', async () => {
|
|
106
|
-
// Layer 1: In-memory lock (prevents redundant async calls within same process)
|
|
107
|
-
if (settingsInitLock) {
|
|
108
|
-
await settingsInitLock
|
|
109
|
-
return
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Create new in-memory lock promise
|
|
113
|
-
settingsInitLock = (async () => {
|
|
114
|
-
try {
|
|
115
|
-
log.debug('workspace', 'Ensuring settings git workspace', {
|
|
116
|
-
branchName: options.branchName,
|
|
117
|
-
mode: options.mode,
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
// Layer 2: File-based lock (prevents concurrent init across processes)
|
|
121
|
-
// Lock file is placed OUTSIDE the settings root (as a sibling) so that
|
|
122
|
-
// acquireFileLock's mkdir does not pre-create the settings directory,
|
|
123
|
-
// which would cause git clone to fail ("already exists and is not empty").
|
|
124
|
-
const lockPath = path.join(path.dirname(options.settingsRoot), '.settings-init.lock')
|
|
125
|
-
const acquired = await acquireFileLock(lockPath)
|
|
126
|
-
|
|
127
|
-
try {
|
|
128
|
-
// GitManager.initializeWorkspace is idempotent (checks for .git),
|
|
129
|
-
// so it's safe to call even if another process just finished init.
|
|
130
|
-
await GitManager.initializeWorkspace({
|
|
131
|
-
workspacePath: options.settingsRoot,
|
|
132
|
-
branchName: options.branchName,
|
|
133
|
-
mode: options.mode,
|
|
134
|
-
baseBranch: this.config.defaultBaseBranch,
|
|
135
|
-
sourceRoot: this.config.sourceRoot,
|
|
136
|
-
defaultRemoteUrl: this.config.defaultRemoteUrl,
|
|
137
|
-
remoteUrl: options.remoteUrl,
|
|
138
|
-
remoteName: this.config.defaultRemoteName,
|
|
139
|
-
branchType: 'orphan', // Key difference: orphan branch for settings
|
|
140
|
-
})
|
|
141
|
-
} finally {
|
|
142
|
-
if (acquired) {
|
|
143
|
-
await releaseFileLock(lockPath)
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
} finally {
|
|
147
|
-
// Always clean up the in-memory lock when done (success or failure)
|
|
148
|
-
settingsInitLock = null
|
|
149
|
-
}
|
|
150
|
-
})()
|
|
151
|
-
|
|
152
|
-
// Wait for initialization to complete
|
|
153
|
-
await settingsInitLock
|
|
154
|
-
})
|
|
155
|
-
}
|
|
156
|
-
}
|