prjct-cli 1.22.0 → 1.24.0
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/CHANGELOG.md +230 -0
- package/bin/prjct +30 -13
- package/dist/bin/prjct-core.mjs +1748 -0
- package/dist/bin/prjct.mjs +17 -36672
- package/dist/cli/linear.mjs +14 -0
- package/dist/daemon/entry.mjs +1429 -0
- package/dist/templates.json +1 -0
- package/package.json +4 -5
- package/bin/prjct.ts +0 -342
- package/core/__tests__/agentic/analysis-injection.test.ts +0 -377
- package/core/__tests__/agentic/cache-eviction.test.ts +0 -294
- package/core/__tests__/agentic/command-context.test.ts +0 -281
- package/core/__tests__/agentic/command-executor.test.ts +0 -659
- package/core/__tests__/agentic/domain-classifier.test.ts +0 -330
- package/core/__tests__/agentic/injection-validator.test.ts +0 -255
- package/core/__tests__/agentic/memory-system.test.ts +0 -281
- package/core/__tests__/agentic/plan-mode.test.ts +0 -386
- package/core/__tests__/agentic/prompt-assembly.test.ts +0 -298
- package/core/__tests__/agentic/prompt-builder.test.ts +0 -243
- package/core/__tests__/agentic/response-validator.test.ts +0 -263
- package/core/__tests__/agentic/semantic-matching.test.ts +0 -131
- package/core/__tests__/agentic/smart-context.test.ts +0 -372
- package/core/__tests__/agentic/tech-normalizer.test.ts +0 -136
- package/core/__tests__/agentic/token-budget.test.ts +0 -294
- package/core/__tests__/ai-tools/formatters.test.ts +0 -476
- package/core/__tests__/domain/bm25.test.ts +0 -225
- package/core/__tests__/domain/change-propagator.test.ts +0 -100
- package/core/__tests__/domain/fibonacci.test.ts +0 -113
- package/core/__tests__/domain/file-hasher.test.ts +0 -146
- package/core/__tests__/domain/file-ranker.test.ts +0 -169
- package/core/__tests__/domain/git-cochange.test.ts +0 -121
- package/core/__tests__/domain/import-graph.test.ts +0 -156
- package/core/__tests__/domain/velocity.test.ts +0 -623
- package/core/__tests__/infrastructure/performance-tracker.test.ts +0 -328
- package/core/__tests__/schemas/model.test.ts +0 -272
- package/core/__tests__/services/dependency-validator.test.ts +0 -175
- package/core/__tests__/services/hierarchical-agent-resolver.test.ts +0 -359
- package/core/__tests__/services/nested-context-resolver.test.ts +0 -443
- package/core/__tests__/services/project-index.test.ts +0 -355
- package/core/__tests__/services/staleness-checker.test.ts +0 -204
- package/core/__tests__/storage/analysis-storage.test.ts +0 -641
- package/core/__tests__/storage/archive-storage.test.ts +0 -455
- package/core/__tests__/storage/safe-reader.test.ts +0 -262
- package/core/__tests__/storage/sqlite-migration.test.ts +0 -1016
- package/core/__tests__/storage/state-storage-feedback.test.ts +0 -463
- package/core/__tests__/storage/state-storage-history.test.ts +0 -469
- package/core/__tests__/storage/storage-manager.test.ts +0 -383
- package/core/__tests__/storage/subtask-handoff.test.ts +0 -237
- package/core/__tests__/types/fs.test.ts +0 -125
- package/core/__tests__/utils/date-helper.test.ts +0 -449
- package/core/__tests__/utils/output.test.ts +0 -278
- package/core/__tests__/utils/preserve-sections.test.ts +0 -216
- package/core/__tests__/utils/project-commands.test.ts +0 -71
- package/core/__tests__/utils/retry.test.ts +0 -381
- package/core/__tests__/workflow/state-machine.test.ts +0 -216
- package/core/agentic/agent-router.ts +0 -150
- package/core/agentic/anti-hallucination.ts +0 -141
- package/core/agentic/chain-of-thought.ts +0 -234
- package/core/agentic/command-classifier.ts +0 -141
- package/core/agentic/command-context.ts +0 -168
- package/core/agentic/command-executor.ts +0 -471
- package/core/agentic/context-builder.ts +0 -285
- package/core/agentic/domain-classifier.ts +0 -525
- package/core/agentic/environment-block.ts +0 -102
- package/core/agentic/ground-truth.ts +0 -706
- package/core/agentic/index.ts +0 -193
- package/core/agentic/injection-validator.ts +0 -208
- package/core/agentic/loop-detector.ts +0 -451
- package/core/agentic/memory-system.ts +0 -1547
- package/core/agentic/orchestrator-executor.ts +0 -579
- package/core/agentic/plan-mode.ts +0 -525
- package/core/agentic/prompt-builder.ts +0 -1069
- package/core/agentic/response-validator.ts +0 -98
- package/core/agentic/services.ts +0 -167
- package/core/agentic/skill-loader.ts +0 -106
- package/core/agentic/smart-context.ts +0 -393
- package/core/agentic/tech-normalizer.ts +0 -167
- package/core/agentic/template-executor.ts +0 -272
- package/core/agentic/template-loader.ts +0 -109
- package/core/agentic/token-budget.ts +0 -226
- package/core/agentic/tool-registry.ts +0 -146
- package/core/agents/index.ts +0 -28
- package/core/agents/performance.ts +0 -429
- package/core/ai-tools/formatters.ts +0 -341
- package/core/ai-tools/generator.ts +0 -144
- package/core/ai-tools/index.ts +0 -15
- package/core/ai-tools/registry.ts +0 -201
- package/core/bus/bus.ts +0 -314
- package/core/bus/index.ts +0 -8
- package/core/cli/linear.ts +0 -500
- package/core/cli/lint-meta-commentary.ts +0 -177
- package/core/cli/start.ts +0 -386
- package/core/commands/analysis.ts +0 -1274
- package/core/commands/analytics.ts +0 -342
- package/core/commands/base.ts +0 -118
- package/core/commands/cleanup.ts +0 -157
- package/core/commands/command-data.ts +0 -463
- package/core/commands/commands.ts +0 -306
- package/core/commands/context.ts +0 -238
- package/core/commands/design.ts +0 -77
- package/core/commands/index.ts +0 -19
- package/core/commands/maintenance.ts +0 -77
- package/core/commands/performance.ts +0 -114
- package/core/commands/planning.ts +0 -662
- package/core/commands/register.ts +0 -127
- package/core/commands/registry.ts +0 -444
- package/core/commands/setup.ts +0 -280
- package/core/commands/shipping.ts +0 -267
- package/core/commands/snapshots.ts +0 -297
- package/core/commands/uninstall.ts +0 -542
- package/core/commands/velocity.ts +0 -149
- package/core/commands/workflow.ts +0 -505
- package/core/config/command-context.config.json +0 -66
- package/core/constants/index.ts +0 -379
- package/core/context/generator.ts +0 -368
- package/core/context-tools/files-tool.ts +0 -577
- package/core/context-tools/imports-tool.ts +0 -400
- package/core/context-tools/index.ts +0 -434
- package/core/context-tools/recent-tool.ts +0 -301
- package/core/context-tools/signatures-tool.ts +0 -495
- package/core/context-tools/summary-tool.ts +0 -301
- package/core/context-tools/token-counter.ts +0 -273
- package/core/context-tools/types.ts +0 -253
- package/core/domain/agent-generator.ts +0 -186
- package/core/domain/agent-loader.ts +0 -419
- package/core/domain/analyzer.ts +0 -387
- package/core/domain/architecture-generator.ts +0 -108
- package/core/domain/bm25.ts +0 -525
- package/core/domain/change-propagator.ts +0 -162
- package/core/domain/context-estimator.ts +0 -175
- package/core/domain/fibonacci.ts +0 -128
- package/core/domain/file-hasher.ts +0 -296
- package/core/domain/file-ranker.ts +0 -151
- package/core/domain/git-cochange.ts +0 -250
- package/core/domain/import-graph.ts +0 -315
- package/core/domain/snapshot-manager.ts +0 -415
- package/core/domain/task-stack.ts +0 -578
- package/core/domain/velocity.ts +0 -470
- package/core/errors.ts +0 -335
- package/core/events/events.ts +0 -85
- package/core/events/index.ts +0 -8
- package/core/index.ts +0 -481
- package/core/infrastructure/agent-detector.ts +0 -135
- package/core/infrastructure/ai-provider.ts +0 -578
- package/core/infrastructure/author-detector.ts +0 -133
- package/core/infrastructure/capability-installer.ts +0 -76
- package/core/infrastructure/claude-agent.ts +0 -297
- package/core/infrastructure/command-installer.ts +0 -752
- package/core/infrastructure/config-manager.ts +0 -364
- package/core/infrastructure/editors-config.ts +0 -172
- package/core/infrastructure/path-manager.ts +0 -571
- package/core/infrastructure/performance-tracker.ts +0 -326
- package/core/infrastructure/permission-manager.ts +0 -289
- package/core/infrastructure/setup.ts +0 -1061
- package/core/infrastructure/update-checker.ts +0 -246
- package/core/integrations/issue-tracker/enricher.ts +0 -271
- package/core/integrations/issue-tracker/index.ts +0 -8
- package/core/integrations/issue-tracker/manager.ts +0 -286
- package/core/integrations/issue-tracker/types.ts +0 -310
- package/core/integrations/jira/cache.ts +0 -57
- package/core/integrations/jira/client.ts +0 -688
- package/core/integrations/jira/index.ts +0 -23
- package/core/integrations/jira/service.ts +0 -244
- package/core/integrations/linear/cache.ts +0 -68
- package/core/integrations/linear/client.ts +0 -436
- package/core/integrations/linear/index.ts +0 -20
- package/core/integrations/linear/service.ts +0 -260
- package/core/integrations/linear/sync.ts +0 -314
- package/core/outcomes/analyzer.ts +0 -286
- package/core/outcomes/index.ts +0 -34
- package/core/outcomes/recorder.ts +0 -195
- package/core/plugin/builtin/webhook.ts +0 -148
- package/core/plugin/hooks.ts +0 -315
- package/core/plugin/index.ts +0 -50
- package/core/plugin/loader.ts +0 -354
- package/core/plugin/registry.ts +0 -326
- package/core/schemas/agents.ts +0 -27
- package/core/schemas/analysis.ts +0 -530
- package/core/schemas/classification.ts +0 -91
- package/core/schemas/command-context.ts +0 -29
- package/core/schemas/enriched-task.ts +0 -291
- package/core/schemas/ideas.ts +0 -114
- package/core/schemas/index.ts +0 -53
- package/core/schemas/issues.ts +0 -159
- package/core/schemas/llm-output.ts +0 -170
- package/core/schemas/metrics.ts +0 -143
- package/core/schemas/model.ts +0 -153
- package/core/schemas/outcomes.ts +0 -487
- package/core/schemas/performance.ts +0 -128
- package/core/schemas/permissions.ts +0 -180
- package/core/schemas/prd.ts +0 -450
- package/core/schemas/project.ts +0 -57
- package/core/schemas/roadmap.ts +0 -322
- package/core/schemas/schemas.ts +0 -38
- package/core/schemas/shipped.ts +0 -109
- package/core/schemas/state.ts +0 -284
- package/core/schemas/velocity.ts +0 -103
- package/core/server/index.ts +0 -21
- package/core/server/routes-extended.ts +0 -566
- package/core/server/routes.ts +0 -176
- package/core/server/server.ts +0 -149
- package/core/server/sse.ts +0 -192
- package/core/services/agent-generator.ts +0 -385
- package/core/services/agent-service.ts +0 -168
- package/core/services/breakdown-service.ts +0 -124
- package/core/services/context-generator.ts +0 -445
- package/core/services/context-selector.ts +0 -429
- package/core/services/dependency-validator.ts +0 -318
- package/core/services/diff-generator.ts +0 -313
- package/core/services/doctor-service.ts +0 -423
- package/core/services/file-categorizer.ts +0 -448
- package/core/services/file-scorer.ts +0 -270
- package/core/services/git-analyzer.ts +0 -293
- package/core/services/hierarchical-agent-resolver.ts +0 -236
- package/core/services/hooks-service.ts +0 -685
- package/core/services/index.ts +0 -46
- package/core/services/local-state-generator.ts +0 -158
- package/core/services/memory-service.ts +0 -181
- package/core/services/nested-context-resolver.ts +0 -842
- package/core/services/project-index.ts +0 -911
- package/core/services/project-service.ts +0 -155
- package/core/services/session-tracker.ts +0 -287
- package/core/services/skill-installer.ts +0 -447
- package/core/services/skill-lock.ts +0 -132
- package/core/services/skill-service.ts +0 -306
- package/core/services/stack-detector.ts +0 -229
- package/core/services/staleness-checker.ts +0 -327
- package/core/services/sync-service.ts +0 -1515
- package/core/services/sync-verifier.ts +0 -253
- package/core/services/watch-service.ts +0 -312
- package/core/session/compaction.ts +0 -248
- package/core/session/index.ts +0 -35
- package/core/session/log-migration.ts +0 -88
- package/core/session/metrics.ts +0 -323
- package/core/session/session-log-manager.ts +0 -307
- package/core/session/task-session-manager.ts +0 -404
- package/core/session/utils.ts +0 -51
- package/core/storage/analysis-storage.ts +0 -373
- package/core/storage/archive-storage.ts +0 -205
- package/core/storage/database.ts +0 -575
- package/core/storage/ideas-storage.ts +0 -298
- package/core/storage/index-storage.ts +0 -523
- package/core/storage/index.ts +0 -79
- package/core/storage/metrics-storage.ts +0 -321
- package/core/storage/migrate-json.ts +0 -720
- package/core/storage/queue-storage.ts +0 -336
- package/core/storage/safe-reader.ts +0 -105
- package/core/storage/shipped-storage.ts +0 -253
- package/core/storage/state-storage.ts +0 -1035
- package/core/storage/storage-manager.ts +0 -205
- package/core/storage/storage.ts +0 -177
- package/core/storage/velocity-storage.ts +0 -149
- package/core/sync/auth-config.ts +0 -138
- package/core/sync/index.ts +0 -31
- package/core/sync/oauth-handler.ts +0 -143
- package/core/sync/sync-client.ts +0 -251
- package/core/sync/sync-manager.ts +0 -327
- package/core/tsconfig.json +0 -22
- package/core/types/agentic.ts +0 -760
- package/core/types/agents.ts +0 -150
- package/core/types/bus.ts +0 -193
- package/core/types/citations.ts +0 -22
- package/core/types/commands.ts +0 -399
- package/core/types/config.ts +0 -92
- package/core/types/core.ts +0 -96
- package/core/types/diff.ts +0 -41
- package/core/types/domain.ts +0 -71
- package/core/types/errors.ts +0 -111
- package/core/types/events.ts +0 -42
- package/core/types/fs.ts +0 -72
- package/core/types/index.ts +0 -510
- package/core/types/infrastructure.ts +0 -210
- package/core/types/integrations.ts +0 -31
- package/core/types/jira.ts +0 -51
- package/core/types/logger.ts +0 -17
- package/core/types/memory.ts +0 -313
- package/core/types/outcomes.ts +0 -190
- package/core/types/output.ts +0 -47
- package/core/types/plugin.ts +0 -25
- package/core/types/project-sync.ts +0 -129
- package/core/types/provider.ts +0 -163
- package/core/types/server.ts +0 -71
- package/core/types/services.ts +0 -84
- package/core/types/session.ts +0 -135
- package/core/types/stack.ts +0 -19
- package/core/types/storage.ts +0 -318
- package/core/types/sync-verifier.ts +0 -33
- package/core/types/sync.ts +0 -121
- package/core/types/task.ts +0 -72
- package/core/types/template.ts +0 -24
- package/core/types/utils.ts +0 -92
- package/core/types/workflow.ts +0 -23
- package/core/utils/agent-stream.ts +0 -140
- package/core/utils/animations.ts +0 -251
- package/core/utils/branding.ts +0 -88
- package/core/utils/cache.ts +0 -187
- package/core/utils/citations.ts +0 -39
- package/core/utils/collection-filters.ts +0 -209
- package/core/utils/date-helper.ts +0 -176
- package/core/utils/error-messages.ts +0 -38
- package/core/utils/file-helper.ts +0 -277
- package/core/utils/fs-helpers.ts +0 -14
- package/core/utils/help.ts +0 -314
- package/core/utils/jsonl-helper.ts +0 -290
- package/core/utils/keychain.ts +0 -127
- package/core/utils/logger.ts +0 -77
- package/core/utils/markdown-builder.ts +0 -280
- package/core/utils/next-steps.ts +0 -95
- package/core/utils/output.ts +0 -403
- package/core/utils/preserve-sections.ts +0 -218
- package/core/utils/project-commands.ts +0 -126
- package/core/utils/project-credentials.ts +0 -143
- package/core/utils/provider-cache.ts +0 -49
- package/core/utils/retry.ts +0 -318
- package/core/utils/runtime.ts +0 -108
- package/core/utils/session-helper.ts +0 -278
- package/core/utils/subtask-table.ts +0 -227
- package/core/utils/version.ts +0 -128
- package/core/wizard/index.ts +0 -13
- package/core/wizard/onboarding.ts +0 -633
- package/core/workflow/index.ts +0 -7
- package/core/workflow/state-machine.ts +0 -198
- package/core/workflow/workflow-preferences.ts +0 -294
- package/dist/core/infrastructure/command-installer.js +0 -1141
- package/dist/core/infrastructure/editors-config.js +0 -177
- package/dist/core/infrastructure/setup.js +0 -2244
- package/dist/core/utils/version.js +0 -141
- package/templates/agentic/agent-routing.md +0 -45
- package/templates/agentic/agents/uxui.md +0 -63
- package/templates/agentic/checklist-routing.md +0 -98
- package/templates/agentic/orchestrator.md +0 -68
- package/templates/agentic/task-fragmentation.md +0 -89
- package/templates/agents/AGENTS.md +0 -68
- package/templates/analysis/analyze.md +0 -84
- package/templates/analysis/patterns.md +0 -60
- package/templates/antigravity/SKILL.md +0 -39
- package/templates/architect/discovery.md +0 -67
- package/templates/architect/phases.md +0 -59
- package/templates/checklists/architecture.md +0 -28
- package/templates/checklists/code-quality.md +0 -28
- package/templates/checklists/data.md +0 -33
- package/templates/checklists/documentation.md +0 -33
- package/templates/checklists/infrastructure.md +0 -33
- package/templates/checklists/performance.md +0 -33
- package/templates/checklists/security.md +0 -33
- package/templates/checklists/testing.md +0 -33
- package/templates/checklists/ux-ui.md +0 -37
- package/templates/commands/analyze.md +0 -56
- package/templates/commands/auth.md +0 -234
- package/templates/commands/bug.md +0 -163
- package/templates/commands/cleanup.md +0 -19
- package/templates/commands/dash.md +0 -99
- package/templates/commands/design.md +0 -15
- package/templates/commands/done.md +0 -291
- package/templates/commands/enrich.md +0 -174
- package/templates/commands/git.md +0 -295
- package/templates/commands/history.md +0 -389
- package/templates/commands/idea.md +0 -88
- package/templates/commands/impact.md +0 -864
- package/templates/commands/init.md +0 -54
- package/templates/commands/jira.md +0 -278
- package/templates/commands/linear.md +0 -288
- package/templates/commands/merge.md +0 -206
- package/templates/commands/next.md +0 -80
- package/templates/commands/p.md +0 -67
- package/templates/commands/p.toml +0 -37
- package/templates/commands/pause.md +0 -136
- package/templates/commands/plan.md +0 -696
- package/templates/commands/prd.md +0 -356
- package/templates/commands/resume.md +0 -171
- package/templates/commands/review.md +0 -276
- package/templates/commands/serve.md +0 -118
- package/templates/commands/setup.md +0 -91
- package/templates/commands/ship.md +0 -475
- package/templates/commands/skill.md +0 -259
- package/templates/commands/spec.md +0 -218
- package/templates/commands/status.md +0 -207
- package/templates/commands/sync.md +0 -104
- package/templates/commands/task.md +0 -312
- package/templates/commands/test.md +0 -93
- package/templates/commands/update.md +0 -63
- package/templates/commands/verify.md +0 -204
- package/templates/commands/workflow.md +0 -150
- package/templates/config/skill-mappings.json +0 -82
- package/templates/context/dashboard.md +0 -256
- package/templates/context/roadmap.md +0 -221
- package/templates/cursor/commands/bug.md +0 -8
- package/templates/cursor/commands/done.md +0 -4
- package/templates/cursor/commands/pause.md +0 -6
- package/templates/cursor/commands/resume.md +0 -4
- package/templates/cursor/commands/ship.md +0 -8
- package/templates/cursor/commands/sync.md +0 -4
- package/templates/cursor/commands/task.md +0 -8
- package/templates/cursor/p.md +0 -29
- package/templates/cursor/router.mdc +0 -28
- package/templates/design/api.md +0 -95
- package/templates/design/architecture.md +0 -77
- package/templates/design/component.md +0 -89
- package/templates/design/database.md +0 -78
- package/templates/design/flow.md +0 -94
- package/templates/global/ANTIGRAVITY.md +0 -254
- package/templates/global/CLAUDE.md +0 -497
- package/templates/global/CURSOR.mdc +0 -266
- package/templates/global/GEMINI.md +0 -293
- package/templates/global/STORAGE-SPEC.md +0 -391
- package/templates/global/WINDSURF.md +0 -266
- package/templates/global/modules/CLAUDE-commands.md +0 -70
- package/templates/global/modules/CLAUDE-core.md +0 -105
- package/templates/global/modules/CLAUDE-git.md +0 -50
- package/templates/global/modules/CLAUDE-intelligence.md +0 -92
- package/templates/global/modules/CLAUDE-storage.md +0 -50
- package/templates/global/modules/module-config.json +0 -36
- package/templates/mcp-config.json +0 -19
- package/templates/permissions/default.jsonc +0 -60
- package/templates/permissions/permissive.jsonc +0 -49
- package/templates/permissions/strict.jsonc +0 -58
- package/templates/planning-methodology.md +0 -195
- package/templates/skills/code-review.md +0 -47
- package/templates/skills/debug.md +0 -61
- package/templates/skills/refactor.md +0 -47
- package/templates/subagents/agent-base.md +0 -20
- package/templates/subagents/domain/backend.md +0 -109
- package/templates/subagents/domain/database.md +0 -121
- package/templates/subagents/domain/devops.md +0 -152
- package/templates/subagents/domain/frontend.md +0 -103
- package/templates/subagents/domain/testing.md +0 -169
- package/templates/subagents/pm-expert.md +0 -366
- package/templates/subagents/workflow/chief-architect.md +0 -657
- package/templates/subagents/workflow/prjct-planner.md +0 -159
- package/templates/subagents/workflow/prjct-shipper.md +0 -188
- package/templates/subagents/workflow/prjct-workflow.md +0 -98
- package/templates/tools/bash.txt +0 -22
- package/templates/tools/edit.txt +0 -18
- package/templates/tools/glob.txt +0 -19
- package/templates/tools/grep.txt +0 -21
- package/templates/tools/read.txt +0 -14
- package/templates/tools/task.txt +0 -20
- package/templates/tools/webfetch.txt +0 -16
- package/templates/tools/websearch.txt +0 -18
- package/templates/tools/write.txt +0 -17
- package/templates/windsurf/router.md +0 -28
- package/templates/windsurf/workflows/bug.md +0 -8
- package/templates/windsurf/workflows/done.md +0 -4
- package/templates/windsurf/workflows/pause.md +0 -4
- package/templates/windsurf/workflows/resume.md +0 -4
- package/templates/windsurf/workflows/ship.md +0 -8
- package/templates/windsurf/workflows/sync.md +0 -4
- package/templates/windsurf/workflows/task.md +0 -8
|
@@ -1,685 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* HooksService - Git hooks integration for auto-sync
|
|
3
|
-
*
|
|
4
|
-
* Manages git hooks that automatically sync prjct context on
|
|
5
|
-
* commit and checkout. Supports multiple hook managers:
|
|
6
|
-
* - lefthook
|
|
7
|
-
* - husky
|
|
8
|
-
* - direct .git/hooks/ scripts
|
|
9
|
-
*
|
|
10
|
-
* @see PRJ-128
|
|
11
|
-
* @module services/hooks-service
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import fs from 'node:fs/promises'
|
|
15
|
-
import path from 'node:path'
|
|
16
|
-
import chalk from 'chalk'
|
|
17
|
-
import configManager from '../infrastructure/config-manager'
|
|
18
|
-
import { getErrorMessage } from '../types/fs'
|
|
19
|
-
import { fileExists } from '../utils/fs-helpers'
|
|
20
|
-
import out from '../utils/output'
|
|
21
|
-
|
|
22
|
-
// ============================================================================
|
|
23
|
-
// TYPES
|
|
24
|
-
// ============================================================================
|
|
25
|
-
|
|
26
|
-
export type HookStrategy = 'lefthook' | 'husky' | 'direct'
|
|
27
|
-
export type HookName = 'post-commit' | 'post-checkout'
|
|
28
|
-
|
|
29
|
-
interface HookConfig {
|
|
30
|
-
enabled: boolean
|
|
31
|
-
strategy: HookStrategy
|
|
32
|
-
hooks: HookName[]
|
|
33
|
-
installedAt?: string
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
interface HooksStatusResult {
|
|
37
|
-
installed: boolean
|
|
38
|
-
strategy: HookStrategy | null
|
|
39
|
-
hooks: Array<{
|
|
40
|
-
name: HookName
|
|
41
|
-
installed: boolean
|
|
42
|
-
path: string
|
|
43
|
-
}>
|
|
44
|
-
detectedManagers: HookStrategy[]
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
interface HooksInstallResult {
|
|
48
|
-
success: boolean
|
|
49
|
-
strategy: HookStrategy
|
|
50
|
-
hooksInstalled: HookName[]
|
|
51
|
-
error?: string
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// ============================================================================
|
|
55
|
-
// HOOK SCRIPT TEMPLATES
|
|
56
|
-
// ============================================================================
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Shell script for post-commit hook
|
|
60
|
-
* Runs prjct sync in quiet mode with rate limiting
|
|
61
|
-
*/
|
|
62
|
-
function getPostCommitScript(): string {
|
|
63
|
-
return `#!/bin/sh
|
|
64
|
-
# prjct auto-sync hook (post-commit)
|
|
65
|
-
# Syncs project context after each commit
|
|
66
|
-
# Installed by: prjct hooks install
|
|
67
|
-
|
|
68
|
-
# Rate limit: skip if synced within last 30 seconds
|
|
69
|
-
LOCK_FILE="\${TMPDIR:-/tmp}/prjct-sync-$(pwd | md5sum 2>/dev/null | cut -d' ' -f1 || md5 -q -s "$(pwd)").lock"
|
|
70
|
-
if [ -f "$LOCK_FILE" ]; then
|
|
71
|
-
LOCK_AGE=$(( $(date +%s) - $(stat -f%m "$LOCK_FILE" 2>/dev/null || stat -c%Y "$LOCK_FILE" 2>/dev/null || echo 0) ))
|
|
72
|
-
if [ "$LOCK_AGE" -lt 30 ]; then
|
|
73
|
-
exit 0
|
|
74
|
-
fi
|
|
75
|
-
fi
|
|
76
|
-
|
|
77
|
-
# Run sync in background, suppress all output
|
|
78
|
-
if command -v prjct >/dev/null 2>&1; then
|
|
79
|
-
touch "$LOCK_FILE"
|
|
80
|
-
prjct sync --quiet --yes >/dev/null 2>&1 &
|
|
81
|
-
fi
|
|
82
|
-
|
|
83
|
-
exit 0
|
|
84
|
-
`
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Shell script for post-checkout hook
|
|
89
|
-
* Syncs project context after branch switch
|
|
90
|
-
*/
|
|
91
|
-
function getPostCheckoutScript(): string {
|
|
92
|
-
return `#!/bin/sh
|
|
93
|
-
# prjct auto-sync hook (post-checkout)
|
|
94
|
-
# Syncs project context after branch switch
|
|
95
|
-
# Installed by: prjct hooks install
|
|
96
|
-
|
|
97
|
-
# Only run on branch checkout (not file checkout)
|
|
98
|
-
# $3 is the checkout type flag: 1 = branch, 0 = file
|
|
99
|
-
if [ "$3" != "1" ]; then
|
|
100
|
-
exit 0
|
|
101
|
-
fi
|
|
102
|
-
|
|
103
|
-
# Skip if old and new refs are the same (no actual branch change)
|
|
104
|
-
if [ "$1" = "$2" ]; then
|
|
105
|
-
exit 0
|
|
106
|
-
fi
|
|
107
|
-
|
|
108
|
-
# Rate limit: skip if synced within last 30 seconds
|
|
109
|
-
LOCK_FILE="\${TMPDIR:-/tmp}/prjct-sync-$(pwd | md5sum 2>/dev/null | cut -d' ' -f1 || md5 -q -s "$(pwd)").lock"
|
|
110
|
-
if [ -f "$LOCK_FILE" ]; then
|
|
111
|
-
LOCK_AGE=$(( $(date +%s) - $(stat -f%m "$LOCK_FILE" 2>/dev/null || stat -c%Y "$LOCK_FILE" 2>/dev/null || echo 0) ))
|
|
112
|
-
if [ "$LOCK_AGE" -lt 30 ]; then
|
|
113
|
-
exit 0
|
|
114
|
-
fi
|
|
115
|
-
fi
|
|
116
|
-
|
|
117
|
-
# Run sync in background, suppress all output
|
|
118
|
-
if command -v prjct >/dev/null 2>&1; then
|
|
119
|
-
touch "$LOCK_FILE"
|
|
120
|
-
prjct sync --quiet --yes >/dev/null 2>&1 &
|
|
121
|
-
fi
|
|
122
|
-
|
|
123
|
-
exit 0
|
|
124
|
-
`
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// ============================================================================
|
|
128
|
-
// HOOK MANAGER DETECTION
|
|
129
|
-
// ============================================================================
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Detect which hook managers are available in the project
|
|
133
|
-
*/
|
|
134
|
-
async function detectHookManagers(projectPath: string): Promise<HookStrategy[]> {
|
|
135
|
-
const detected: HookStrategy[] = []
|
|
136
|
-
|
|
137
|
-
// Check for lefthook
|
|
138
|
-
if (
|
|
139
|
-
(await fileExists(path.join(projectPath, 'lefthook.yml'))) ||
|
|
140
|
-
(await fileExists(path.join(projectPath, 'lefthook.yaml')))
|
|
141
|
-
) {
|
|
142
|
-
detected.push('lefthook')
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Check for husky
|
|
146
|
-
if (
|
|
147
|
-
(await fileExists(path.join(projectPath, '.husky'))) ||
|
|
148
|
-
(await fileExists(path.join(projectPath, '.husky', '_')))
|
|
149
|
-
) {
|
|
150
|
-
detected.push('husky')
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Direct .git/hooks is always available if it's a git repo
|
|
154
|
-
if (await fileExists(path.join(projectPath, '.git'))) {
|
|
155
|
-
detected.push('direct')
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return detected
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Select the best hook strategy based on what's available
|
|
163
|
-
*/
|
|
164
|
-
function selectStrategy(detected: HookStrategy[]): HookStrategy {
|
|
165
|
-
// Prefer managed hook tools over direct
|
|
166
|
-
if (detected.includes('lefthook')) return 'lefthook'
|
|
167
|
-
if (detected.includes('husky')) return 'husky'
|
|
168
|
-
return 'direct'
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// ============================================================================
|
|
172
|
-
// INSTALLATION STRATEGIES
|
|
173
|
-
// ============================================================================
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Install hooks via lefthook (append to existing config)
|
|
177
|
-
*/
|
|
178
|
-
async function installLefthook(projectPath: string, hooks: HookName[]): Promise<boolean> {
|
|
179
|
-
const configFile = (await fileExists(path.join(projectPath, 'lefthook.yml')))
|
|
180
|
-
? 'lefthook.yml'
|
|
181
|
-
: 'lefthook.yaml'
|
|
182
|
-
const configPath = path.join(projectPath, configFile)
|
|
183
|
-
|
|
184
|
-
let content = await fs.readFile(configPath, 'utf-8')
|
|
185
|
-
|
|
186
|
-
for (const hook of hooks) {
|
|
187
|
-
const sectionName = hook // e.g. "post-commit"
|
|
188
|
-
const commandName = `prjct-sync-${hook}`
|
|
189
|
-
|
|
190
|
-
// Check if already configured
|
|
191
|
-
if (content.includes(commandName)) {
|
|
192
|
-
continue
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const hookBlock = `
|
|
196
|
-
${sectionName}:
|
|
197
|
-
commands:
|
|
198
|
-
${commandName}:
|
|
199
|
-
run: prjct sync --quiet --yes
|
|
200
|
-
fail_text: "prjct sync failed (non-blocking)"
|
|
201
|
-
`
|
|
202
|
-
|
|
203
|
-
// If the hook section already exists, add command to it
|
|
204
|
-
const sectionRegex = new RegExp(`^${sectionName}:\\s*$`, 'm')
|
|
205
|
-
if (sectionRegex.test(content)) {
|
|
206
|
-
// Insert command into existing section
|
|
207
|
-
content = content.replace(
|
|
208
|
-
sectionRegex,
|
|
209
|
-
`${sectionName}:\n commands:\n ${commandName}:\n run: prjct sync --quiet --yes\n fail_text: "prjct sync failed (non-blocking)"`
|
|
210
|
-
)
|
|
211
|
-
} else {
|
|
212
|
-
// Append new section
|
|
213
|
-
content = `${content.trimEnd()}\n${hookBlock}`
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
await fs.writeFile(configPath, content, 'utf-8')
|
|
218
|
-
return true
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Install hooks via husky
|
|
223
|
-
*/
|
|
224
|
-
async function installHusky(projectPath: string, hooks: HookName[]): Promise<boolean> {
|
|
225
|
-
const huskyDir = path.join(projectPath, '.husky')
|
|
226
|
-
|
|
227
|
-
for (const hook of hooks) {
|
|
228
|
-
const hookPath = path.join(huskyDir, hook)
|
|
229
|
-
const script = hook === 'post-commit' ? getPostCommitScript() : getPostCheckoutScript()
|
|
230
|
-
|
|
231
|
-
if (await fileExists(hookPath)) {
|
|
232
|
-
// Append to existing hook if not already present
|
|
233
|
-
const existing = await fs.readFile(hookPath, 'utf-8')
|
|
234
|
-
if (existing.includes('prjct sync')) {
|
|
235
|
-
continue
|
|
236
|
-
}
|
|
237
|
-
await fs.appendFile(hookPath, '\n# prjct auto-sync\nprjct sync --quiet --yes &\n')
|
|
238
|
-
} else {
|
|
239
|
-
await fs.writeFile(hookPath, script, { mode: 0o755 })
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
return true
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* Install hooks directly into .git/hooks/
|
|
248
|
-
*/
|
|
249
|
-
async function installDirect(projectPath: string, hooks: HookName[]): Promise<boolean> {
|
|
250
|
-
const hooksDir = path.join(projectPath, '.git', 'hooks')
|
|
251
|
-
|
|
252
|
-
if (!(await fileExists(hooksDir))) {
|
|
253
|
-
await fs.mkdir(hooksDir, { recursive: true })
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
for (const hook of hooks) {
|
|
257
|
-
const hookPath = path.join(hooksDir, hook)
|
|
258
|
-
const script = hook === 'post-commit' ? getPostCommitScript() : getPostCheckoutScript()
|
|
259
|
-
|
|
260
|
-
if (await fileExists(hookPath)) {
|
|
261
|
-
const existing = await fs.readFile(hookPath, 'utf-8')
|
|
262
|
-
if (existing.includes('prjct sync')) {
|
|
263
|
-
continue // Already installed
|
|
264
|
-
}
|
|
265
|
-
// Append to existing hook
|
|
266
|
-
await fs.appendFile(
|
|
267
|
-
hookPath,
|
|
268
|
-
`\n# prjct auto-sync\n${script.split('\n').slice(1).join('\n')}`
|
|
269
|
-
)
|
|
270
|
-
} else {
|
|
271
|
-
await fs.writeFile(hookPath, script, { mode: 0o755 })
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
return true
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// ============================================================================
|
|
279
|
-
// UNINSTALL STRATEGIES
|
|
280
|
-
// ============================================================================
|
|
281
|
-
|
|
282
|
-
async function uninstallLefthook(projectPath: string): Promise<boolean> {
|
|
283
|
-
const configFile = (await fileExists(path.join(projectPath, 'lefthook.yml')))
|
|
284
|
-
? 'lefthook.yml'
|
|
285
|
-
: 'lefthook.yaml'
|
|
286
|
-
const configPath = path.join(projectPath, configFile)
|
|
287
|
-
|
|
288
|
-
if (!(await fileExists(configPath))) return false
|
|
289
|
-
|
|
290
|
-
let content = await fs.readFile(configPath, 'utf-8')
|
|
291
|
-
|
|
292
|
-
// Remove prjct-sync commands
|
|
293
|
-
content = content.replace(/\s*prjct-sync-[\w-]+:[\s\S]*?(?=\n\S|\n*$)/g, '')
|
|
294
|
-
|
|
295
|
-
// Clean up empty sections
|
|
296
|
-
content = content.replace(/^(post-commit|post-checkout):\s*commands:\s*$/gm, '')
|
|
297
|
-
|
|
298
|
-
await fs.writeFile(configPath, `${content.trimEnd()}\n`, 'utf-8')
|
|
299
|
-
return true
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
async function uninstallHusky(projectPath: string): Promise<boolean> {
|
|
303
|
-
const huskyDir = path.join(projectPath, '.husky')
|
|
304
|
-
|
|
305
|
-
for (const hook of ['post-commit', 'post-checkout'] as HookName[]) {
|
|
306
|
-
const hookPath = path.join(huskyDir, hook)
|
|
307
|
-
if (!(await fileExists(hookPath))) continue
|
|
308
|
-
|
|
309
|
-
const content = await fs.readFile(hookPath, 'utf-8')
|
|
310
|
-
if (!content.includes('prjct sync')) continue
|
|
311
|
-
|
|
312
|
-
// Remove prjct lines
|
|
313
|
-
const cleaned = content
|
|
314
|
-
.split('\n')
|
|
315
|
-
.filter((line) => !line.includes('prjct sync') && !line.includes('prjct auto-sync'))
|
|
316
|
-
.join('\n')
|
|
317
|
-
|
|
318
|
-
if (cleaned.trim() === '#!/bin/sh' || cleaned.trim() === '#!/usr/bin/env sh') {
|
|
319
|
-
// Hook is now empty, remove it
|
|
320
|
-
await fs.unlink(hookPath)
|
|
321
|
-
} else {
|
|
322
|
-
await fs.writeFile(hookPath, cleaned, { mode: 0o755 })
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
return true
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
async function uninstallDirect(projectPath: string): Promise<boolean> {
|
|
330
|
-
const hooksDir = path.join(projectPath, '.git', 'hooks')
|
|
331
|
-
|
|
332
|
-
for (const hook of ['post-commit', 'post-checkout'] as HookName[]) {
|
|
333
|
-
const hookPath = path.join(hooksDir, hook)
|
|
334
|
-
if (!(await fileExists(hookPath))) continue
|
|
335
|
-
|
|
336
|
-
const content = await fs.readFile(hookPath, 'utf-8')
|
|
337
|
-
if (!content.includes('prjct sync')) continue
|
|
338
|
-
|
|
339
|
-
if (content.includes('Installed by: prjct hooks install')) {
|
|
340
|
-
// Entirely ours, remove it
|
|
341
|
-
await fs.unlink(hookPath)
|
|
342
|
-
} else {
|
|
343
|
-
// Shared hook, just remove our lines
|
|
344
|
-
const cleaned = content
|
|
345
|
-
.split('\n')
|
|
346
|
-
.filter((line) => !line.includes('prjct sync') && !line.includes('prjct auto-sync'))
|
|
347
|
-
.join('\n')
|
|
348
|
-
await fs.writeFile(hookPath, cleaned, { mode: 0o755 })
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
return true
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
// ============================================================================
|
|
356
|
-
// HOOKS SERVICE
|
|
357
|
-
// ============================================================================
|
|
358
|
-
|
|
359
|
-
class HooksService {
|
|
360
|
-
/**
|
|
361
|
-
* Install git hooks for auto-sync
|
|
362
|
-
*/
|
|
363
|
-
async install(
|
|
364
|
-
projectPath: string,
|
|
365
|
-
options: { strategy?: HookStrategy; hooks?: HookName[] } = {}
|
|
366
|
-
): Promise<HooksInstallResult> {
|
|
367
|
-
const hooks: HookName[] = options.hooks || ['post-commit', 'post-checkout']
|
|
368
|
-
|
|
369
|
-
// Detect available managers
|
|
370
|
-
const detected = await detectHookManagers(projectPath)
|
|
371
|
-
|
|
372
|
-
if (detected.length === 0) {
|
|
373
|
-
return {
|
|
374
|
-
success: false,
|
|
375
|
-
strategy: 'direct',
|
|
376
|
-
hooksInstalled: [],
|
|
377
|
-
error: 'Not a git repository. Run "git init" first.',
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
const strategy = options.strategy || selectStrategy(detected)
|
|
382
|
-
|
|
383
|
-
try {
|
|
384
|
-
let success = false
|
|
385
|
-
|
|
386
|
-
switch (strategy) {
|
|
387
|
-
case 'lefthook':
|
|
388
|
-
success = await installLefthook(projectPath, hooks)
|
|
389
|
-
break
|
|
390
|
-
case 'husky':
|
|
391
|
-
success = await installHusky(projectPath, hooks)
|
|
392
|
-
break
|
|
393
|
-
case 'direct':
|
|
394
|
-
success = await installDirect(projectPath, hooks)
|
|
395
|
-
break
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
if (success) {
|
|
399
|
-
// Save hook config to project.json
|
|
400
|
-
await this.saveHookConfig(projectPath, {
|
|
401
|
-
enabled: true,
|
|
402
|
-
strategy,
|
|
403
|
-
hooks,
|
|
404
|
-
installedAt: new Date().toISOString(),
|
|
405
|
-
})
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
return {
|
|
409
|
-
success,
|
|
410
|
-
strategy,
|
|
411
|
-
hooksInstalled: success ? hooks : [],
|
|
412
|
-
}
|
|
413
|
-
} catch (error) {
|
|
414
|
-
return {
|
|
415
|
-
success: false,
|
|
416
|
-
strategy,
|
|
417
|
-
hooksInstalled: [],
|
|
418
|
-
error: getErrorMessage(error),
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
/**
|
|
424
|
-
* Uninstall git hooks
|
|
425
|
-
*/
|
|
426
|
-
async uninstall(projectPath: string): Promise<{ success: boolean; error?: string }> {
|
|
427
|
-
try {
|
|
428
|
-
// Read current config to determine strategy
|
|
429
|
-
const config = await this.getHookConfig(projectPath)
|
|
430
|
-
const strategy = config?.strategy || 'direct'
|
|
431
|
-
|
|
432
|
-
let success = false
|
|
433
|
-
|
|
434
|
-
switch (strategy) {
|
|
435
|
-
case 'lefthook':
|
|
436
|
-
success = await uninstallLefthook(projectPath)
|
|
437
|
-
break
|
|
438
|
-
case 'husky':
|
|
439
|
-
success = await uninstallHusky(projectPath)
|
|
440
|
-
break
|
|
441
|
-
case 'direct':
|
|
442
|
-
success = await uninstallDirect(projectPath)
|
|
443
|
-
break
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
// Clear hook config
|
|
447
|
-
if (success) {
|
|
448
|
-
await this.saveHookConfig(projectPath, {
|
|
449
|
-
enabled: false,
|
|
450
|
-
strategy,
|
|
451
|
-
hooks: [],
|
|
452
|
-
})
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
return { success }
|
|
456
|
-
} catch (error) {
|
|
457
|
-
return { success: false, error: getErrorMessage(error) }
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
/**
|
|
462
|
-
* Get hook installation status
|
|
463
|
-
*/
|
|
464
|
-
async status(projectPath: string): Promise<HooksStatusResult> {
|
|
465
|
-
const detected = await detectHookManagers(projectPath)
|
|
466
|
-
const config = await this.getHookConfig(projectPath)
|
|
467
|
-
|
|
468
|
-
const hookNames: HookName[] = ['post-commit', 'post-checkout']
|
|
469
|
-
const hooks = await Promise.all(
|
|
470
|
-
hookNames.map(async (name) => ({
|
|
471
|
-
name,
|
|
472
|
-
installed: await this.isHookInstalled(projectPath, name, config?.strategy || null),
|
|
473
|
-
path: await this.getHookPath(projectPath, name, config?.strategy || null),
|
|
474
|
-
}))
|
|
475
|
-
)
|
|
476
|
-
|
|
477
|
-
return {
|
|
478
|
-
installed: hooks.some((h) => h.installed),
|
|
479
|
-
strategy: config?.strategy || null,
|
|
480
|
-
hooks,
|
|
481
|
-
detectedManagers: detected,
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
/**
|
|
486
|
-
* Run the hooks CLI command
|
|
487
|
-
*/
|
|
488
|
-
async run(projectPath: string, subcommand: string): Promise<number> {
|
|
489
|
-
const projectId = await configManager.getProjectId(projectPath)
|
|
490
|
-
|
|
491
|
-
if (!projectId) {
|
|
492
|
-
console.error('No prjct project found. Run "prjct init" first.')
|
|
493
|
-
return 1
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
switch (subcommand) {
|
|
497
|
-
case 'install':
|
|
498
|
-
return this.runInstall(projectPath)
|
|
499
|
-
case 'uninstall':
|
|
500
|
-
return this.runUninstall(projectPath)
|
|
501
|
-
case 'status':
|
|
502
|
-
return this.runStatus(projectPath)
|
|
503
|
-
default:
|
|
504
|
-
return this.runStatus(projectPath)
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
// ==========================================================================
|
|
509
|
-
// CLI SUBCOMMANDS
|
|
510
|
-
// ==========================================================================
|
|
511
|
-
|
|
512
|
-
private async runInstall(projectPath: string): Promise<number> {
|
|
513
|
-
out.start()
|
|
514
|
-
out.section('Git Hooks Installation')
|
|
515
|
-
|
|
516
|
-
const detected = await detectHookManagers(projectPath)
|
|
517
|
-
const strategy = selectStrategy(detected)
|
|
518
|
-
|
|
519
|
-
console.log(` Strategy: ${chalk.cyan(strategy)}`)
|
|
520
|
-
console.log(` Hooks: ${chalk.dim('post-commit, post-checkout')}`)
|
|
521
|
-
console.log('')
|
|
522
|
-
|
|
523
|
-
const result = await this.install(projectPath, { strategy })
|
|
524
|
-
|
|
525
|
-
if (result.success) {
|
|
526
|
-
out.done(`Hooks installed via ${result.strategy}`)
|
|
527
|
-
console.log('')
|
|
528
|
-
for (const hook of result.hooksInstalled) {
|
|
529
|
-
console.log(` ${chalk.green('✓')} ${hook}`)
|
|
530
|
-
}
|
|
531
|
-
console.log('')
|
|
532
|
-
console.log(chalk.dim(' Context will auto-sync on commit and branch switch.'))
|
|
533
|
-
console.log(chalk.dim(' Remove with: prjct hooks uninstall'))
|
|
534
|
-
} else {
|
|
535
|
-
out.fail(result.error || 'Failed to install hooks')
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
console.log('')
|
|
539
|
-
out.end()
|
|
540
|
-
return result.success ? 0 : 1
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
private async runUninstall(projectPath: string): Promise<number> {
|
|
544
|
-
out.start()
|
|
545
|
-
out.section('Git Hooks Removal')
|
|
546
|
-
|
|
547
|
-
const result = await this.uninstall(projectPath)
|
|
548
|
-
|
|
549
|
-
if (result.success) {
|
|
550
|
-
out.done('Hooks removed')
|
|
551
|
-
} else {
|
|
552
|
-
out.fail(result.error || 'Failed to remove hooks')
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
console.log('')
|
|
556
|
-
out.end()
|
|
557
|
-
return result.success ? 0 : 1
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
private async runStatus(projectPath: string): Promise<number> {
|
|
561
|
-
out.start()
|
|
562
|
-
out.section('Git Hooks Status')
|
|
563
|
-
|
|
564
|
-
const status = await this.status(projectPath)
|
|
565
|
-
|
|
566
|
-
if (status.installed) {
|
|
567
|
-
console.log(` Status: ${chalk.green('Active')}`)
|
|
568
|
-
console.log(` Strategy: ${chalk.cyan(status.strategy)}`)
|
|
569
|
-
} else {
|
|
570
|
-
console.log(` Status: ${chalk.dim('Not installed')}`)
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
console.log('')
|
|
574
|
-
for (const hook of status.hooks) {
|
|
575
|
-
const icon = hook.installed ? chalk.green('✓') : chalk.dim('○')
|
|
576
|
-
const label = hook.installed ? hook.name : chalk.dim(hook.name)
|
|
577
|
-
console.log(` ${icon} ${label}`)
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
if (status.detectedManagers.length > 0) {
|
|
581
|
-
console.log('')
|
|
582
|
-
console.log(` ${chalk.dim('Available managers:')} ${status.detectedManagers.join(', ')}`)
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
if (!status.installed) {
|
|
586
|
-
console.log('')
|
|
587
|
-
console.log(chalk.dim(' Install with: prjct hooks install'))
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
console.log('')
|
|
591
|
-
out.end()
|
|
592
|
-
return 0
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
// ==========================================================================
|
|
596
|
-
// HELPERS
|
|
597
|
-
// ==========================================================================
|
|
598
|
-
|
|
599
|
-
private async isHookInstalled(
|
|
600
|
-
projectPath: string,
|
|
601
|
-
hook: HookName,
|
|
602
|
-
strategy: HookStrategy | null
|
|
603
|
-
): Promise<boolean> {
|
|
604
|
-
if (strategy === 'lefthook') {
|
|
605
|
-
const configFile = (await fileExists(path.join(projectPath, 'lefthook.yml')))
|
|
606
|
-
? 'lefthook.yml'
|
|
607
|
-
: 'lefthook.yaml'
|
|
608
|
-
const configPath = path.join(projectPath, configFile)
|
|
609
|
-
if (!(await fileExists(configPath))) return false
|
|
610
|
-
const content = await fs.readFile(configPath, 'utf-8')
|
|
611
|
-
return content.includes(`prjct-sync-${hook}`)
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
if (strategy === 'husky') {
|
|
615
|
-
const hookPath = path.join(projectPath, '.husky', hook)
|
|
616
|
-
if (!(await fileExists(hookPath))) return false
|
|
617
|
-
return (await fs.readFile(hookPath, 'utf-8')).includes('prjct sync')
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
// Direct
|
|
621
|
-
const hookPath = path.join(projectPath, '.git', 'hooks', hook)
|
|
622
|
-
if (!(await fileExists(hookPath))) return false
|
|
623
|
-
return (await fs.readFile(hookPath, 'utf-8')).includes('prjct sync')
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
private async getHookPath(
|
|
627
|
-
projectPath: string,
|
|
628
|
-
hook: HookName,
|
|
629
|
-
strategy: HookStrategy | null
|
|
630
|
-
): Promise<string> {
|
|
631
|
-
if (strategy === 'lefthook') {
|
|
632
|
-
return (await fileExists(path.join(projectPath, 'lefthook.yml')))
|
|
633
|
-
? 'lefthook.yml'
|
|
634
|
-
: 'lefthook.yaml'
|
|
635
|
-
}
|
|
636
|
-
if (strategy === 'husky') {
|
|
637
|
-
return `.husky/${hook}`
|
|
638
|
-
}
|
|
639
|
-
return `.git/hooks/${hook}`
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
private async getHookConfig(projectPath: string): Promise<HookConfig | null> {
|
|
643
|
-
const projectId = await configManager.getProjectId(projectPath)
|
|
644
|
-
if (!projectId) return null
|
|
645
|
-
|
|
646
|
-
try {
|
|
647
|
-
const projectJsonPath = path.join(
|
|
648
|
-
process.env.HOME || '',
|
|
649
|
-
'.prjct-cli',
|
|
650
|
-
'projects',
|
|
651
|
-
projectId,
|
|
652
|
-
'project.json'
|
|
653
|
-
)
|
|
654
|
-
if (!(await fileExists(projectJsonPath))) return null
|
|
655
|
-
const project = JSON.parse(await fs.readFile(projectJsonPath, 'utf-8'))
|
|
656
|
-
return project.hooks || null
|
|
657
|
-
} catch {
|
|
658
|
-
return null
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
private async saveHookConfig(projectPath: string, config: HookConfig): Promise<void> {
|
|
663
|
-
const projectId = await configManager.getProjectId(projectPath)
|
|
664
|
-
if (!projectId) return
|
|
665
|
-
|
|
666
|
-
try {
|
|
667
|
-
const projectJsonPath = path.join(
|
|
668
|
-
process.env.HOME || '',
|
|
669
|
-
'.prjct-cli',
|
|
670
|
-
'projects',
|
|
671
|
-
projectId,
|
|
672
|
-
'project.json'
|
|
673
|
-
)
|
|
674
|
-
if (!(await fileExists(projectJsonPath))) return
|
|
675
|
-
|
|
676
|
-
const project = JSON.parse(await fs.readFile(projectJsonPath, 'utf-8'))
|
|
677
|
-
project.hooks = config
|
|
678
|
-
await fs.writeFile(projectJsonPath, JSON.stringify(project, null, 2))
|
|
679
|
-
} catch {
|
|
680
|
-
// Non-fatal
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
export const hooksService = new HooksService()
|