@taskforcehq/taskforce 0.3.168
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/README.md +241 -0
- package/dist/Taskforce.d.ts +3 -0
- package/dist/Taskforce.js +3 -0
- package/dist/Taskforce.module.css +7587 -0
- package/dist/TaskforceCore.CategoryFallback.test.d.ts +1 -0
- package/dist/TaskforceCore.CategoryFallback.test.js +98 -0
- package/dist/TaskforceCore.d.ts +12 -0
- package/dist/TaskforceCore.js +729 -0
- package/dist/TaskforceCore.test.d.ts +1 -0
- package/dist/TaskforceCore.test.js +2132 -0
- package/dist/TaskforceScreenshot.d.ts +11 -0
- package/dist/TaskforceScreenshot.js +113 -0
- package/dist/TaskforceWidget.d.ts +6 -0
- package/dist/TaskforceWidget.js +101 -0
- package/dist/TaskforceWidget.test.d.ts +1 -0
- package/dist/TaskforceWidget.test.js +92 -0
- package/dist/UnsavedChangesRelaxed.test.d.ts +1 -0
- package/dist/UnsavedChangesRelaxed.test.js +16 -0
- package/dist/__tests__/localizationCatalogs.test.d.ts +1 -0
- package/dist/__tests__/localizationCatalogs.test.js +37 -0
- package/dist/__tests__/planParser.test.d.ts +1 -0
- package/dist/__tests__/planParser.test.js +183 -0
- package/dist/__tests__/releaseVersion.test.d.ts +1 -0
- package/dist/__tests__/releaseVersion.test.js +32 -0
- package/dist/__tests__/structuredCommentValidation.test.d.ts +1 -0
- package/dist/__tests__/structuredCommentValidation.test.js +31 -0
- package/dist/__tests__/structuredDocValidation.test.d.ts +1 -0
- package/dist/__tests__/structuredDocValidation.test.js +30 -0
- package/dist/__tests__/templateEngine.test.d.ts +1 -0
- package/dist/__tests__/templateEngine.test.js +150 -0
- package/dist/__tests__/templates.test.d.ts +1 -0
- package/dist/__tests__/templates.test.js +45 -0
- package/dist/__tests__/workflowDocumentFixtures.test.d.ts +1 -0
- package/dist/__tests__/workflowDocumentFixtures.test.js +78 -0
- package/dist/__tests__/workflowExportMatrix.test.d.ts +1 -0
- package/dist/__tests__/workflowExportMatrix.test.js +113 -0
- package/dist/__tests__/workflowExportSnapshots.test.d.ts +1 -0
- package/dist/__tests__/workflowExportSnapshots.test.js +28 -0
- package/dist/__tests__/workflowGeneration.test.d.ts +1 -0
- package/dist/__tests__/workflowGeneration.test.js +12 -0
- package/dist/__tests__/workflowSimulation.test.d.ts +1 -0
- package/dist/__tests__/workflowSimulation.test.js +113 -0
- package/dist/__tests__/workflowV2Policy.test.d.ts +1 -0
- package/dist/__tests__/workflowV2Policy.test.js +80 -0
- package/dist/api.d.ts +30 -0
- package/dist/api.js +85 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +217 -0
- package/dist/compat/priorityCompat.d.ts +2 -0
- package/dist/compat/priorityCompat.js +24 -0
- package/dist/components/features/DocumentGenerateModal.d.ts +9 -0
- package/dist/components/features/DocumentGenerateModal.js +114 -0
- package/dist/components/features/DocumentIndex.d.ts +19 -0
- package/dist/components/features/DocumentIndex.js +51 -0
- package/dist/components/features/DocumentViewer.d.ts +9 -0
- package/dist/components/features/DocumentViewer.js +253 -0
- package/dist/components/features/DocumentWorkspace.d.ts +21 -0
- package/dist/components/features/DocumentWorkspace.js +88 -0
- package/dist/components/features/TaskSettings.d.ts +209 -0
- package/dist/components/features/TaskSettings.js +500 -0
- package/dist/components/features/TaskSettings.test.d.ts +1 -0
- package/dist/components/features/TaskSettings.test.js +308 -0
- package/dist/components/features/settings/CategoryManager.d.ts +19 -0
- package/dist/components/features/settings/CategoryManager.js +113 -0
- package/dist/components/features/settings/TaxonomyEditor.d.ts +8 -0
- package/dist/components/features/settings/TaxonomyEditor.js +98 -0
- package/dist/components/features/settings/TaxonomyManager 2.d.ts +52 -0
- package/dist/components/features/settings/TaxonomyManager 2.js +75 -0
- package/dist/components/features/settings/TaxonomyManager.d.ts +54 -0
- package/dist/components/features/settings/TaxonomyManager.js +77 -0
- package/dist/components/features/settings/TypeManager.d.ts +10 -0
- package/dist/components/features/settings/TypeManager.js +50 -0
- package/dist/components/features/settings/WorkflowEditorPrototype.d.ts +15 -0
- package/dist/components/features/settings/WorkflowEditorPrototype.js +245 -0
- package/dist/components/task/TaskActionHeader.d.ts +20 -0
- package/dist/components/task/TaskActionHeader.js +13 -0
- package/dist/components/task/TaskCard.d.ts +47 -0
- package/dist/components/task/TaskCard.js +326 -0
- package/dist/components/task/TaskCard.test.d.ts +1 -0
- package/dist/components/task/TaskCard.test.js +95 -0
- package/dist/components/task/TaskContextUpload.d.ts +12 -0
- package/dist/components/task/TaskContextUpload.js +393 -0
- package/dist/components/task/TaskContextUpload.test.d.ts +1 -0
- package/dist/components/task/TaskContextUpload.test.js +189 -0
- package/dist/components/task/TaskForm.d.ts +105 -0
- package/dist/components/task/TaskForm.js +241 -0
- package/dist/components/task/TaskForm.test.d.ts +1 -0
- package/dist/components/task/TaskForm.test.js +288 -0
- package/dist/components/task/TaskKanban 2.d.ts +76 -0
- package/dist/components/task/TaskKanban 2.js +1004 -0
- package/dist/components/task/TaskKanban.d.ts +76 -0
- package/dist/components/task/TaskKanban.empty-column-drop.test.d.ts +1 -0
- package/dist/components/task/TaskKanban.empty-column-drop.test.js +66 -0
- package/dist/components/task/TaskKanban.js +1026 -0
- package/dist/components/task/TaskKanban.test.d.ts +1 -0
- package/dist/components/task/TaskKanban.test.js +46 -0
- package/dist/components/task/TaskList.d.ts +74 -0
- package/dist/components/task/TaskList.js +38 -0
- package/dist/components/task/TaskList.test.d.ts +1 -0
- package/dist/components/task/TaskList.test.js +270 -0
- package/dist/components/task/TaskScreenshotSection.d.ts +15 -0
- package/dist/components/task/TaskScreenshotSection.js +22 -0
- package/dist/components/ui/LevelSelector.d.ts +13 -0
- package/dist/components/ui/LevelSelector.js +25 -0
- package/dist/components/ui/LevelSelector.test.d.ts +1 -0
- package/dist/components/ui/LevelSelector.test.js +55 -0
- package/dist/components/ui/Markdown.d.ts +6 -0
- package/dist/components/ui/Markdown.js +94 -0
- package/dist/components/ui/Markdown.test.d.ts +1 -0
- package/dist/components/ui/Markdown.test.js +38 -0
- package/dist/components/ui/Modal.d.ts +16 -0
- package/dist/components/ui/Modal.js +49 -0
- package/dist/components/ui/MultiSelectFilter.d.ts +10 -0
- package/dist/components/ui/MultiSelectFilter.js +71 -0
- package/dist/components/ui/MultiSelectFilter.test.d.ts +1 -0
- package/dist/components/ui/MultiSelectFilter.test.js +74 -0
- package/dist/components/ui/TaxonomyDropdown.d.ts +17 -0
- package/dist/components/ui/TaxonomyDropdown.js +145 -0
- package/dist/components/ui/TaxonomyDropdown.test.d.ts +1 -0
- package/dist/components/ui/TaxonomyDropdown.test.js +130 -0
- package/dist/components/views/StandaloneLayout 2.d.ts +8 -0
- package/dist/components/views/StandaloneLayout 2.js +1660 -0
- package/dist/components/views/StandaloneLayout.d.ts +8 -0
- package/dist/components/views/StandaloneLayout.js +2117 -0
- package/dist/components/views/WidgetView.d.ts +8 -0
- package/dist/components/views/WidgetView.js +221 -0
- package/dist/config/envSchema.d.ts +28 -0
- package/dist/config/envSchema.js +374 -0
- package/dist/config/envSchema.test.d.ts +1 -0
- package/dist/config/envSchema.test.js +97 -0
- package/dist/config/surfaceBase.d.ts +2 -0
- package/dist/config/surfaceBase.js +8 -0
- package/dist/core/BulkUpdateFieldsPreview.test.d.ts +1 -0
- package/dist/core/BulkUpdateFieldsPreview.test.js +62 -0
- package/dist/core/CompletedAt.test.d.ts +1 -0
- package/dist/core/CompletedAt.test.js +44 -0
- package/dist/core/CreatedBy.test.d.ts +1 -0
- package/dist/core/CreatedBy.test.js +60 -0
- package/dist/core/EntitlementsPolicy.test.d.ts +1 -0
- package/dist/core/EntitlementsPolicy.test.js +145 -0
- package/dist/core/InitiativeTemplates.test.d.ts +1 -0
- package/dist/core/InitiativeTemplates.test.js +50 -0
- package/dist/core/ParentLifecycle.test.d.ts +1 -0
- package/dist/core/ParentLifecycle.test.js +98 -0
- package/dist/core/ScheduleFields.test.d.ts +1 -0
- package/dist/core/ScheduleFields.test.js +197 -0
- package/dist/core/SetupState.test.d.ts +1 -0
- package/dist/core/SetupState.test.js +113 -0
- package/dist/core/SystemAdmin.test.d.ts +1 -0
- package/dist/core/SystemAdmin.test.js +295 -0
- package/dist/core/TaskTaxonomyValidation.test.d.ts +1 -0
- package/dist/core/TaskTaxonomyValidation.test.js +64 -0
- package/dist/core/Taskforce.d.ts +1063 -0
- package/dist/core/Taskforce.ids.test.d.ts +1 -0
- package/dist/core/Taskforce.ids.test.js +168 -0
- package/dist/core/Taskforce.js +5981 -0
- package/dist/core/Taskforce.realtimeMutation.test.d.ts +1 -0
- package/dist/core/Taskforce.realtimeMutation.test.js +76 -0
- package/dist/core/WorkspaceDelete.test.d.ts +1 -0
- package/dist/core/WorkspaceDelete.test.js +140 -0
- package/dist/core/WorkspacePermissions.test.d.ts +1 -0
- package/dist/core/WorkspacePermissions.test.js +151 -0
- package/dist/core/WorkspacePlanMode.test.d.ts +1 -0
- package/dist/core/WorkspacePlanMode.test.js +104 -0
- package/dist/hooks/authGuardPolicy.d.ts +26 -0
- package/dist/hooks/authGuardPolicy.js +32 -0
- package/dist/hooks/authGuardPolicy.test.d.ts +1 -0
- package/dist/hooks/authGuardPolicy.test.js +76 -0
- package/dist/hooks/useDraggable.d.ts +12 -0
- package/dist/hooks/useDraggable.js +65 -0
- package/dist/hooks/useRealtimeSync.d.ts +24 -0
- package/dist/hooks/useRealtimeSync.js +275 -0
- package/dist/hooks/useRealtimeSync.test.d.ts +1 -0
- package/dist/hooks/useRealtimeSync.test.js +284 -0
- package/dist/hooks/useSyncOrchestrator.d.ts +164 -0
- package/dist/hooks/useSyncOrchestrator.js +1427 -0
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.d.ts +1 -0
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +367 -0
- package/dist/hooks/useTaskforce.d.ts +727 -0
- package/dist/hooks/useTaskforce.js +4396 -0
- package/dist/hooks/useTaskforce.priority-filters.test.d.ts +1 -0
- package/dist/hooks/useTaskforce.priority-filters.test.js +33 -0
- package/dist/hooks/useWorkspaceSyncController.d.ts +80 -0
- package/dist/hooks/useWorkspaceSyncController.js +598 -0
- package/dist/hooks/useWorkspaceSyncController.test.d.ts +1 -0
- package/dist/hooks/useWorkspaceSyncController.test.js +236 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +11 -0
- package/dist/localization/index.d.ts +8 -0
- package/dist/localization/index.js +81 -0
- package/dist/localization/locales/en-US.d.ts +151 -0
- package/dist/localization/locales/en-US.js +150 -0
- package/dist/localization/locales/es-419.d.ts +2 -0
- package/dist/localization/locales/es-419.js +150 -0
- package/dist/localization/locales/pt-BR.d.ts +2 -0
- package/dist/localization/locales/pt-BR.js +150 -0
- package/dist/main.d.ts +1 -0
- package/dist/main.js +33 -0
- package/dist/mcp/optionResolution.d.ts +14 -0
- package/dist/mcp/optionResolution.js +126 -0
- package/dist/mcp/optionResolution.test.d.ts +1 -0
- package/dist/mcp/optionResolution.test.js +39 -0
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.js +1793 -0
- package/dist/mcp/structuredCommentValidation.d.ts +6 -0
- package/dist/mcp/structuredCommentValidation.js +56 -0
- package/dist/mcp/structuredDocValidation.d.ts +6 -0
- package/dist/mcp/structuredDocValidation.js +75 -0
- package/dist/migrations/billingSchemaParity.test.d.ts +1 -0
- package/dist/migrations/billingSchemaParity.test.js +46 -0
- package/dist/migrations/clientMigrations.d.ts +9 -0
- package/dist/migrations/clientMigrations.js +74 -0
- package/dist/migrations/flags.d.ts +6 -0
- package/dist/migrations/flags.js +8 -0
- package/dist/migrations/taskMigrations.d.ts +71 -0
- package/dist/migrations/taskMigrations.js +236 -0
- package/dist/migrations/taskMigrations.test.d.ts +1 -0
- package/dist/migrations/taskMigrations.test.js +34 -0
- package/dist/migrations/taskSchemaMigrations.d.ts +11 -0
- package/dist/migrations/taskSchemaMigrations.js +453 -0
- package/dist/plugins/index.d.ts +10 -0
- package/dist/plugins/index.js +10 -0
- package/dist/plugins/vite.d.ts +19 -0
- package/dist/plugins/vite.js +271 -0
- package/dist/public/android-chrome-192x192.png +0 -0
- package/dist/public/android-chrome-512x512.png +0 -0
- package/dist/public/apple-touch-icon.png +0 -0
- package/dist/public/favicon-16x16.png +0 -0
- package/dist/public/favicon-32x32.png +0 -0
- package/dist/public/favicon.ico +0 -0
- package/dist/public/favicon.png +0 -0
- package/dist/public/site.webmanifest +1 -0
- package/dist/public/taskforce.png +0 -0
- package/dist/resources/templates/WORKFLOW_V2_POLICY.md +55 -0
- package/dist/resources/templates/environments/aider.yaml +14 -0
- package/dist/resources/templates/environments/antigravity.yaml +10 -0
- package/dist/resources/templates/environments/claude.yaml +10 -0
- package/dist/resources/templates/environments/cline.yaml +10 -0
- package/dist/resources/templates/environments/codex.yaml +13 -0
- package/dist/resources/templates/environments/copilot.yaml +10 -0
- package/dist/resources/templates/environments/cursor.yaml +9 -0
- package/dist/resources/templates/environments/gemini.yaml +9 -0
- package/dist/resources/templates/environments/windsurf.yaml +10 -0
- package/dist/resources/templates/workflow-sources/README.md +9 -0
- package/dist/resources/templates/workflow-sources/workflowDocs.mjs +520 -0
- package/dist/resources/templates/workflow-sources/workflowEditorOverrides.json +1 -0
- package/dist/resources/templates/workflows/collaborate.yaml +104 -0
- package/dist/resources/templates/workflows/do.yaml +82 -0
- package/dist/resources/templates/workflows/evaluate.yaml +111 -0
- package/dist/resources/templates/workflows/execute.yaml +142 -0
- package/dist/resources/templates/workflows/plan.yaml +112 -0
- package/dist/resources/templates/workflows/review.yaml +109 -0
- package/dist/runtime/capabilities.d.ts +8 -0
- package/dist/runtime/capabilities.js +16 -0
- package/dist/security/virusScan.d.ts +16 -0
- package/dist/security/virusScan.js +83 -0
- package/dist/server/auth.d.ts +51 -0
- package/dist/server/auth.js +266 -0
- package/dist/server/auth.test.d.ts +1 -0
- package/dist/server/auth.test.js +111 -0
- package/dist/server/buildInfo.d.ts +7 -0
- package/dist/server/buildInfo.js +100 -0
- package/dist/server/buildInfo.test.d.ts +1 -0
- package/dist/server/buildInfo.test.js +29 -0
- package/dist/server/cors.d.ts +7 -0
- package/dist/server/cors.js +76 -0
- package/dist/server/cors.test.d.ts +1 -0
- package/dist/server/cors.test.js +71 -0
- package/dist/server/email.d.ts +28 -0
- package/dist/server/email.js +138 -0
- package/dist/server/email.test.d.ts +1 -0
- package/dist/server/email.test.js +113 -0
- package/dist/server/index.cookieProxy.test.d.ts +1 -0
- package/dist/server/index.cookieProxy.test.js +19 -0
- package/dist/server/index.d.ts +17 -0
- package/dist/server/index.js +757 -0
- package/dist/server/index.rateLimit.test.d.ts +1 -0
- package/dist/server/index.rateLimit.test.js +13 -0
- package/dist/server/index.test.d.ts +1 -0
- package/dist/server/index.test.js +19 -0
- package/dist/server/rateLimit.d.ts +29 -0
- package/dist/server/rateLimit.js +99 -0
- package/dist/server/rateLimit.test.d.ts +1 -0
- package/dist/server/rateLimit.test.js +107 -0
- package/dist/server/realtimeSync.auth.test.d.ts +1 -0
- package/dist/server/realtimeSync.auth.test.js +62 -0
- package/dist/server/realtimeSync.replay.test.d.ts +1 -0
- package/dist/server/realtimeSync.replay.test.js +192 -0
- package/dist/server/realtimeSync.workspaceIsolation.test.d.ts +1 -0
- package/dist/server/realtimeSync.workspaceIsolation.test.js +101 -0
- package/dist/server/realtimeSyncWs.d.ts +30 -0
- package/dist/server/realtimeSyncWs.js +257 -0
- package/dist/server/routes.billing.d.ts +15 -0
- package/dist/server/routes.billing.js +991 -0
- package/dist/server/routes.billing.test.d.ts +1 -0
- package/dist/server/routes.billing.test.js +387 -0
- package/dist/server/routes.d.ts +72 -0
- package/dist/server/routes.js +4820 -0
- package/dist/server/routes.sync.d.ts +17 -0
- package/dist/server/routes.sync.integration.test.d.ts +1 -0
- package/dist/server/routes.sync.integration.test.js +470 -0
- package/dist/server/routes.sync.js +1141 -0
- package/dist/server/routes.test.d.ts +1 -0
- package/dist/server/routes.test.js +4945 -0
- package/dist/server/runtimeMode.d.ts +4 -0
- package/dist/server/runtimeMode.js +25 -0
- package/dist/server/runtimeMode.test.d.ts +1 -0
- package/dist/server/runtimeMode.test.js +26 -0
- package/dist/server/securityHeaders.d.ts +3 -0
- package/dist/server/securityHeaders.js +25 -0
- package/dist/server/securityHeaders.test.d.ts +1 -0
- package/dist/server/securityHeaders.test.js +32 -0
- package/dist/server/smtpTransport.d.ts +21 -0
- package/dist/server/smtpTransport.js +11 -0
- package/dist/server/start.d.ts +1 -0
- package/dist/server/start.js +17 -0
- package/dist/services/formatTransformers.d.ts +29 -0
- package/dist/services/formatTransformers.js +56 -0
- package/dist/services/templateEngine.d.ts +46 -0
- package/dist/services/templateEngine.js +148 -0
- package/dist/shared/runtimeContract.d.ts +12 -0
- package/dist/shared/runtimeContract.js +23 -0
- package/dist/shared/runtimeContract.test.d.ts +1 -0
- package/dist/shared/runtimeContract.test.js +23 -0
- package/dist/soak/preflight.cli.d.ts +1 -0
- package/dist/soak/preflight.cli.js +28 -0
- package/dist/soak/preflight.d.ts +26 -0
- package/dist/soak/preflight.js +113 -0
- package/dist/soak/preflight.test.d.ts +1 -0
- package/dist/soak/preflight.test.js +39 -0
- package/dist/soak/syncSoakHarness.d.ts +6 -0
- package/dist/soak/syncSoakHarness.js +10 -0
- package/dist/storage/createDatabaseAdapter.d.ts +2 -0
- package/dist/storage/createDatabaseAdapter.js +9 -0
- package/dist/storage/databaseAdapter.d.ts +22 -0
- package/dist/storage/databaseAdapter.js +1 -0
- package/dist/storage/documentIntegrity.d.ts +28 -0
- package/dist/storage/documentIntegrity.js +103 -0
- package/dist/storage/documentPurge.d.ts +14 -0
- package/dist/storage/documentPurge.js +46 -0
- package/dist/storage/documentRegistry.d.ts +39 -0
- package/dist/storage/documentRegistry.js +115 -0
- package/dist/storage/integritySchedule.d.ts +17 -0
- package/dist/storage/integritySchedule.js +62 -0
- package/dist/storage/objectStorage.d.ts +20 -0
- package/dist/storage/objectStorage.js +15 -0
- package/dist/storage/objectStorageClient.d.ts +18 -0
- package/dist/storage/objectStorageClient.js +301 -0
- package/dist/storage/objectStorageConfig.d.ts +2 -0
- package/dist/storage/objectStorageConfig.js +45 -0
- package/dist/storage/postgresAdapter.d.ts +20 -0
- package/dist/storage/postgresAdapter.js +204 -0
- package/dist/storage/postgresWorker.d.ts +1 -0
- package/dist/storage/postgresWorker.js +110 -0
- package/dist/storage/providerConfig.d.ts +6 -0
- package/dist/storage/providerConfig.js +11 -0
- package/dist/storage/sqliteAdapter.d.ts +14 -0
- package/dist/storage/sqliteAdapter.js +28 -0
- package/dist/storage/taskAssetStore.d.ts +56 -0
- package/dist/storage/taskAssetStore.js +297 -0
- package/dist/sync/cloudSyncApi.d.ts +47 -0
- package/dist/sync/cloudSyncApi.js +138 -0
- package/dist/sync/cloudSyncApi.test.d.ts +1 -0
- package/dist/sync/cloudSyncApi.test.js +54 -0
- package/dist/sync/encryptionService.d.ts +11 -0
- package/dist/sync/encryptionService.js +61 -0
- package/dist/sync/encryptionService.test.d.ts +1 -0
- package/dist/sync/encryptionService.test.js +52 -0
- package/dist/sync/realtimeFeature.d.ts +6 -0
- package/dist/sync/realtimeFeature.js +20 -0
- package/dist/sync/realtimeFeature.test.d.ts +1 -0
- package/dist/sync/realtimeFeature.test.js +30 -0
- package/dist/sync/realtimeReconcile.d.ts +30 -0
- package/dist/sync/realtimeReconcile.js +90 -0
- package/dist/sync/realtimeReconcile.test.d.ts +1 -0
- package/dist/sync/realtimeReconcile.test.js +43 -0
- package/dist/sync/syncService.d.ts +96 -0
- package/dist/sync/syncService.js +423 -0
- package/dist/sync/syncService.test.d.ts +1 -0
- package/dist/sync/syncService.test.js +484 -0
- package/dist/sync/workspaceSyncModel.d.ts +61 -0
- package/dist/sync/workspaceSyncModel.js +136 -0
- package/dist/sync/workspaceSyncModel.test.d.ts +1 -0
- package/dist/sync/workspaceSyncModel.test.js +73 -0
- package/dist/test/setup.d.ts +1 -0
- package/dist/test/setup.js +15 -0
- package/dist/types.d.ts +157 -0
- package/dist/types.js +41 -0
- package/dist/ui/assets/RussoOne-Regular-C3BxZIj7.ttf +0 -0
- package/dist/ui/assets/index-0W5vyCpn.css +1 -0
- package/dist/ui/assets/index-MCZuNNfn.js +23 -0
- package/dist/ui/assets/logo_white_01-0Vx-iIIv.png +0 -0
- package/dist/ui/assets/taskforce-BxLPokNB.png +0 -0
- package/dist/ui/assets/vendor-dnd-BI1K77C1.js +5 -0
- package/dist/ui/assets/vendor-icons-ZFG3SGwo.js +1 -0
- package/dist/ui/assets/vendor-markdown-CKYkQHfj.js +29 -0
- package/dist/ui/assets/vendor-react-DdIDsh7f.js +9 -0
- package/dist/ui/assets/vendor-router-BtiNU7Dl.js +3 -0
- package/dist/ui/branding/logos/logo_black_01.png +0 -0
- package/dist/ui/branding/logos/logo_black_02.png +0 -0
- package/dist/ui/branding/logos/logo_white_01.png +0 -0
- package/dist/ui/branding/logos/logo_white_02.png +0 -0
- package/dist/ui/favicon/android-chrome-192x192.png +0 -0
- package/dist/ui/favicon/android-chrome-512x512.png +0 -0
- package/dist/ui/favicon/apple-touch-icon.png +0 -0
- package/dist/ui/favicon/favicon-16x16.png +0 -0
- package/dist/ui/favicon/favicon-32x32.png +0 -0
- package/dist/ui/favicon/favicon.ico +0 -0
- package/dist/ui/favicon/favicon.png +0 -0
- package/dist/ui/favicon/site.webmanifest +1 -0
- package/dist/ui/fonts/RussoOne-Regular.ttf +0 -0
- package/dist/ui/images/taskforce.png +0 -0
- package/dist/ui/index.html +19 -0
- package/dist/utils/checklistMarkdown.d.ts +9 -0
- package/dist/utils/checklistMarkdown.js +46 -0
- package/dist/utils/constants.d.ts +21 -0
- package/dist/utils/constants.icons.test.d.ts +1 -0
- package/dist/utils/constants.icons.test.js +40 -0
- package/dist/utils/constants.js +100 -0
- package/dist/utils/contextFiles.d.ts +8 -0
- package/dist/utils/contextFiles.js +54 -0
- package/dist/utils/formatting.d.ts +5 -0
- package/dist/utils/formatting.js +30 -0
- package/dist/utils/planParser.d.ts +22 -0
- package/dist/utils/planParser.js +151 -0
- package/dist/utils/taskDragZones.d.ts +8 -0
- package/dist/utils/taskDragZones.js +14 -0
- package/dist/utils/taskDragZones.test.d.ts +1 -0
- package/dist/utils/taskDragZones.test.js +14 -0
- package/dist/utils/taskHierarchy.d.ts +4 -0
- package/dist/utils/taskHierarchy.js +69 -0
- package/dist/utils/taskHierarchy.test.d.ts +1 -0
- package/dist/utils/taskHierarchy.test.js +38 -0
- package/dist/utils/taskParenting.d.ts +3 -0
- package/dist/utils/taskParenting.js +24 -0
- package/dist/utils/taskParenting.test.d.ts +1 -0
- package/dist/utils/taskParenting.test.js +21 -0
- package/dist/utils/taskRelations.d.ts +6 -0
- package/dist/utils/taskRelations.js +24 -0
- package/dist/utils/taskRelations.test.d.ts +1 -0
- package/dist/utils/taskRelations.test.js +34 -0
- package/dist/utils/taskUtils.d.ts +2 -0
- package/dist/utils/taskUtils.js +20 -0
- package/dist/utils/taskforceApiClient.d.ts +7 -0
- package/dist/utils/taskforceApiClient.js +42 -0
- package/dist/utils/workspaceIdentity.d.ts +8 -0
- package/dist/utils/workspaceIdentity.js +47 -0
- package/dist/utils/workspaceIdentity.test.d.ts +1 -0
- package/dist/utils/workspaceIdentity.test.js +32 -0
- package/dist/utils/workspaceLifecycle.d.ts +26 -0
- package/dist/utils/workspaceLifecycle.js +69 -0
- package/dist/utils/workspaceLifecycle.test.d.ts +1 -0
- package/dist/utils/workspaceLifecycle.test.js +64 -0
- package/package.json +133 -0
- package/scripts/check-compat-boundaries.js +48 -0
- package/scripts/check-env.ts +24 -0
- package/scripts/check-localization-strings.mjs +49 -0
- package/scripts/check-runtime-boundaries.mjs +44 -0
- package/scripts/debug-global.mjs +34 -0
- package/scripts/debug-server.mjs +36 -0
- package/scripts/export-sqlite-to-postgres.mjs +125 -0
- package/scripts/generate-env-example.ts +8 -0
- package/scripts/generate-workflow-templates.mjs +134 -0
- package/scripts/mcp-stdio-adapter.mjs +160 -0
- package/scripts/postinstall.js +60 -0
- package/scripts/release-version-lib.mjs +88 -0
- package/scripts/release-version.mjs +72 -0
- package/scripts/run-dev-proxy.sh +49 -0
- package/scripts/run-e2e-realtime.sh +16 -0
- package/scripts/run-smoke.sh +23 -0
- package/scripts/run-soak.sh +29 -0
- package/scripts/test-global.js +143 -0
- package/scripts/update-homebrew-formula.sh +38 -0
- package/src/resources/templates/WORKFLOW_V2_POLICY.md +55 -0
- package/src/resources/templates/environments/aider.yaml +14 -0
- package/src/resources/templates/environments/antigravity.yaml +10 -0
- package/src/resources/templates/environments/claude.yaml +10 -0
- package/src/resources/templates/environments/cline.yaml +10 -0
- package/src/resources/templates/environments/codex.yaml +13 -0
- package/src/resources/templates/environments/copilot.yaml +10 -0
- package/src/resources/templates/environments/cursor.yaml +9 -0
- package/src/resources/templates/environments/gemini.yaml +9 -0
- package/src/resources/templates/environments/windsurf.yaml +10 -0
- package/src/resources/templates/workflow-sources/README.md +9 -0
- package/src/resources/templates/workflow-sources/workflowDocs.mjs +520 -0
- package/src/resources/templates/workflow-sources/workflowEditorOverrides.json +1 -0
- package/src/resources/templates/workflows/collaborate.yaml +104 -0
- package/src/resources/templates/workflows/do.yaml +82 -0
- package/src/resources/templates/workflows/evaluate.yaml +111 -0
- package/src/resources/templates/workflows/execute.yaml +142 -0
- package/src/resources/templates/workflows/plan.yaml +112 -0
- package/src/resources/templates/workflows/review.yaml +109 -0
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
const SPECS = [
|
|
2
|
+
{ key: 'PORT', description: 'Standalone server port.', category: 'Core', defaultValue: '8080', targets: ['server', 'all'] },
|
|
3
|
+
{ key: 'TASKFORCE_TENANT_ID', description: 'Tenant identifier.', category: 'Core', defaultValue: 'default', targets: ['server', 'all'] },
|
|
4
|
+
{ key: 'TASKFORCE_WORKSPACE_ID', description: 'Optional explicit workspace scope for MCP/server tooling.', category: 'Core', targets: ['server', 'all'] },
|
|
5
|
+
{ key: 'TASKFORCE_RUNTIME_MODE', description: 'Runtime mode (local|cloud).', category: 'Core', defaultValue: 'local', example: 'cloud', targets: ['server', 'all'] },
|
|
6
|
+
{ key: 'TASKFORCE_DB_PROVIDER', description: 'Database provider (sqlite|postgres).', category: 'Core', defaultValue: 'sqlite', example: 'postgres', targets: ['server', 'all'] },
|
|
7
|
+
{ key: 'TASKFORCE_BILLING_ENABLED', description: 'Enable Stripe billing routes and webhook processing.', category: 'Core', defaultValue: 'false', targets: ['server', 'all'] },
|
|
8
|
+
{ key: 'TASKFORCE_DATABASE_URL', description: 'Postgres connection string when provider=postgres.', category: 'Core', example: 'postgresql://user:pass@host:5432/db', secret: true, targets: ['server', 'all'] },
|
|
9
|
+
{ key: 'TASKFORCE_ALLOW_SQLITE_IN_CLOUD', description: 'Emergency override to allow sqlite in cloud runtime.', category: 'Core', defaultValue: 'false', targets: ['server', 'all'] },
|
|
10
|
+
{ key: 'TASKFORCE_FORCE_SETUP', description: 'Force local setup gate for development.', category: 'Core', defaultValue: 'false', targets: ['server', 'all'] },
|
|
11
|
+
{ key: 'TASKFORCE_FORCE_GLOBAL_SETUP', description: 'Force global setup step for development.', category: 'Core', defaultValue: 'false', targets: ['server', 'all'] },
|
|
12
|
+
{ key: 'TASKFORCE_FORCE_WORKSPACE_SETUP', description: 'Force workspace setup step for development.', category: 'Core', defaultValue: 'false', targets: ['server', 'all'] },
|
|
13
|
+
{ key: 'TASKFORCE_GIT_BACKUP', description: 'Enable git backup persistence behavior in local tooling.', category: 'Core', defaultValue: 'false', targets: ['server', 'all'] },
|
|
14
|
+
{ key: 'TASKFORCE_REALTIME_SYNC', description: 'Enable realtime sync transport layer (feature-flag).', category: 'Core', defaultValue: 'false', targets: ['server', 'all'] },
|
|
15
|
+
{ key: 'TASKFORCE_AUTH_ENABLED', description: 'Enable auth endpoints and session checks.', category: 'Auth', defaultValue: 'true', targets: ['server', 'all'] },
|
|
16
|
+
{ key: 'TASKFORCE_AUTH_REQUIRED_FOR_API', description: 'Require auth for protected API routes.', category: 'Auth', defaultValue: 'true', targets: ['server', 'all'] },
|
|
17
|
+
{ key: 'TASKFORCE_AUTH_EXCLUDED_PATHS', description: 'Comma-separated API paths excluded from auth checks.', category: 'Auth', defaultValue: '/api/taskforce/health,/api/taskforce/version,/api/taskforce/auth', targets: ['server', 'all'] },
|
|
18
|
+
{ key: 'TASKFORCE_AUTH_DEFAULT_ROLE', description: 'Default role for unauthenticated context (owner|admin|member|read-only).', category: 'Auth', defaultValue: 'member', targets: ['server', 'all'] },
|
|
19
|
+
{ key: 'TASKFORCE_AUTH_SESSION_COOKIE', description: 'Session cookie name.', category: 'Auth', defaultValue: 'taskforce_session', targets: ['server', 'all'] },
|
|
20
|
+
{ key: 'TASKFORCE_AUTH_SESSION_SECRET', description: 'Session signing secret.', category: 'Auth', secret: true, targets: ['server', 'all'] },
|
|
21
|
+
{ key: 'TASKFORCE_AUTH_SESSION_TTL_SECONDS', description: 'Session max age in seconds.', category: 'Auth', defaultValue: '2592000', targets: ['server', 'all'] },
|
|
22
|
+
{ key: 'TASKFORCE_AUTH_SESSION_SAMESITE', description: 'SameSite policy (Lax|Strict|None).', category: 'Auth', defaultValue: 'None', targets: ['server', 'all'] },
|
|
23
|
+
{ key: 'TASKFORCE_AUTH_SESSION_DOMAIN', description: 'Optional cookie domain.', category: 'Auth', targets: ['server', 'all'] },
|
|
24
|
+
{ key: 'TASKFORCE_BOOTSTRAP_ADMIN_EMAIL', description: 'Bootstrap admin email used by login fallback.', category: 'Auth', targets: ['server', 'all'] },
|
|
25
|
+
{ key: 'TASKFORCE_BOOTSTRAP_ADMIN_PASSWORD', description: 'Bootstrap admin password used by login fallback.', category: 'Auth', secret: true, targets: ['server', 'all'] },
|
|
26
|
+
{ key: 'TASKFORCE_AUTH_PUBLIC_BASE_URL', description: 'Public auth URL used in email links.', category: 'Auth', example: 'https://performance-app.taskforcehq.ai', targets: ['server', 'all'] },
|
|
27
|
+
{ key: 'TASKFORCE_AUTH_EXPOSE_DEV_TOKENS', description: 'Expose dev auth tokens in responses.', category: 'Auth', defaultValue: 'false', targets: ['server', 'all'] },
|
|
28
|
+
{ key: 'TASKFORCE_CLOUD_PROXY_BASE_URL', description: 'Optional upstream cloud base URL for local proxying of auth/sync API calls.', category: 'Auth', example: 'https://performance-app.taskforcehq.ai', targets: ['server', 'all'] },
|
|
29
|
+
{ key: 'TASKFORCE_CF_ACCESS_CLIENT_ID', description: 'Optional Cloudflare Access service token client id for local cloud proxy.', category: 'Cloudflare', secret: true, targets: ['server', 'all'] },
|
|
30
|
+
{ key: 'TASKFORCE_CF_ACCESS_CLIENT_SECRET', description: 'Optional Cloudflare Access service token client secret for local cloud proxy.', category: 'Cloudflare', secret: true, targets: ['server', 'all'] },
|
|
31
|
+
{ key: 'TASKFORCE_OBJECT_STORAGE_PROVIDER', description: 'Object storage provider (local|r2).', category: 'Storage', defaultValue: 'local', targets: ['server', 'all'] },
|
|
32
|
+
{ key: 'TASKFORCE_STORAGE_PROVIDER', description: 'Legacy alias for object storage provider.', category: 'Storage', targets: ['server', 'all'] },
|
|
33
|
+
{ key: 'R2_ACCOUNT_ID', description: 'Cloudflare R2 account ID.', category: 'Storage', secret: true, targets: ['server', 'all'] },
|
|
34
|
+
{ key: 'R2_BUCKET', description: 'Cloudflare R2 bucket name.', category: 'Storage', targets: ['server', 'all'] },
|
|
35
|
+
{ key: 'R2_ACCESS_KEY_ID', description: 'R2 access key ID.', category: 'Storage', secret: true, targets: ['server', 'all'] },
|
|
36
|
+
{ key: 'R2_SECRET_ACCESS_KEY', description: 'R2 secret access key.', category: 'Storage', secret: true, targets: ['server', 'all'] },
|
|
37
|
+
{ key: 'R2_ENDPOINT', description: 'Optional R2 endpoint override.', category: 'Storage', targets: ['server', 'all'] },
|
|
38
|
+
{ key: 'R2_KEY_PREFIX', description: 'R2 key prefix.', category: 'Storage', defaultValue: 'taskforce', targets: ['server', 'all'] },
|
|
39
|
+
{ key: 'R2_SIGNED_UPLOAD_TTL_SECONDS', description: 'Upload signed URL TTL.', category: 'Storage', defaultValue: '600', targets: ['server', 'all'] },
|
|
40
|
+
{ key: 'R2_SIGNED_DOWNLOAD_TTL_SECONDS', description: 'Download signed URL TTL.', category: 'Storage', defaultValue: '300', targets: ['server', 'all'] },
|
|
41
|
+
{ key: 'TASKFORCE_VIRUS_SCAN_PROVIDER', description: 'Virus scanner provider (none|clamav).', category: 'Security', defaultValue: 'none', targets: ['server', 'all'] },
|
|
42
|
+
{ key: 'TASKFORCE_VIRUS_SCAN_FAIL_CLOSED', description: 'Fail uploads closed on scan failures.', category: 'Security', defaultValue: 'false', targets: ['server', 'all'] },
|
|
43
|
+
{ key: 'TASKFORCE_VIRUS_SCAN_TIMEOUT_MS', description: 'Virus scan timeout in ms.', category: 'Security', defaultValue: '15000', targets: ['server', 'all'] },
|
|
44
|
+
{ key: 'TASKFORCE_VIRUS_SCAN_ASYNC', description: 'Run scan asynchronously when supported.', category: 'Security', defaultValue: 'true', targets: ['server', 'all'] },
|
|
45
|
+
{ key: 'TASKFORCE_CLAMAV_HOST', description: 'ClamAV host.', category: 'Security', defaultValue: '127.0.0.1', targets: ['server', 'all'] },
|
|
46
|
+
{ key: 'TASKFORCE_CLAMAV_PORT', description: 'ClamAV port.', category: 'Security', defaultValue: '3310', targets: ['server', 'all'] },
|
|
47
|
+
{ key: 'TASKFORCE_EMAIL_PROVIDER', description: 'Email provider (console|resend|google-workspace).', category: 'Email', defaultValue: 'console', targets: ['server', 'all'] },
|
|
48
|
+
{ key: 'TASKFORCE_EMAIL_FROM', description: 'Email from address.', category: 'Email', defaultValue: 'Taskforce <no-reply@taskforcehq.ai>', targets: ['server', 'all'] },
|
|
49
|
+
{ key: 'TASKFORCE_EMAIL_REPLY_TO', description: 'Optional reply-to address.', category: 'Email', targets: ['server', 'all'] },
|
|
50
|
+
{ key: 'TASKFORCE_EMAIL_PROVIDER_API_KEY', description: 'Provider API key.', category: 'Email', secret: true, targets: ['server', 'all'] },
|
|
51
|
+
{ key: 'TASKFORCE_EMAIL_RESEND_API_KEY', description: 'Legacy resend API key alias.', category: 'Email', secret: true, targets: ['server', 'all'] },
|
|
52
|
+
{ key: 'TASKFORCE_EMAIL_GOOGLE_APP_PASSWORD', description: 'Google Workspace app password alias.', category: 'Email', secret: true, targets: ['server', 'all'] },
|
|
53
|
+
{ key: 'TASKFORCE_ALLOWED_ORIGINS', description: 'Allowed CORS origins (comma-separated).', category: 'Security', targets: ['server', 'all'] },
|
|
54
|
+
{ key: 'TASKFORCE_API_RATE_LIMIT_ENABLED', description: 'Enable API rate limiter.', category: 'Security', defaultValue: 'true', targets: ['server', 'all'] },
|
|
55
|
+
{ key: 'TASKFORCE_API_RATE_LIMIT_MAX_REQUESTS', description: 'API rate limit max requests per window.', category: 'Security', defaultValue: '120', targets: ['server', 'all'] },
|
|
56
|
+
{ key: 'TASKFORCE_API_RATE_LIMIT_WINDOW_MS', description: 'API rate limit window size in ms.', category: 'Security', defaultValue: '60000', targets: ['server', 'all'] },
|
|
57
|
+
{ key: 'TASKFORCE_STRIPE_WEBHOOK_RATE_LIMIT_MAX_REQUESTS', description: 'Stripe webhook-specific rate limit max requests per window.', category: 'Security', defaultValue: '600', targets: ['server', 'all'] },
|
|
58
|
+
{ key: 'TASKFORCE_STRIPE_WEBHOOK_RATE_LIMIT_WINDOW_MS', description: 'Stripe webhook-specific rate limit window size in ms.', category: 'Security', defaultValue: '60000', targets: ['server', 'all'] },
|
|
59
|
+
{ key: 'TASKFORCE_MAX_PAYLOAD_BYTES', description: 'Maximum accepted API payload size in bytes.', category: 'Security', defaultValue: '1048576', targets: ['server', 'all'] },
|
|
60
|
+
{ key: 'TASKFORCE_DOCUMENT_RETENTION_DAYS', description: 'Document retention period.', category: 'Security', defaultValue: '7', targets: ['server', 'all'] },
|
|
61
|
+
{ key: 'TASKFORCE_DOCUMENT_PURGE_ENABLED', description: 'Enable document purge scheduler.', category: 'Security', defaultValue: 'true', targets: ['server', 'all'] },
|
|
62
|
+
{ key: 'TASKFORCE_DOCUMENT_PURGE_INTERVAL_MINUTES', description: 'Document purge interval in minutes.', category: 'Security', defaultValue: '15', targets: ['server', 'all'] },
|
|
63
|
+
{ key: 'TASKFORCE_STORAGE_INTEGRITY_SCHEDULE_POLL_MINUTES', description: 'Storage integrity schedule poll frequency in minutes.', category: 'Security', defaultValue: '1', targets: ['server', 'all'] },
|
|
64
|
+
{ key: 'TASKFORCE_SYNC_KEY', description: 'Optional sync encryption key.', category: 'Security', secret: true, targets: ['server', 'all'] },
|
|
65
|
+
{ key: 'TASKFORCE_STRIPE_SECRET_KEY', description: 'Stripe secret API key used by server-side billing routes.', category: 'Security', secret: true, targets: ['server', 'all'] },
|
|
66
|
+
{ key: 'TASKFORCE_STRIPE_WEBHOOK_SECRET', description: 'Stripe webhook signing secret used to verify incoming webhooks.', category: 'Security', secret: true, targets: ['server', 'all'] },
|
|
67
|
+
{ key: 'TASKFORCE_BUILD_VERSION', description: 'Build version metadata.', category: 'Build', targets: ['server', 'all'] },
|
|
68
|
+
{ key: 'TASKFORCE_BUILD_GIT_SHA', description: 'Build git SHA metadata.', category: 'Build', targets: ['server', 'all'] },
|
|
69
|
+
{ key: 'TASKFORCE_BUILD_TIME', description: 'Build timestamp metadata.', category: 'Build', targets: ['server', 'all'] },
|
|
70
|
+
{ key: 'TASKFORCE_BUILD_DEPLOY_ID', description: 'Build deploy ID metadata.', category: 'Build', targets: ['server', 'all'] },
|
|
71
|
+
{ key: 'VITE_TASKFORCE_BASE_URL', description: 'Client base URL for both API and cloud auth when specific overrides are not set.', category: 'Client', example: 'https://performance-app.taskforcehq.ai', targets: ['all'] },
|
|
72
|
+
{ key: 'VITE_TASKFORCE_API_BASE_URL', description: 'Client API base URL (overrides VITE_TASKFORCE_BASE_URL when set).', category: 'Client', example: 'https://performance-app.taskforcehq.ai', targets: ['all'] },
|
|
73
|
+
{ key: 'VITE_TASKFORCE_CLOUD_AUTH_BASE_URL', description: 'Client cloud auth base URL.', category: 'Client', example: 'https://performance-app.taskforcehq.ai', targets: ['all'] },
|
|
74
|
+
{ key: 'VITE_TASKFORCE_WS_BASE_URL', description: 'Client websocket base URL override.', category: 'Client', example: 'wss://performance-app.taskforcehq.ai', targets: ['all'] },
|
|
75
|
+
{ key: 'SYNC_SOAK_LOCAL_BASE_URL', description: 'Soak local app URL.', category: 'Soak', defaultValue: 'http://localhost:5174', targets: ['soak', 'all'] },
|
|
76
|
+
{ key: 'SYNC_SOAK_CLOUD_BASE_URL', description: 'Soak cloud app URL.', category: 'Soak', example: 'https://performance-app.taskforcehq.ai', targets: ['soak', 'all'] },
|
|
77
|
+
{ key: 'SYNC_SOAK_WORKSPACE_ID', description: 'Base soak workspace ID.', category: 'Soak', defaultValue: 'test-smoke', targets: ['soak', 'all'] },
|
|
78
|
+
{ key: 'SYNC_SOAK_EMAIL', description: 'Primary soak account email fallback.', category: 'Soak', targets: ['soak', 'all'] },
|
|
79
|
+
{ key: 'SYNC_SOAK_PASSWORD', description: 'Primary soak account password fallback.', category: 'Soak', secret: true, targets: ['soak', 'all'] },
|
|
80
|
+
{ key: 'SYNC_SOAK_ACCOUNTS_JSON', description: 'JSON account pool for soak login rotation.', category: 'Soak', secret: true, example: '[{"email":"test_01@taskforcehq.ai","password":"..."}]', targets: ['soak', 'all'] },
|
|
81
|
+
{ key: 'SYNC_SOAK_ACCOUNT_WORKSPACES_JSON', description: 'Optional explicit account->workspace map.', category: 'Soak', example: '{"test_01@taskforcehq.ai":"test-smoke-test-01"}', targets: ['soak', 'all'] },
|
|
82
|
+
{ key: 'SYNC_SOAK_PROFILE', description: 'Soak profile (smoke|stress|overnight).', category: 'Soak', defaultValue: 'smoke', targets: ['soak', 'all'] },
|
|
83
|
+
{ key: 'SYNC_SOAK_DURATION_MIN', description: 'Soak duration in minutes.', category: 'Soak', defaultValue: '60', targets: ['soak', 'all'] },
|
|
84
|
+
{ key: 'SYNC_SOAK_SEED', description: 'Deterministic soak seed.', category: 'Soak', example: '4242', targets: ['soak', 'all'] },
|
|
85
|
+
{ key: 'SYNC_SOAK_ARTIFACT_DIR', description: 'Soak artifacts output directory.', category: 'Soak', defaultValue: '.taskforce/soak-artifacts', targets: ['soak', 'all'] },
|
|
86
|
+
{ key: 'SYNC_SOAK_ENABLE_SYNC_DEBUG', description: 'Enable sync debug diagnostics during soak.', category: 'Soak', defaultValue: 'false', targets: ['soak', 'all'] },
|
|
87
|
+
{ key: 'SYNC_SOAK_STRESS_ACTIONS', description: 'Stress action count.', category: 'Soak', defaultValue: '150', targets: ['soak', 'all'] },
|
|
88
|
+
{ key: 'SYNC_SOAK_STRESS_CHECKPOINT_INTERVAL', description: 'Stress checkpoint interval.', category: 'Soak', defaultValue: '50', targets: ['soak', 'all'] },
|
|
89
|
+
{ key: 'SYNC_SOAK_STRESS_SETTLE_RETRIES', description: 'Stress settle retry count.', category: 'Soak', defaultValue: '3', targets: ['soak', 'all'] },
|
|
90
|
+
{ key: 'SYNC_SOAK_STRESS_SETTLE_WAIT_MS', description: 'Stress settle wait time in ms.', category: 'Soak', defaultValue: '500', targets: ['soak', 'all'] },
|
|
91
|
+
{ key: 'SYNC_SOAK_STRESS_HARD_RESET_SETTLE', description: 'Enable hard reset settle path in stress.', category: 'Soak', defaultValue: 'true', targets: ['soak', 'all'] },
|
|
92
|
+
{ key: 'SYNC_SOAK_OVERNIGHT_ACTION_INTERVAL_MS', description: 'Overnight action pacing interval in ms.', category: 'Soak', defaultValue: '60000', targets: ['soak', 'all'] },
|
|
93
|
+
{ key: 'SYNC_SOAK_OVERNIGHT_CHECKPOINT_MIN', description: 'Overnight checkpoint interval in minutes.', category: 'Soak', defaultValue: '15', targets: ['soak', 'all'] },
|
|
94
|
+
{ key: 'SYNC_SOAK_OVERNIGHT_MAX_ACTIONS', description: 'Overnight hard cap on action count.', category: 'Soak', defaultValue: '10000', targets: ['soak', 'all'] },
|
|
95
|
+
{ key: 'SYNC_SOAK_OVERNIGHT_MIN_ACTIONS', description: 'Overnight minimum actions expected before pass.', category: 'Soak', defaultValue: '80', targets: ['soak', 'all'] },
|
|
96
|
+
{ key: 'CF_ACCESS_CLIENT_ID', description: 'Cloudflare Access service token client id (soak).', category: 'Cloudflare', secret: true, targets: ['soak', 'all'] },
|
|
97
|
+
{ key: 'CF_ACCESS_CLIENT_SECRET', description: 'Cloudflare Access service token client secret (soak).', category: 'Cloudflare', secret: true, targets: ['soak', 'all'] }
|
|
98
|
+
];
|
|
99
|
+
const SPEC_BY_KEY = new Map(SPECS.map((spec) => [spec.key, spec]));
|
|
100
|
+
const MANAGED_PREFIXES = ['TASKFORCE_', 'SYNC_SOAK_', 'VITE_TASKFORCE_', 'R2_', 'CF_ACCESS_'];
|
|
101
|
+
const ADDITIONAL_MANAGED_KEYS = new Set(['PORT']);
|
|
102
|
+
function hasValue(env, key) {
|
|
103
|
+
return String(env[key] || '').trim().length > 0;
|
|
104
|
+
}
|
|
105
|
+
function parseBoolean(value) {
|
|
106
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
107
|
+
if (!normalized)
|
|
108
|
+
return null;
|
|
109
|
+
if (['1', 'true', 'yes', 'on'].includes(normalized))
|
|
110
|
+
return true;
|
|
111
|
+
if (['0', 'false', 'no', 'off'].includes(normalized))
|
|
112
|
+
return false;
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
function parsePositiveInt(value) {
|
|
116
|
+
if (!String(value || '').trim())
|
|
117
|
+
return null;
|
|
118
|
+
const parsed = Number.parseInt(String(value), 10);
|
|
119
|
+
if (!Number.isFinite(parsed) || parsed <= 0)
|
|
120
|
+
return null;
|
|
121
|
+
return parsed;
|
|
122
|
+
}
|
|
123
|
+
function isHttpUrl(value) {
|
|
124
|
+
const raw = String(value || '').trim();
|
|
125
|
+
if (!raw)
|
|
126
|
+
return false;
|
|
127
|
+
try {
|
|
128
|
+
const parsed = new URL(raw);
|
|
129
|
+
return parsed.protocol === 'http:' || parsed.protocol === 'https:';
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function isManagedKey(key) {
|
|
136
|
+
if (ADDITIONAL_MANAGED_KEYS.has(key))
|
|
137
|
+
return true;
|
|
138
|
+
return MANAGED_PREFIXES.some((prefix) => key.startsWith(prefix));
|
|
139
|
+
}
|
|
140
|
+
function validateCommonNumbers(env, errors) {
|
|
141
|
+
const numericKeys = [
|
|
142
|
+
'PORT',
|
|
143
|
+
'TASKFORCE_API_RATE_LIMIT_MAX_REQUESTS',
|
|
144
|
+
'TASKFORCE_API_RATE_LIMIT_WINDOW_MS',
|
|
145
|
+
'TASKFORCE_STRIPE_WEBHOOK_RATE_LIMIT_MAX_REQUESTS',
|
|
146
|
+
'TASKFORCE_STRIPE_WEBHOOK_RATE_LIMIT_WINDOW_MS',
|
|
147
|
+
'TASKFORCE_MAX_PAYLOAD_BYTES',
|
|
148
|
+
'TASKFORCE_DOCUMENT_RETENTION_DAYS',
|
|
149
|
+
'TASKFORCE_DOCUMENT_PURGE_INTERVAL_MINUTES',
|
|
150
|
+
'TASKFORCE_STORAGE_INTEGRITY_SCHEDULE_POLL_MINUTES',
|
|
151
|
+
'TASKFORCE_AUTH_SESSION_TTL_SECONDS',
|
|
152
|
+
'TASKFORCE_VIRUS_SCAN_TIMEOUT_MS',
|
|
153
|
+
'TASKFORCE_CLAMAV_PORT',
|
|
154
|
+
'R2_SIGNED_UPLOAD_TTL_SECONDS',
|
|
155
|
+
'R2_SIGNED_DOWNLOAD_TTL_SECONDS',
|
|
156
|
+
'SYNC_SOAK_DURATION_MIN',
|
|
157
|
+
'SYNC_SOAK_STRESS_ACTIONS',
|
|
158
|
+
'SYNC_SOAK_STRESS_CHECKPOINT_INTERVAL',
|
|
159
|
+
'SYNC_SOAK_STRESS_SETTLE_RETRIES',
|
|
160
|
+
'SYNC_SOAK_STRESS_SETTLE_WAIT_MS',
|
|
161
|
+
'SYNC_SOAK_OVERNIGHT_ACTION_INTERVAL_MS',
|
|
162
|
+
'SYNC_SOAK_OVERNIGHT_CHECKPOINT_MIN',
|
|
163
|
+
'SYNC_SOAK_OVERNIGHT_MAX_ACTIONS',
|
|
164
|
+
'SYNC_SOAK_OVERNIGHT_MIN_ACTIONS'
|
|
165
|
+
];
|
|
166
|
+
for (const key of numericKeys) {
|
|
167
|
+
const raw = env[key];
|
|
168
|
+
if (!String(raw || '').trim())
|
|
169
|
+
continue;
|
|
170
|
+
if (parsePositiveInt(raw) === null) {
|
|
171
|
+
errors.push(`${key} must be a positive integer.`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function validateServerEnv(env, errors) {
|
|
176
|
+
const providerRaw = String(env.TASKFORCE_DB_PROVIDER || 'sqlite').trim().toLowerCase();
|
|
177
|
+
const provider = providerRaw === 'postgres' ? 'postgres' : 'sqlite';
|
|
178
|
+
if (!['sqlite', 'postgres'].includes(providerRaw) && providerRaw !== '') {
|
|
179
|
+
errors.push('TASKFORCE_DB_PROVIDER must be one of: sqlite, postgres.');
|
|
180
|
+
}
|
|
181
|
+
const runtimeRaw = String(env.TASKFORCE_RUNTIME_MODE || '').trim().toLowerCase();
|
|
182
|
+
const runtimeMode = runtimeRaw === 'cloud' || runtimeRaw === 'local'
|
|
183
|
+
? runtimeRaw
|
|
184
|
+
: (provider === 'postgres' ? 'cloud' : 'local');
|
|
185
|
+
if (runtimeRaw && !['local', 'cloud'].includes(runtimeRaw)) {
|
|
186
|
+
errors.push('TASKFORCE_RUNTIME_MODE must be one of: local, cloud.');
|
|
187
|
+
}
|
|
188
|
+
const allowSqliteInCloud = parseBoolean(env.TASKFORCE_ALLOW_SQLITE_IN_CLOUD) === true;
|
|
189
|
+
const billingEnabled = parseBoolean(env.TASKFORCE_BILLING_ENABLED) === true;
|
|
190
|
+
if (runtimeMode === 'cloud' && provider !== 'postgres' && !allowSqliteInCloud) {
|
|
191
|
+
errors.push('Cloud runtime requires TASKFORCE_DB_PROVIDER=postgres (or explicit TASKFORCE_ALLOW_SQLITE_IN_CLOUD=true override).');
|
|
192
|
+
}
|
|
193
|
+
if (billingEnabled && runtimeMode !== 'cloud') {
|
|
194
|
+
errors.push('TASKFORCE_BILLING_ENABLED requires TASKFORCE_RUNTIME_MODE=cloud.');
|
|
195
|
+
}
|
|
196
|
+
if (provider === 'postgres' && !hasValue(env, 'TASKFORCE_DATABASE_URL')) {
|
|
197
|
+
errors.push('TASKFORCE_DATABASE_URL is required when TASKFORCE_DB_PROVIDER=postgres.');
|
|
198
|
+
}
|
|
199
|
+
if (billingEnabled && !hasValue(env, 'TASKFORCE_STRIPE_SECRET_KEY')) {
|
|
200
|
+
errors.push('TASKFORCE_STRIPE_SECRET_KEY is required when TASKFORCE_BILLING_ENABLED=true.');
|
|
201
|
+
}
|
|
202
|
+
if (billingEnabled && !hasValue(env, 'TASKFORCE_STRIPE_WEBHOOK_SECRET')) {
|
|
203
|
+
errors.push('TASKFORCE_STRIPE_WEBHOOK_SECRET is required when TASKFORCE_BILLING_ENABLED=true.');
|
|
204
|
+
}
|
|
205
|
+
const authEnabled = parseBoolean(env.TASKFORCE_AUTH_ENABLED);
|
|
206
|
+
const effectiveAuthEnabled = authEnabled === null ? runtimeMode === 'cloud' : authEnabled;
|
|
207
|
+
if (effectiveAuthEnabled && !hasValue(env, 'TASKFORCE_AUTH_SESSION_SECRET')) {
|
|
208
|
+
errors.push('TASKFORCE_AUTH_SESSION_SECRET is required when auth is enabled.');
|
|
209
|
+
}
|
|
210
|
+
const sameSiteRaw = String(env.TASKFORCE_AUTH_SESSION_SAMESITE || '').trim();
|
|
211
|
+
if (sameSiteRaw && !['Lax', 'Strict', 'None', 'lax', 'strict', 'none'].includes(sameSiteRaw)) {
|
|
212
|
+
errors.push('TASKFORCE_AUTH_SESSION_SAMESITE must be one of: Lax, Strict, None.');
|
|
213
|
+
}
|
|
214
|
+
const objectProviderRaw = String(env.TASKFORCE_OBJECT_STORAGE_PROVIDER || env.TASKFORCE_STORAGE_PROVIDER || 'local').trim().toLowerCase();
|
|
215
|
+
const objectProvider = objectProviderRaw === 'r2' ? 'r2' : 'local';
|
|
216
|
+
if (objectProviderRaw && !['local', 'r2'].includes(objectProviderRaw)) {
|
|
217
|
+
errors.push('TASKFORCE_OBJECT_STORAGE_PROVIDER must be one of: local, r2.');
|
|
218
|
+
}
|
|
219
|
+
if (objectProvider === 'r2') {
|
|
220
|
+
const requiredR2 = ['R2_ACCOUNT_ID', 'R2_BUCKET', 'R2_ACCESS_KEY_ID', 'R2_SECRET_ACCESS_KEY'];
|
|
221
|
+
for (const key of requiredR2) {
|
|
222
|
+
if (!hasValue(env, key))
|
|
223
|
+
errors.push(`${key} is required when TASKFORCE_OBJECT_STORAGE_PROVIDER=r2.`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const emailProviderRaw = String(env.TASKFORCE_EMAIL_PROVIDER || 'console').trim().toLowerCase();
|
|
227
|
+
if (!['console', 'resend', 'google-workspace'].includes(emailProviderRaw)) {
|
|
228
|
+
errors.push('TASKFORCE_EMAIL_PROVIDER must be one of: console, resend, google-workspace.');
|
|
229
|
+
}
|
|
230
|
+
if (emailProviderRaw === 'resend' || emailProviderRaw === 'google-workspace') {
|
|
231
|
+
const hasApiKey = hasValue(env, 'TASKFORCE_EMAIL_PROVIDER_API_KEY')
|
|
232
|
+
|| (emailProviderRaw === 'resend' && hasValue(env, 'TASKFORCE_EMAIL_RESEND_API_KEY'))
|
|
233
|
+
|| (emailProviderRaw === 'google-workspace' && hasValue(env, 'TASKFORCE_EMAIL_GOOGLE_APP_PASSWORD'));
|
|
234
|
+
if (!hasApiKey) {
|
|
235
|
+
if (emailProviderRaw === 'resend') {
|
|
236
|
+
errors.push('TASKFORCE_EMAIL_PROVIDER_API_KEY (or TASKFORCE_EMAIL_RESEND_API_KEY) is required when TASKFORCE_EMAIL_PROVIDER=resend.');
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
errors.push('TASKFORCE_EMAIL_PROVIDER_API_KEY (or TASKFORCE_EMAIL_GOOGLE_APP_PASSWORD) is required when TASKFORCE_EMAIL_PROVIDER=google-workspace.');
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
const virusProviderRaw = String(env.TASKFORCE_VIRUS_SCAN_PROVIDER || 'none').trim().toLowerCase();
|
|
244
|
+
if (!['none', 'clamav'].includes(virusProviderRaw)) {
|
|
245
|
+
errors.push('TASKFORCE_VIRUS_SCAN_PROVIDER must be one of: none, clamav.');
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
function validateSoakEnv(env, errors) {
|
|
249
|
+
if (!hasValue(env, 'SYNC_SOAK_LOCAL_BASE_URL'))
|
|
250
|
+
errors.push('SYNC_SOAK_LOCAL_BASE_URL is required for soak tests.');
|
|
251
|
+
if (!hasValue(env, 'SYNC_SOAK_CLOUD_BASE_URL'))
|
|
252
|
+
errors.push('SYNC_SOAK_CLOUD_BASE_URL is required for soak tests.');
|
|
253
|
+
if (!hasValue(env, 'SYNC_SOAK_WORKSPACE_ID'))
|
|
254
|
+
errors.push('SYNC_SOAK_WORKSPACE_ID is required for soak tests.');
|
|
255
|
+
if (hasValue(env, 'SYNC_SOAK_LOCAL_BASE_URL') && !isHttpUrl(env.SYNC_SOAK_LOCAL_BASE_URL)) {
|
|
256
|
+
errors.push('SYNC_SOAK_LOCAL_BASE_URL must be an absolute http(s) URL.');
|
|
257
|
+
}
|
|
258
|
+
if (hasValue(env, 'SYNC_SOAK_CLOUD_BASE_URL') && !isHttpUrl(env.SYNC_SOAK_CLOUD_BASE_URL)) {
|
|
259
|
+
errors.push('SYNC_SOAK_CLOUD_BASE_URL must be an absolute http(s) URL.');
|
|
260
|
+
}
|
|
261
|
+
const hasAccountPool = hasValue(env, 'SYNC_SOAK_ACCOUNTS_JSON');
|
|
262
|
+
const hasSingleAccount = hasValue(env, 'SYNC_SOAK_EMAIL') && hasValue(env, 'SYNC_SOAK_PASSWORD');
|
|
263
|
+
if (!hasAccountPool && !hasSingleAccount) {
|
|
264
|
+
errors.push('Provide SYNC_SOAK_ACCOUNTS_JSON or both SYNC_SOAK_EMAIL and SYNC_SOAK_PASSWORD.');
|
|
265
|
+
}
|
|
266
|
+
if (hasAccountPool) {
|
|
267
|
+
try {
|
|
268
|
+
const parsed = JSON.parse(String(env.SYNC_SOAK_ACCOUNTS_JSON || ''));
|
|
269
|
+
if (!Array.isArray(parsed) || parsed.length === 0) {
|
|
270
|
+
errors.push('SYNC_SOAK_ACCOUNTS_JSON must be a non-empty JSON array.');
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
for (const entry of parsed) {
|
|
274
|
+
const email = String(entry?.email || '').trim();
|
|
275
|
+
const password = String(entry?.password || '').trim();
|
|
276
|
+
if (!email || !password) {
|
|
277
|
+
errors.push('Each SYNC_SOAK_ACCOUNTS_JSON entry must include non-empty email and password.');
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
errors.push('SYNC_SOAK_ACCOUNTS_JSON must be valid JSON.');
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
const profile = String(env.SYNC_SOAK_PROFILE || 'smoke').trim().toLowerCase();
|
|
288
|
+
if (!['smoke', 'stress', 'overnight'].includes(profile)) {
|
|
289
|
+
errors.push('SYNC_SOAK_PROFILE must be one of: smoke, stress, overnight.');
|
|
290
|
+
}
|
|
291
|
+
if (hasValue(env, 'SYNC_SOAK_ACCOUNT_WORKSPACES_JSON')) {
|
|
292
|
+
try {
|
|
293
|
+
const parsed = JSON.parse(String(env.SYNC_SOAK_ACCOUNT_WORKSPACES_JSON || ''));
|
|
294
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
295
|
+
errors.push('SYNC_SOAK_ACCOUNT_WORKSPACES_JSON must be a JSON object map (email->workspaceId).');
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
errors.push('SYNC_SOAK_ACCOUNT_WORKSPACES_JSON must be valid JSON.');
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
export function validateEnvironment(env = process.env, options = {}) {
|
|
304
|
+
const target = options.target || 'all';
|
|
305
|
+
const strictUnknown = options.strictUnknown !== false;
|
|
306
|
+
const errors = [];
|
|
307
|
+
const warnings = [];
|
|
308
|
+
validateCommonNumbers(env, errors);
|
|
309
|
+
if (target === 'server' || target === 'all') {
|
|
310
|
+
validateServerEnv(env, errors);
|
|
311
|
+
}
|
|
312
|
+
if (target === 'soak' || target === 'all') {
|
|
313
|
+
validateSoakEnv(env, errors);
|
|
314
|
+
}
|
|
315
|
+
if (strictUnknown) {
|
|
316
|
+
const known = new Set(SPECS.map((spec) => spec.key));
|
|
317
|
+
for (const key of Object.keys(env)) {
|
|
318
|
+
if (!isManagedKey(key))
|
|
319
|
+
continue;
|
|
320
|
+
if (!known.has(key)) {
|
|
321
|
+
errors.push(`Unknown managed environment variable: ${key}`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return {
|
|
326
|
+
ok: errors.length === 0,
|
|
327
|
+
errors,
|
|
328
|
+
warnings
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
export function assertEnvironment(env = process.env, options = {}) {
|
|
332
|
+
const result = validateEnvironment(env, options);
|
|
333
|
+
if (result.ok)
|
|
334
|
+
return;
|
|
335
|
+
const details = result.errors.map((entry) => `- ${entry}`).join('\n');
|
|
336
|
+
throw new Error(`Environment validation failed:\n${details}`);
|
|
337
|
+
}
|
|
338
|
+
export function getEnvSpecs(target = 'all') {
|
|
339
|
+
if (target === 'all')
|
|
340
|
+
return [...SPECS];
|
|
341
|
+
return SPECS.filter((spec) => spec.targets.includes(target) || spec.targets.includes('all'));
|
|
342
|
+
}
|
|
343
|
+
export function renderEnvExample(target = 'all') {
|
|
344
|
+
const specs = getEnvSpecs(target);
|
|
345
|
+
const categoryOrder = ['Core', 'Auth', 'Storage', 'Security', 'Email', 'Build', 'Client', 'Soak', 'Cloudflare'];
|
|
346
|
+
const grouped = new Map();
|
|
347
|
+
for (const category of categoryOrder)
|
|
348
|
+
grouped.set(category, []);
|
|
349
|
+
for (const spec of specs) {
|
|
350
|
+
const bucket = grouped.get(spec.category);
|
|
351
|
+
if (bucket)
|
|
352
|
+
bucket.push(spec);
|
|
353
|
+
}
|
|
354
|
+
const out = [];
|
|
355
|
+
out.push('# Generated file. Edit values locally; do not store secrets in git.');
|
|
356
|
+
out.push('# Regenerate with: npm run gen:env-example');
|
|
357
|
+
out.push('');
|
|
358
|
+
for (const category of categoryOrder) {
|
|
359
|
+
const entries = grouped.get(category) || [];
|
|
360
|
+
if (entries.length === 0)
|
|
361
|
+
continue;
|
|
362
|
+
out.push(`# ${category}`);
|
|
363
|
+
for (const spec of entries) {
|
|
364
|
+
out.push(`# ${spec.description}`);
|
|
365
|
+
const value = spec.defaultValue
|
|
366
|
+
?? spec.example
|
|
367
|
+
?? (spec.secret ? '' : '');
|
|
368
|
+
out.push(`${spec.key}=${value}`);
|
|
369
|
+
out.push('');
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return `${out.join('\n').trimEnd()}\n`;
|
|
373
|
+
}
|
|
374
|
+
export const MANAGED_ENV_KEYS = Object.freeze([...SPEC_BY_KEY.keys()]);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { renderEnvExample, validateEnvironment } from './envSchema';
|
|
3
|
+
describe('config/envSchema', () => {
|
|
4
|
+
it('fails server validation when cloud runtime is configured without postgres', () => {
|
|
5
|
+
const result = validateEnvironment({
|
|
6
|
+
TASKFORCE_RUNTIME_MODE: 'cloud',
|
|
7
|
+
TASKFORCE_DB_PROVIDER: 'sqlite',
|
|
8
|
+
TASKFORCE_AUTH_ENABLED: 'false'
|
|
9
|
+
}, { target: 'server', strictUnknown: false });
|
|
10
|
+
expect(result.ok).toBe(false);
|
|
11
|
+
expect(result.errors.join('\n')).toMatch(/Cloud runtime requires TASKFORCE_DB_PROVIDER=postgres/i);
|
|
12
|
+
});
|
|
13
|
+
it('fails soak validation when credentials are missing', () => {
|
|
14
|
+
const result = validateEnvironment({
|
|
15
|
+
SYNC_SOAK_LOCAL_BASE_URL: 'http://localhost:5174',
|
|
16
|
+
SYNC_SOAK_CLOUD_BASE_URL: 'https://performance-app.taskforcehq.ai',
|
|
17
|
+
SYNC_SOAK_WORKSPACE_ID: 'test-smoke'
|
|
18
|
+
}, { target: 'soak', strictUnknown: false });
|
|
19
|
+
expect(result.ok).toBe(false);
|
|
20
|
+
expect(result.errors.join('\n')).toMatch(/SYNC_SOAK_ACCOUNTS_JSON|SYNC_SOAK_EMAIL/i);
|
|
21
|
+
});
|
|
22
|
+
it('renders an env example with key sections', () => {
|
|
23
|
+
const example = renderEnvExample('all');
|
|
24
|
+
expect(example).toContain('# Core');
|
|
25
|
+
expect(example).toContain('TASKFORCE_DB_PROVIDER=');
|
|
26
|
+
expect(example).toContain('SYNC_SOAK_ACCOUNTS_JSON=');
|
|
27
|
+
});
|
|
28
|
+
it('rejects unknown email provider', () => {
|
|
29
|
+
const result = validateEnvironment({
|
|
30
|
+
TASKFORCE_RUNTIME_MODE: 'cloud',
|
|
31
|
+
TASKFORCE_DB_PROVIDER: 'postgres',
|
|
32
|
+
TASKFORCE_DATABASE_URL: 'postgresql://localhost/test',
|
|
33
|
+
TASKFORCE_AUTH_ENABLED: 'false',
|
|
34
|
+
TASKFORCE_EMAIL_PROVIDER: 'ses'
|
|
35
|
+
}, { target: 'server', strictUnknown: false });
|
|
36
|
+
expect(result.ok).toBe(false);
|
|
37
|
+
expect(result.errors.join('\n')).toContain('TASKFORCE_EMAIL_PROVIDER must be one of: console, resend, google-workspace.');
|
|
38
|
+
});
|
|
39
|
+
it('requires google workspace app password when provider is google-workspace', () => {
|
|
40
|
+
const result = validateEnvironment({
|
|
41
|
+
TASKFORCE_RUNTIME_MODE: 'cloud',
|
|
42
|
+
TASKFORCE_DB_PROVIDER: 'postgres',
|
|
43
|
+
TASKFORCE_DATABASE_URL: 'postgresql://localhost/test',
|
|
44
|
+
TASKFORCE_AUTH_ENABLED: 'false',
|
|
45
|
+
TASKFORCE_EMAIL_PROVIDER: 'google-workspace'
|
|
46
|
+
}, { target: 'server', strictUnknown: false });
|
|
47
|
+
expect(result.ok).toBe(false);
|
|
48
|
+
expect(result.errors.join('\n')).toContain('TASKFORCE_EMAIL_PROVIDER_API_KEY (or TASKFORCE_EMAIL_GOOGLE_APP_PASSWORD) is required when TASKFORCE_EMAIL_PROVIDER=google-workspace.');
|
|
49
|
+
});
|
|
50
|
+
it('accepts google workspace provider with google app password alias', () => {
|
|
51
|
+
const result = validateEnvironment({
|
|
52
|
+
TASKFORCE_RUNTIME_MODE: 'cloud',
|
|
53
|
+
TASKFORCE_DB_PROVIDER: 'postgres',
|
|
54
|
+
TASKFORCE_DATABASE_URL: 'postgresql://localhost/test',
|
|
55
|
+
TASKFORCE_AUTH_ENABLED: 'false',
|
|
56
|
+
TASKFORCE_EMAIL_PROVIDER: 'google-workspace',
|
|
57
|
+
TASKFORCE_EMAIL_GOOGLE_APP_PASSWORD: 'app-password'
|
|
58
|
+
}, { target: 'server', strictUnknown: false });
|
|
59
|
+
expect(result.ok).toBe(true);
|
|
60
|
+
});
|
|
61
|
+
it('requires TASKFORCE_STRIPE_SECRET_KEY when billing is enabled', () => {
|
|
62
|
+
const result = validateEnvironment({
|
|
63
|
+
TASKFORCE_RUNTIME_MODE: 'cloud',
|
|
64
|
+
TASKFORCE_DB_PROVIDER: 'postgres',
|
|
65
|
+
TASKFORCE_DATABASE_URL: 'postgresql://localhost/test',
|
|
66
|
+
TASKFORCE_AUTH_ENABLED: 'false',
|
|
67
|
+
TASKFORCE_BILLING_ENABLED: 'true',
|
|
68
|
+
TASKFORCE_STRIPE_WEBHOOK_SECRET: 'whsec_test'
|
|
69
|
+
}, { target: 'server', strictUnknown: false });
|
|
70
|
+
expect(result.ok).toBe(false);
|
|
71
|
+
expect(result.errors.join('\n')).toContain('TASKFORCE_STRIPE_SECRET_KEY is required when TASKFORCE_BILLING_ENABLED=true.');
|
|
72
|
+
});
|
|
73
|
+
it('requires TASKFORCE_STRIPE_WEBHOOK_SECRET when billing is enabled', () => {
|
|
74
|
+
const result = validateEnvironment({
|
|
75
|
+
TASKFORCE_RUNTIME_MODE: 'cloud',
|
|
76
|
+
TASKFORCE_DB_PROVIDER: 'postgres',
|
|
77
|
+
TASKFORCE_DATABASE_URL: 'postgresql://localhost/test',
|
|
78
|
+
TASKFORCE_AUTH_ENABLED: 'false',
|
|
79
|
+
TASKFORCE_BILLING_ENABLED: 'true',
|
|
80
|
+
TASKFORCE_STRIPE_SECRET_KEY: 'sk_test_x'
|
|
81
|
+
}, { target: 'server', strictUnknown: false });
|
|
82
|
+
expect(result.ok).toBe(false);
|
|
83
|
+
expect(result.errors.join('\n')).toContain('TASKFORCE_STRIPE_WEBHOOK_SECRET is required when TASKFORCE_BILLING_ENABLED=true.');
|
|
84
|
+
});
|
|
85
|
+
it('requires cloud runtime when billing is enabled', () => {
|
|
86
|
+
const result = validateEnvironment({
|
|
87
|
+
TASKFORCE_RUNTIME_MODE: 'local',
|
|
88
|
+
TASKFORCE_DB_PROVIDER: 'sqlite',
|
|
89
|
+
TASKFORCE_AUTH_ENABLED: 'false',
|
|
90
|
+
TASKFORCE_BILLING_ENABLED: 'true',
|
|
91
|
+
TASKFORCE_STRIPE_SECRET_KEY: 'sk_test_x',
|
|
92
|
+
TASKFORCE_STRIPE_WEBHOOK_SECRET: 'whsec_test'
|
|
93
|
+
}, { target: 'server', strictUnknown: false });
|
|
94
|
+
expect(result.ok).toBe(false);
|
|
95
|
+
expect(result.errors.join('\n')).toContain('TASKFORCE_BILLING_ENABLED requires TASKFORCE_RUNTIME_MODE=cloud.');
|
|
96
|
+
});
|
|
97
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as os from 'os';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import { TaskforceCore } from './Taskforce';
|
|
6
|
+
function makeTempProjectRoot() {
|
|
7
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'taskforce-bulk-preview-'));
|
|
8
|
+
fs.mkdirSync(root, { recursive: true });
|
|
9
|
+
return root;
|
|
10
|
+
}
|
|
11
|
+
describe('TaskforceCore bulk update preflight', () => {
|
|
12
|
+
it('builds deterministic apply set and skips invalid/noop updates', () => {
|
|
13
|
+
const projectRoot = makeTempProjectRoot();
|
|
14
|
+
const core = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
|
|
15
|
+
const task = core.createTask({ title: 'A', priority: 2 });
|
|
16
|
+
const preview = core.bulkUpdateFieldsPreview([
|
|
17
|
+
{ title: 'missing id' },
|
|
18
|
+
{ id: 'task-missing', title: 'x' },
|
|
19
|
+
{ id: task.id, priority: 2 }, // no-op
|
|
20
|
+
{ id: task.id, priority: 3 } // apply
|
|
21
|
+
]);
|
|
22
|
+
expect(preview.total).toBe(4);
|
|
23
|
+
expect(preview.toApply).toHaveLength(1);
|
|
24
|
+
expect(preview.toApply[0].id).toBe(task.id);
|
|
25
|
+
expect(preview.toApply[0].changedFields).toEqual(['priority']);
|
|
26
|
+
expect(preview.skipped.map(s => s.code)).toEqual(expect.arrayContaining(['TASK_ID_REQUIRED', 'TASK_NOT_FOUND', 'DUPLICATE_SUPERSEDED']));
|
|
27
|
+
core.close();
|
|
28
|
+
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
29
|
+
});
|
|
30
|
+
it('applies only changed fields and returns skip counters', () => {
|
|
31
|
+
const projectRoot = makeTempProjectRoot();
|
|
32
|
+
const core = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
|
|
33
|
+
const task = core.createTask({ title: 'A', priority: 2 });
|
|
34
|
+
const result = core.bulkUpdateFields([
|
|
35
|
+
{ id: task.id, priority: 2 }, // no-op
|
|
36
|
+
{ id: task.id, priority: 3 }, // superseded
|
|
37
|
+
{ id: task.id, priority: 4 } // effective
|
|
38
|
+
]);
|
|
39
|
+
const refreshed = core.getTask(task.id);
|
|
40
|
+
expect(refreshed?.priority).toBe(4);
|
|
41
|
+
expect(result.updated).toBe(1);
|
|
42
|
+
expect(result.skippedNoop).toBe(2);
|
|
43
|
+
expect(result.deduped).toBe(2);
|
|
44
|
+
core.close();
|
|
45
|
+
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
46
|
+
});
|
|
47
|
+
it('includes priority distribution before and after', () => {
|
|
48
|
+
const projectRoot = makeTempProjectRoot();
|
|
49
|
+
const core = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
|
|
50
|
+
const a = core.createTask({ title: 'A', priority: 1 });
|
|
51
|
+
core.createTask({ title: 'B', priority: 2 });
|
|
52
|
+
const preview = core.bulkUpdateFieldsPreview([
|
|
53
|
+
{ id: a.id, priority: 4 }
|
|
54
|
+
]);
|
|
55
|
+
expect(preview.priorityBefore?.[1]).toBe(1);
|
|
56
|
+
expect(preview.priorityBefore?.[4] || 0).toBe(0);
|
|
57
|
+
expect(preview.priorityAfter?.[1] || 0).toBe(0);
|
|
58
|
+
expect(preview.priorityAfter?.[4]).toBe(1);
|
|
59
|
+
core.close();
|
|
60
|
+
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
5
|
+
import { TaskforceCore } from './Taskforce';
|
|
6
|
+
const roots = [];
|
|
7
|
+
function createCore() {
|
|
8
|
+
const projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'taskforce-completed-at-'));
|
|
9
|
+
roots.push(projectRoot);
|
|
10
|
+
return new TaskforceCore({ projectRoot });
|
|
11
|
+
}
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
while (roots.length > 0) {
|
|
14
|
+
const root = roots.pop();
|
|
15
|
+
if (root)
|
|
16
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
describe('TaskforceCore completedAt behavior', () => {
|
|
20
|
+
it('sets completedAt when status transitions to done', () => {
|
|
21
|
+
const core = createCore();
|
|
22
|
+
const task = core.createTask({ title: 'A', status: 'task' });
|
|
23
|
+
const updated = core.updateTask(task.id, { status: 'done' });
|
|
24
|
+
expect(updated?.completedAt).toBeTruthy();
|
|
25
|
+
core.close();
|
|
26
|
+
});
|
|
27
|
+
it('preserves completedAt when archiving a previously completed task', () => {
|
|
28
|
+
const core = createCore();
|
|
29
|
+
const task = core.createTask({ title: 'B', status: 'task' });
|
|
30
|
+
const completed = core.updateTask(task.id, { status: 'cancelled' });
|
|
31
|
+
expect(completed?.completedAt).toBeTruthy();
|
|
32
|
+
const completedAt = completed?.completedAt;
|
|
33
|
+
const archived = core.archiveTask(task.id, true, 'No longer needed');
|
|
34
|
+
expect(archived?.completedAt).toBe(completedAt);
|
|
35
|
+
core.close();
|
|
36
|
+
});
|
|
37
|
+
it('sets completedAt when archiving an active task', () => {
|
|
38
|
+
const core = createCore();
|
|
39
|
+
const task = core.createTask({ title: 'C', status: 'in-progress' });
|
|
40
|
+
const archived = core.archiveTask(task.id, false);
|
|
41
|
+
expect(archived?.completedAt).toBeTruthy();
|
|
42
|
+
core.close();
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|