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/content-id-index.ts
DELETED
|
@@ -1,479 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs/promises'
|
|
2
|
-
import path from 'node:path'
|
|
3
|
-
|
|
4
|
-
import { isValidId } from './id'
|
|
5
|
-
import { isNotFoundError } from './utils/error'
|
|
6
|
-
import { type LogicalPath, type PhysicalPath, type EntrySlug, type ContentId } from './paths'
|
|
7
|
-
|
|
8
|
-
/** Logical path representing entries stored at the branch root (no parent collection). Rare in practice. */
|
|
9
|
-
const EMPTY_LOGICAL_PATH = '' as LogicalPath
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Strips embedded IDs from each physical path segment to produce a logical path.
|
|
13
|
-
* e.g. "content/posts.a1b2c3d4e5f6" → "content/posts"
|
|
14
|
-
* This is a module-private helper for internal path conversion only.
|
|
15
|
-
*/
|
|
16
|
-
function toLogicalCollectionPath(physicalPath: string): LogicalPath {
|
|
17
|
-
if (physicalPath === '.') return EMPTY_LOGICAL_PATH
|
|
18
|
-
// extractSlugFromFilename strips the ID (and extension) from each segment
|
|
19
|
-
return physicalPath
|
|
20
|
-
.split('/')
|
|
21
|
-
.map((seg) => extractSlugFromFilename(seg))
|
|
22
|
-
.join('/') as LogicalPath
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface IdLocation {
|
|
26
|
-
id: ContentId
|
|
27
|
-
type: 'entry' | 'collection'
|
|
28
|
-
relativePath: PhysicalPath // e.g. 'content/posts/dune.a1b2c3d4e5f6.json'
|
|
29
|
-
collection?: LogicalPath // e.g. 'content/posts' (for entries only) — always logical, never physical
|
|
30
|
-
slug?: EntrySlug // e.g. 'dune' (for entries only)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* ContentIdIndex manages the bidirectional mapping between content IDs and file paths.
|
|
35
|
-
*
|
|
36
|
-
* IDs are embedded in filenames using the patterns:
|
|
37
|
-
* - Entries: `{type}.{slug}.{12-char-id}.{ext}` (e.g., `post.dune.a1b2c3d4e5f6.json`)
|
|
38
|
-
* - Collection directories: `{slug}.{12-char-id}/` (e.g., `posts.a1b2c3d4e5f6/`)
|
|
39
|
-
*
|
|
40
|
-
* This class builds an in-memory index by scanning filenames recursively, providing O(1) lookups
|
|
41
|
-
* in both directions (ID→path and path→ID).
|
|
42
|
-
*
|
|
43
|
-
* The index is built lazily on first access to optimize Lambda cold starts.
|
|
44
|
-
*
|
|
45
|
-
* ## Multi-Process Consistency
|
|
46
|
-
*
|
|
47
|
-
* This class is NOT thread-safe. In multi-process environments (e.g., multiple Lambda
|
|
48
|
-
* instances or server processes), each process maintains its own in-memory index.
|
|
49
|
-
*
|
|
50
|
-
* **Consistency guarantees:**
|
|
51
|
-
* - Filenames on the filesystem are the source of truth
|
|
52
|
-
* - Each process discovers the same filenames when building its index
|
|
53
|
-
* - Write operations that change filenames are atomic (rename is atomic)
|
|
54
|
-
* - Read operations always reflect current filesystem state after index rebuild
|
|
55
|
-
*
|
|
56
|
-
* **Race condition handling:**
|
|
57
|
-
* - Multiple processes creating entries simultaneously: Each generates a unique ID,
|
|
58
|
-
* collisions detected during index build (fail fast)
|
|
59
|
-
* - One process writes, another reads: Reader's stale index might miss new IDs until
|
|
60
|
-
* next rebuild. This is acceptable - eventual consistency.
|
|
61
|
-
* - Index drift: Rare, but processes can rebuild index if they detect missing IDs
|
|
62
|
-
*
|
|
63
|
-
* For most use cases (CMS with human editors), race conditions are unlikely and
|
|
64
|
-
* eventual consistency is sufficient.
|
|
65
|
-
*/
|
|
66
|
-
export class ContentIdIndex {
|
|
67
|
-
private idToLocation: Map<string, IdLocation> = new Map()
|
|
68
|
-
private pathToId: Map<string, string> = new Map()
|
|
69
|
-
private byCollection: Map<string, Set<string>> = new Map()
|
|
70
|
-
private root: string
|
|
71
|
-
|
|
72
|
-
constructor(root: string) {
|
|
73
|
-
this.root = path.resolve(root)
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Build index by scanning filenames recursively.
|
|
78
|
-
* Throws if duplicate IDs found (collision detection).
|
|
79
|
-
*/
|
|
80
|
-
async buildFromFilenames(startPath: string = ''): Promise<void> {
|
|
81
|
-
await this.scanDirectory(startPath)
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
private async scanDirectory(relativePath: string): Promise<void> {
|
|
85
|
-
const absoluteDir = path.join(this.root, relativePath)
|
|
86
|
-
|
|
87
|
-
try {
|
|
88
|
-
const entries = await fs.readdir(absoluteDir, { withFileTypes: true })
|
|
89
|
-
|
|
90
|
-
for (const entry of entries) {
|
|
91
|
-
// Skip hidden files and directories (including _ids_)
|
|
92
|
-
if (entry.name.startsWith('.') || entry.name === '_ids_') {
|
|
93
|
-
continue
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const fullRelativePath = path.join(relativePath, entry.name)
|
|
97
|
-
const id = extractIdFromFilename(entry.name)
|
|
98
|
-
|
|
99
|
-
if (id) {
|
|
100
|
-
// Collision detection
|
|
101
|
-
if (this.idToLocation.has(id)) {
|
|
102
|
-
const existing = this.idToLocation.get(id)!
|
|
103
|
-
throw new Error(
|
|
104
|
-
`ID collision detected: ${id}\n` +
|
|
105
|
-
` File 1: ${existing.relativePath}\n` +
|
|
106
|
-
` File 2: ${fullRelativePath}`,
|
|
107
|
-
)
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const location: IdLocation = {
|
|
111
|
-
id, // already ContentId from extractIdFromFilename
|
|
112
|
-
type: entry.isDirectory() ? 'collection' : 'entry',
|
|
113
|
-
relativePath: fullRelativePath as PhysicalPath, // filesystem path with embedded IDs
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Extract slug and collection for entries
|
|
117
|
-
if (!entry.isDirectory()) {
|
|
118
|
-
const slug = extractSlugFromFilename(entry.name)
|
|
119
|
-
// Convert physical collection path to logical by stripping embedded IDs from each segment
|
|
120
|
-
// e.g., "content/posts.a1b2c3d4e5f6" → "content/posts"
|
|
121
|
-
const physicalCollection = path.dirname(fullRelativePath)
|
|
122
|
-
const collectionPath = toLogicalCollectionPath(physicalCollection)
|
|
123
|
-
location.slug = slug as EntrySlug // slug extracted from validated filename
|
|
124
|
-
location.collection = collectionPath
|
|
125
|
-
|
|
126
|
-
// Add to collection index
|
|
127
|
-
if (!this.byCollection.has(collectionPath)) {
|
|
128
|
-
this.byCollection.set(collectionPath, new Set())
|
|
129
|
-
}
|
|
130
|
-
this.byCollection.get(collectionPath)!.add(id)
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
this.idToLocation.set(id, location)
|
|
134
|
-
this.pathToId.set(fullRelativePath, id)
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Recurse into directories
|
|
138
|
-
if (entry.isDirectory()) {
|
|
139
|
-
await this.scanDirectory(fullRelativePath)
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
} catch (err) {
|
|
143
|
-
// Directory might not exist yet
|
|
144
|
-
if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {
|
|
145
|
-
throw err
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Forward lookup: ID → location (O(1))
|
|
152
|
-
*/
|
|
153
|
-
findById(id: string): IdLocation | null {
|
|
154
|
-
return this.idToLocation.get(id) || null
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Reverse lookup: path → ID (O(1))
|
|
159
|
-
*/
|
|
160
|
-
findByPath(relativePath: PhysicalPath): ContentId | null {
|
|
161
|
-
return (this.pathToId.get(relativePath) as ContentId | undefined) || null
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Get all ID locations in the index.
|
|
166
|
-
* Useful for validation and checking references.
|
|
167
|
-
*/
|
|
168
|
-
getAllLocations(): IdLocation[] {
|
|
169
|
-
return Array.from(this.idToLocation.values())
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Get all entries in a collection by collection path.
|
|
174
|
-
*
|
|
175
|
-
* Performance: O(1) + O(m) where m is the number of entries in the collection.
|
|
176
|
-
*
|
|
177
|
-
* @param collectionPath - The collection path (e.g., "content/posts")
|
|
178
|
-
* @returns Array of IdLocation objects for entries in the collection
|
|
179
|
-
*/
|
|
180
|
-
getEntriesInCollection(collectionPath: LogicalPath): IdLocation[] {
|
|
181
|
-
const idSet = this.byCollection.get(collectionPath)
|
|
182
|
-
if (!idSet) {
|
|
183
|
-
return []
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const locations: IdLocation[] = []
|
|
187
|
-
for (const id of idSet) {
|
|
188
|
-
const location = this.idToLocation.get(id)
|
|
189
|
-
if (location) {
|
|
190
|
-
locations.push(location)
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
return locations
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Add a new entry or collection to the index.
|
|
199
|
-
* Note: This only updates the in-memory index. The file with embedded ID
|
|
200
|
-
* must already exist on disk (created by ContentStore).
|
|
201
|
-
*/
|
|
202
|
-
add(location: Omit<IdLocation, 'id'>): void {
|
|
203
|
-
const id = extractIdFromFilename(path.basename(location.relativePath))
|
|
204
|
-
if (!id) {
|
|
205
|
-
throw new Error(`Cannot add location without ID in filename: ${location.relativePath}`)
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Collision detection
|
|
209
|
-
if (this.idToLocation.has(id)) {
|
|
210
|
-
const existing = this.idToLocation.get(id)!
|
|
211
|
-
throw new Error(
|
|
212
|
-
`ID collision detected: ${id}\n` +
|
|
213
|
-
` File 1: ${existing.relativePath}\n` +
|
|
214
|
-
` File 2: ${location.relativePath}`,
|
|
215
|
-
)
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const fullLocation: IdLocation = {
|
|
219
|
-
...location,
|
|
220
|
-
id, // already ContentId from extractIdFromFilename
|
|
221
|
-
}
|
|
222
|
-
this.idToLocation.set(id, fullLocation)
|
|
223
|
-
this.pathToId.set(location.relativePath, id)
|
|
224
|
-
|
|
225
|
-
// Add to collection index if it's an entry
|
|
226
|
-
if (fullLocation.type === 'entry' && fullLocation.collection) {
|
|
227
|
-
if (!this.byCollection.has(fullLocation.collection)) {
|
|
228
|
-
this.byCollection.set(fullLocation.collection, new Set())
|
|
229
|
-
}
|
|
230
|
-
this.byCollection.get(fullLocation.collection)!.add(id)
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Remove an entry or collection from the index by ID.
|
|
236
|
-
* Note: This only updates the in-memory index. The file must be deleted separately.
|
|
237
|
-
*/
|
|
238
|
-
remove(id: ContentId): void {
|
|
239
|
-
const location = this.idToLocation.get(id)
|
|
240
|
-
if (!location) return
|
|
241
|
-
|
|
242
|
-
// Remove from collection index if it's an entry
|
|
243
|
-
if (location.type === 'entry' && location.collection) {
|
|
244
|
-
const idSet = this.byCollection.get(location.collection)
|
|
245
|
-
if (idSet) {
|
|
246
|
-
idSet.delete(id)
|
|
247
|
-
// Clean up empty Sets to prevent memory leaks
|
|
248
|
-
if (idSet.size === 0) {
|
|
249
|
-
this.byCollection.delete(location.collection)
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
this.idToLocation.delete(id)
|
|
255
|
-
this.pathToId.delete(location.relativePath)
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* Update the path for an existing ID (e.g., after file rename/move).
|
|
260
|
-
* This is used to keep the index in sync when files are renamed.
|
|
261
|
-
*/
|
|
262
|
-
updatePath(id: ContentId, newRelativePath: PhysicalPath): void {
|
|
263
|
-
const location = this.idToLocation.get(id)
|
|
264
|
-
if (!location) {
|
|
265
|
-
throw new Error(`Cannot update path for unknown ID: ${id}`)
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// Remove old path mapping
|
|
269
|
-
this.pathToId.delete(location.relativePath)
|
|
270
|
-
|
|
271
|
-
// Update location
|
|
272
|
-
location.relativePath = newRelativePath
|
|
273
|
-
|
|
274
|
-
// Update slug and collection for entries
|
|
275
|
-
if (location.type === 'entry') {
|
|
276
|
-
const oldCollection = location.collection
|
|
277
|
-
location.slug = extractSlugFromFilename(path.basename(newRelativePath)) as EntrySlug // from validated filename
|
|
278
|
-
const physicalCollection = path.dirname(newRelativePath)
|
|
279
|
-
location.collection = toLogicalCollectionPath(physicalCollection)
|
|
280
|
-
|
|
281
|
-
// Update collection index if collection changed
|
|
282
|
-
if (oldCollection !== location.collection) {
|
|
283
|
-
// Remove from old collection
|
|
284
|
-
if (oldCollection) {
|
|
285
|
-
const oldSet = this.byCollection.get(oldCollection)
|
|
286
|
-
if (oldSet) {
|
|
287
|
-
oldSet.delete(id)
|
|
288
|
-
if (oldSet.size === 0) {
|
|
289
|
-
this.byCollection.delete(oldCollection)
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// Add to new collection
|
|
295
|
-
if (location.collection) {
|
|
296
|
-
if (!this.byCollection.has(location.collection)) {
|
|
297
|
-
this.byCollection.set(location.collection, new Set())
|
|
298
|
-
}
|
|
299
|
-
this.byCollection.get(location.collection)!.add(id)
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// Add new path mapping
|
|
305
|
-
this.pathToId.set(newRelativePath, id)
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* Extract ID from filename.
|
|
311
|
-
* Returns null if filename doesn't contain an ID or is a metadata file.
|
|
312
|
-
*
|
|
313
|
-
* Pattern:
|
|
314
|
-
* - Collection entry files: type.slug.id.ext → ID is parts[parts.length - 2] (e.g., "post.dune.a1b2c3d4e5f6.json")
|
|
315
|
-
* - Collection directories: slug.id → ID is parts[1] (e.g., "posts.a1b2c3d4e5f6")
|
|
316
|
-
* - Metadata files: .collection.json, .gitignore, etc. → null
|
|
317
|
-
*
|
|
318
|
-
* Edge cases:
|
|
319
|
-
* - Slugs with dots: "post.my.page.a1b2c3d4e5f6.json" → extracts "a1b2c3d4e5f6"
|
|
320
|
-
* - Hidden files with IDs: ".hidden.a1b2c3d4e5f6.json" → returns null (metadata)
|
|
321
|
-
* - No ID present: "file.json" → returns null
|
|
322
|
-
*/
|
|
323
|
-
export function extractIdFromFilename(filename: string): ContentId | null {
|
|
324
|
-
// Skip metadata files (no IDs) - anything starting with dot is metadata
|
|
325
|
-
// This includes .collection.json, .gitignore, and even .hidden.id.json
|
|
326
|
-
if (filename.startsWith('.')) {
|
|
327
|
-
return null
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
const parts = filename.split('.')
|
|
331
|
-
|
|
332
|
-
// Files: type.slug.id.ext → need at least 3 parts
|
|
333
|
-
// The ID is always the second-to-last part before the extension
|
|
334
|
-
if (parts.length >= 3) {
|
|
335
|
-
const candidate = parts[parts.length - 2]
|
|
336
|
-
if (isValidId(candidate)) return candidate as ContentId
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// Directories: slug.id → exactly 2 parts (slug and ID, no extension)
|
|
340
|
-
if (parts.length === 2) {
|
|
341
|
-
const candidate = parts[parts.length - 1]
|
|
342
|
-
if (isValidId(candidate)) return candidate as ContentId
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
return null
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
/**
|
|
349
|
-
* Resolve a logical collection path to its actual filesystem path with embedded IDs.
|
|
350
|
-
* Recursively resolves each path segment to handle nested collections.
|
|
351
|
-
*
|
|
352
|
-
* Example:
|
|
353
|
-
* Input: resolveCollectionPath(root, "content/docs/api")
|
|
354
|
-
* Output: "/abs/path/to/content/docs.bChqT78gcaLd/api.meiuwxTSo7UN"
|
|
355
|
-
*
|
|
356
|
-
* @param root - Absolute path to the workspace root
|
|
357
|
-
* @param logicalPath - Logical path from schema (e.g., "content/docs/api")
|
|
358
|
-
* @returns Absolute filesystem path with embedded IDs, or null if path doesn't exist
|
|
359
|
-
*/
|
|
360
|
-
export async function resolveCollectionPath(
|
|
361
|
-
root: string,
|
|
362
|
-
logicalPath: LogicalPath,
|
|
363
|
-
): Promise<string | null> {
|
|
364
|
-
// Dynamic import: keep Node built-ins out of browser/edge bundles
|
|
365
|
-
const fs = await import('node:fs/promises')
|
|
366
|
-
const path = await import('node:path')
|
|
367
|
-
|
|
368
|
-
const segments = logicalPath.split('/').filter(Boolean)
|
|
369
|
-
let currentPath = root
|
|
370
|
-
|
|
371
|
-
for (const segment of segments) {
|
|
372
|
-
try {
|
|
373
|
-
const entries = await fs.readdir(currentPath, { withFileTypes: true })
|
|
374
|
-
const matchingDir = entries.find((entry) => {
|
|
375
|
-
if (!entry.isDirectory()) return false
|
|
376
|
-
// Extract logical name from directory (strips embedded ID)
|
|
377
|
-
const logicalName = extractSlugFromFilename(entry.name)
|
|
378
|
-
return logicalName === segment
|
|
379
|
-
})
|
|
380
|
-
|
|
381
|
-
if (matchingDir) {
|
|
382
|
-
currentPath = path.join(currentPath, matchingDir.name)
|
|
383
|
-
} else {
|
|
384
|
-
// Directory not found - might not exist yet
|
|
385
|
-
return null
|
|
386
|
-
}
|
|
387
|
-
} catch (err: unknown) {
|
|
388
|
-
if (isNotFoundError(err)) return null
|
|
389
|
-
throw err
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
return currentPath
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
/**
|
|
397
|
-
* Extract entry type name from filename.
|
|
398
|
-
* For collection entry files with pattern type.slug.id.ext, returns the type (first part).
|
|
399
|
-
*
|
|
400
|
-
* Examples:
|
|
401
|
-
* - "post.my-slug.a1b2c3d4e5f6.json" → "post"
|
|
402
|
-
* - "article.test.a1b2c3d4e5f6.md" → "article"
|
|
403
|
-
* - "posts.a1b2c3d4e5f6" → null (directory, not an entry file)
|
|
404
|
-
*
|
|
405
|
-
* @param filename - The filename to parse
|
|
406
|
-
* @returns Entry type name or null if not a valid entry file
|
|
407
|
-
*/
|
|
408
|
-
export function extractEntryTypeFromFilename(filename: string): string | null {
|
|
409
|
-
if (filename.startsWith('.')) return null
|
|
410
|
-
|
|
411
|
-
const parts = filename.split('.')
|
|
412
|
-
|
|
413
|
-
// Need at least 4 parts for type.slug.id.ext
|
|
414
|
-
if (parts.length >= 4) {
|
|
415
|
-
const possibleId = parts[parts.length - 2]
|
|
416
|
-
if (isValidId(possibleId)) {
|
|
417
|
-
return parts[0] // Entry type is first part
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
return null
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
/**
|
|
425
|
-
* Extract slug from filename.
|
|
426
|
-
*
|
|
427
|
-
* Collection entries: type.slug.id.ext → slug is parts[1...-2] (between type and ID)
|
|
428
|
-
* Directories: slug.id → slug is parts[0] (before ID)
|
|
429
|
-
*
|
|
430
|
-
* For collection entry files (4+ parts), automatically strips the first part (type) to extract just the slug.
|
|
431
|
-
*
|
|
432
|
-
* Examples:
|
|
433
|
-
* - "post.my-slug.a1b2c3d4e5f6.json" → "my-slug" (4 parts)
|
|
434
|
-
* - "post.my.page.a1b2c3d4e5f6.json" → "my.page" (dotted slug, 5 parts)
|
|
435
|
-
* - "posts.a1b2c3d4e5f6" → "posts" (directory, 2 parts)
|
|
436
|
-
*
|
|
437
|
-
* @param filename - The filename to parse
|
|
438
|
-
* @param entryTypeName - Optional entry type name for explicit type matching (e.g., "post")
|
|
439
|
-
* If provided and matches first part, strips it from slug
|
|
440
|
-
*/
|
|
441
|
-
export function extractSlugFromFilename(filename: string, entryTypeName?: string): string {
|
|
442
|
-
const parts = filename.split('.')
|
|
443
|
-
|
|
444
|
-
// Files: type.slug.id.ext (at least 3 parts)
|
|
445
|
-
if (parts.length >= 3) {
|
|
446
|
-
const possibleId = parts[parts.length - 2]
|
|
447
|
-
if (isValidId(possibleId)) {
|
|
448
|
-
// Get all parts before ID (excluding extension)
|
|
449
|
-
let slugParts = parts.slice(0, parts.length - 2)
|
|
450
|
-
|
|
451
|
-
// If entryTypeName is provided and matches the first part, strip it
|
|
452
|
-
if (entryTypeName && slugParts.length > 1 && slugParts[0] === entryTypeName) {
|
|
453
|
-
slugParts = slugParts.slice(1)
|
|
454
|
-
}
|
|
455
|
-
// Auto-detect: 4+ parts means type.slug.id.ext format
|
|
456
|
-
else if (parts.length >= 4 && slugParts.length > 1) {
|
|
457
|
-
// Strip first part (the type) to get just the slug
|
|
458
|
-
slugParts = slugParts.slice(1)
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
return slugParts.join('.')
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
// Directories: slug.id (exactly 2 parts)
|
|
466
|
-
if (parts.length === 2) {
|
|
467
|
-
const possibleId = parts[parts.length - 1]
|
|
468
|
-
if (isValidId(possibleId)) {
|
|
469
|
-
return parts[0]
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
// No ID found, remove extension and return filename without extension
|
|
474
|
-
if (parts.length > 1) {
|
|
475
|
-
return parts.slice(0, -1).join('.')
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
return filename
|
|
479
|
-
}
|