gitforest 0.1.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/.bunignore +7 -0
- package/.github/workflows/ci.yml +73 -0
- package/CLAUDE.md +111 -0
- package/CONTRIBUTING.md +145 -0
- package/README.md +168 -0
- package/bun.lock +267 -0
- package/bunfig.toml +15 -0
- package/cli +0 -0
- package/config/gitforest.example.yaml +94 -0
- package/docs/ai/IMPROVEMENT_PLAN.md +341 -0
- package/docs/ai/VERIFICATION_REPORT.md +87 -0
- package/docs/ai/architecture.md +169 -0
- package/docs/ai/checks/check-2025-12-02-tests.md +40 -0
- package/docs/ai/checks/check-2025-12-02.md +55 -0
- package/docs/ai/checks/test-verification-report.md +85 -0
- package/docs/ai/implementation-guide.md +776 -0
- package/docs/ai/research/gitty-codebase-analysis.md +221 -0
- package/docs/ai/tickets/GENERAL-sitrep.md +30 -0
- package/docs/ai/tickets/TASK-database-tests-sitrep.md +25 -0
- package/docs/ai/tickets/TASK-deprecated-functions-sitrep.md +28 -0
- package/docs/ai/tickets/TASK-detail-modal-sitrep.md +28 -0
- package/docs/ai/tickets/TASK-filter-overlay-sitrep.md +24 -0
- package/docs/ai/tickets/TASK-github-service-sitrep.md +32 -0
- package/docs/ai/tickets/TASK-github-token-sitrep.md +51 -0
- package/docs/ai/tickets/TASK-hascommits-sitrep.md +35 -0
- package/docs/ai/tickets/TASK-keybindings-sitrep.md +26 -0
- package/docs/ai/tickets/TASK-layout-sitrep.md +25 -0
- package/docs/ai/tickets/TASK-markdown-sitrep.md +28 -0
- package/docs/ai/tickets/TASK-project-item-sitrep.md +79 -0
- package/docs/ai/tickets/TASK-sitrep.md +28 -0
- package/docs/ai/tickets/TASK-state-sitrep.md +26 -0
- package/docs/ai/tickets/TASK-types-sitrep.md +25 -0
- package/docs/ai/tickets/TASK-unified-item-fix-sitrep.md +26 -0
- package/docs/ai/tickets/TKT-001-sitrep.md +24 -0
- package/docs/ai/tickets/TKT-002-sitrep.md +25 -0
- package/docs/ai/tickets/TKT-003-git-service-refactoring-complete.md +46 -0
- package/docs/ai/tickets/TKT-003-git-service-refactoring-plan.md +135 -0
- package/docs/ai/tickets/TKT-003-sitrep.md +26 -0
- package/docs/ai/tickets/TKT-004-sitrep.md +27 -0
- package/docs/ai/tickets/TKT-005-sitrep.md +25 -0
- package/docs/ai/tickets/TKT-006-sitrep.md +26 -0
- package/docs/ai/tickets/TKT-007-sitrep.md +30 -0
- package/docs/ai/tickets/TKT-008-sitrep.md +32 -0
- package/docs/ai/tickets/TKT-009-sitrep.md +27 -0
- package/docs/ai/tickets/TKT-010-sitrep.md +27 -0
- package/docs/ai/tickets/TKT-011-sitrep.md +26 -0
- package/docs/ai/tickets/TKT-012-sitrep.md +25 -0
- package/docs/ai/tickets/sitreps/TASK-actions-sitrep.md +28 -0
- package/docs/ai/tickets/sitreps/TASK-actions-test-sitrep.md +25 -0
- package/docs/ai/tickets/sitreps/TASK-app-integration-sitrep.md +25 -0
- package/docs/ai/tickets/sitreps/TASK-background-fetch-sitrep.md +24 -0
- package/docs/ai/tickets/sitreps/TASK-background-fetch-test-sitrep.md +29 -0
- package/docs/ai/tickets/sitreps/TASK-batch-tests-sitrep.md +29 -0
- package/docs/ai/tickets/sitreps/TASK-bun-test-sitrep.md +26 -0
- package/docs/ai/tickets/sitreps/TASK-cache-tests-sitrep.md +30 -0
- package/docs/ai/tickets/sitreps/TASK-cli-tests-sitrep.md +28 -0
- package/docs/ai/tickets/sitreps/TASK-clone-error-handling-sitrep.md +26 -0
- package/docs/ai/tickets/sitreps/TASK-commands-tests-sitrep.md +25 -0
- package/docs/ai/tickets/sitreps/TASK-component-tests-1-sitrep.md +30 -0
- package/docs/ai/tickets/sitreps/TASK-configloader-tests-sitrep.md +25 -0
- package/docs/ai/tickets/sitreps/TASK-confirm-dialog-test-sitrep.md +29 -0
- package/docs/ai/tickets/sitreps/TASK-coverage-sitrep.md +95 -0
- package/docs/ai/tickets/sitreps/TASK-database-tests-summary.md +61 -0
- package/docs/ai/tickets/sitreps/TASK-error-boundary-sitrep.md +30 -0
- package/docs/ai/tickets/sitreps/TASK-error-tests-sitrep.md +27 -0
- package/docs/ai/tickets/sitreps/TASK-errors-tests-sitrep.md +25 -0
- package/docs/ai/tickets/sitreps/TASK-extract-reducer-sitrep.md +27 -0
- package/docs/ai/tickets/sitreps/TASK-filter-overlay-test-sitrep.md +25 -0
- package/docs/ai/tickets/sitreps/TASK-final-verification-sitrep.md +28 -0
- package/docs/ai/tickets/sitreps/TASK-fix-all-tests-sitrep.md +25 -0
- package/docs/ai/tickets/sitreps/TASK-fix-hooks-sitrep.md +26 -0
- package/docs/ai/tickets/sitreps/TASK-fix-remaining-tests-sitrep.md +25 -0
- package/docs/ai/tickets/sitreps/TASK-fix-test-failures-sitrep.md +26 -0
- package/docs/ai/tickets/sitreps/TASK-fix-tests-sitrep.md +24 -0
- package/docs/ai/tickets/sitreps/TASK-formatters-tests-sitrep.md +25 -0
- package/docs/ai/tickets/sitreps/TASK-git-timeouts-sitrep.md +29 -0
- package/docs/ai/tickets/sitreps/TASK-github-cache-test-sitrep.md +25 -0
- package/docs/ai/tickets/sitreps/TASK-githubcli-tests-sitrep.md +24 -0
- package/docs/ai/tickets/sitreps/TASK-gitstatus-tests-sitrep.md +24 -0
- package/docs/ai/tickets/sitreps/TASK-hooks-isolation-sitrep.md +27 -0
- package/docs/ai/tickets/sitreps/TASK-keybindings-tests-sitrep.md +25 -0
- package/docs/ai/tickets/sitreps/TASK-layout-tests-sitrep.md +25 -0
- package/docs/ai/tickets/sitreps/TASK-mock-factories-sitrep.md +27 -0
- package/docs/ai/tickets/sitreps/TASK-modal-tests-sitrep.md +32 -0
- package/docs/ai/tickets/sitreps/TASK-processbatch-fix-sitrep.md +27 -0
- package/docs/ai/tickets/sitreps/TASK-projectlist-tests-sitrep.md +30 -0
- package/docs/ai/tickets/sitreps/TASK-projectutils-tests-sitrep.md +25 -0
- package/docs/ai/tickets/sitreps/TASK-scanner-tests-sitrep.md +29 -0
- package/docs/ai/tickets/sitreps/TASK-select-all-sitrep.md +25 -0
- package/docs/ai/tickets/sitreps/TASK-shell-error-handling-sitrep.md +27 -0
- package/docs/ai/tickets/sitreps/TASK-store-tests-sitrep.md +25 -0
- package/docs/ai/tickets/sitreps/TASK-test-fixes-sitrep.md +26 -0
- package/docs/ai/tickets/sitreps/TASK-test-summary-sitrep.md +25 -0
- package/docs/ai/tickets/sitreps/TASK-test-verification-sitrep.md +27 -0
- package/docs/ai/tickets/sitreps/TASK-testsuite-sitrep.md +75 -0
- package/docs/ai/tickets/sitreps/TASK-unified-reducer-tests-sitrep.md +29 -0
- package/docs/ai/tickets/sitreps/TASK-unified-repos-test-sitrep.md +29 -0
- package/docs/ai/tickets/sitreps/TASK-unified-tests-sitrep.md +25 -0
- package/docs/ai/tickets/sitreps/TASK-useprojects-tests-sitrep.md +25 -0
- package/docs/ai/tickets/sitreps/TASK-utility-tests-sitrep.md +32 -0
- package/docs/ai/tickets/sitreps/TKT-003-git-service-refactoring-sitrep.md +64 -0
- package/docs/ai/tkt-001-fix-database-error.md +217 -0
- package/docs/ai/ui-enhancement-plan.md +562 -0
- package/package.json +50 -0
- package/src/app.tsx +43 -0
- package/src/cli/config.ts +94 -0
- package/src/cli/formatters.ts +632 -0
- package/src/cli/index.ts +583 -0
- package/src/components/CloneDialog.tsx +137 -0
- package/src/components/ColumnHeader.tsx +128 -0
- package/src/components/CommandPalette.tsx +120 -0
- package/src/components/ConfirmDialog.tsx +105 -0
- package/src/components/ErrorBoundary.tsx +128 -0
- package/src/components/FilterBar.tsx +71 -0
- package/src/components/FilterOptionsOverlay.tsx +131 -0
- package/src/components/HelpOverlay.tsx +120 -0
- package/src/components/Layout.tsx +379 -0
- package/src/components/MarkdownRenderer.tsx +127 -0
- package/src/components/ProgressBar.tsx +53 -0
- package/src/components/ProjectItem.tsx +143 -0
- package/src/components/ProjectList.tsx +90 -0
- package/src/components/RepoDetailModal.tsx +367 -0
- package/src/components/StatusBar.tsx +188 -0
- package/src/components/UnifiedProjectItem.tsx +436 -0
- package/src/components/ViewModeIndicator.tsx +37 -0
- package/src/components/onboarding/CompleteStep.tsx +82 -0
- package/src/components/onboarding/DirectoriesStep.test.tsx +52 -0
- package/src/components/onboarding/DirectoriesStep.tsx +847 -0
- package/src/components/onboarding/DirectoriesStep.unit.test.ts +345 -0
- package/src/components/onboarding/GitHubAuthStep.tsx +268 -0
- package/src/components/onboarding/OnboardingWizard.tsx +130 -0
- package/src/components/onboarding/WelcomeStep.tsx +69 -0
- package/src/config/loader.ts +263 -0
- package/src/config/onboarding.ts +67 -0
- package/src/constants.ts +96 -0
- package/src/db/index.ts +147 -0
- package/src/db/schema.ts +70 -0
- package/src/git/commands.ts +283 -0
- package/src/git/index.ts +2 -0
- package/src/git/operations.ts +93 -0
- package/src/git/service.ts +539 -0
- package/src/git/status.ts +84 -0
- package/src/git/types.ts +5 -0
- package/src/github/auth.ts +311 -0
- package/src/github/cache.ts +231 -0
- package/src/github/cli.ts +22 -0
- package/src/github/unified.ts +415 -0
- package/src/hooks/useBackgroundFetch.ts +76 -0
- package/src/hooks/useConfirmDialogActions.ts +120 -0
- package/src/hooks/useKeyBindings.ts +656 -0
- package/src/hooks/useProjects.ts +47 -0
- package/src/hooks/useUnifiedRepos.ts +317 -0
- package/src/index.tsx +494 -0
- package/src/operations/batch.ts +280 -0
- package/src/operations/commands.ts +140 -0
- package/src/operations/index.ts +37 -0
- package/src/scanner/index.ts +424 -0
- package/src/scanner/markers.ts +43 -0
- package/src/scanner/submodules.ts +61 -0
- package/src/services/git.ts +484 -0
- package/src/services/github.ts +676 -0
- package/src/services/index.ts +28 -0
- package/src/services/types.ts +99 -0
- package/src/state/actions.ts +175 -0
- package/src/state/reducer.ts +294 -0
- package/src/state/store.tsx +216 -0
- package/src/state/types.ts +8 -0
- package/src/types/index.ts +383 -0
- package/src/ui/theme.ts +44 -0
- package/src/utils/array.ts +14 -0
- package/src/utils/debug.ts +38 -0
- package/src/utils/errors.ts +17 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/markdown.ts +230 -0
- package/src/utils/project-utils.ts +129 -0
- package/src/utils/rate-limiter.ts +134 -0
- package/src/utils/retry.ts +147 -0
- package/src/utils/timeout.ts +56 -0
- package/test/integration/app.isolated.tsx +240 -0
- package/test/integration/cli-commands.test.ts +287 -0
- package/test/integration/cli-validation.test.ts +264 -0
- package/test/integration/git-operations.test.ts +218 -0
- package/test/integration/scanner.test.ts +228 -0
- package/test/preload.ts +18 -0
- package/test/unit/cli/commands.test.ts +13 -0
- package/test/unit/cli/formatters.test.ts +1116 -0
- package/test/unit/cli/github-commands.test.ts +12 -0
- package/test/unit/components/CloneDialog.test.tsx +240 -0
- package/test/unit/components/ColumnHeader.test.tsx +128 -0
- package/test/unit/components/CommandPalette.test.tsx +355 -0
- package/test/unit/components/ConfirmDialog.test.tsx +111 -0
- package/test/unit/components/ErrorBoundary.test.tsx +139 -0
- package/test/unit/components/FilterBar.test.tsx +43 -0
- package/test/unit/components/FilterOptionsOverlay.test.tsx +197 -0
- package/test/unit/components/HelpOverlay.test.tsx +90 -0
- package/test/unit/components/Layout.test.tsx +328 -0
- package/test/unit/components/MarkdownRenderer.test.tsx +45 -0
- package/test/unit/components/ProgressBar.test.tsx +138 -0
- package/test/unit/components/ProjectItem.test.tsx +182 -0
- package/test/unit/components/ProjectList.test.tsx +311 -0
- package/test/unit/components/RepoDetailModal.test.tsx +445 -0
- package/test/unit/components/StatusBar.test.tsx +112 -0
- package/test/unit/components/UnifiedProjectItem.test.tsx +618 -0
- package/test/unit/components/ViewModeIndicator.test.tsx +137 -0
- package/test/unit/components/test-utils.tsx +63 -0
- package/test/unit/config/loader.test.ts +692 -0
- package/test/unit/db/database.test.ts +978 -0
- package/test/unit/db/index.test.ts +314 -0
- package/test/unit/fixtures/setup.ts +186 -0
- package/test/unit/git/commands-untested.test.ts +205 -0
- package/test/unit/git/commands.test.ts +269 -0
- package/test/unit/git/operations.test.ts +322 -0
- package/test/unit/git/status.test.ts +219 -0
- package/test/unit/github/auth.test.ts +317 -0
- package/test/unit/github/cache.test.ts +1028 -0
- package/test/unit/github/cli.test.ts +135 -0
- package/test/unit/github/unified.test.ts +1201 -0
- package/test/unit/graceful-shutdown.test.ts +83 -0
- package/test/unit/hooks/useBackgroundFetch.test.tsx +239 -0
- package/test/unit/hooks/useConfirmDialogActions.test.tsx +81 -0
- package/test/unit/hooks/useKeyBindings.isolated.ts +715 -0
- package/test/unit/hooks/useProjects.test.tsx +186 -0
- package/test/unit/hooks/useUnifiedRepos-simple.test.tsx +115 -0
- package/test/unit/hooks/useUnifiedRepos.test.tsx +177 -0
- package/test/unit/mocks/config.ts +109 -0
- package/test/unit/mocks/git-service.ts +274 -0
- package/test/unit/mocks/github-service.ts +250 -0
- package/test/unit/mocks/index.ts +72 -0
- package/test/unit/mocks/project.ts +148 -0
- package/test/unit/mocks/state-mocks.ts +187 -0
- package/test/unit/mocks/unified.ts +169 -0
- package/test/unit/operations/batch.test.ts +216 -0
- package/test/unit/operations/commands.test.ts +550 -0
- package/test/unit/scanner/errors.test.ts +297 -0
- package/test/unit/scanner/index.test.ts +1011 -0
- package/test/unit/scanner/markers.test.ts +150 -0
- package/test/unit/scanner/submodules.test.ts +99 -0
- package/test/unit/services/git-errors.test.ts +190 -0
- package/test/unit/services/git.test.ts +442 -0
- package/test/unit/services/github-errors.test.ts +293 -0
- package/test/unit/services/github.test.ts +200 -0
- package/test/unit/state/actions.test.ts +217 -0
- package/test/unit/state/reducer.test.ts +745 -0
- package/test/unit/state/store.test.tsx +711 -0
- package/test/unit/types/commands.test.ts +220 -0
- package/test/unit/types/schema.test.ts +179 -0
- package/test/unit/utils/array.test.ts +73 -0
- package/test/unit/utils/debug.test.ts +23 -0
- package/test/unit/utils/errors.test.ts +295 -0
- package/test/unit/utils/markdown.test.ts +163 -0
- package/test/unit/utils/project-utils.test.ts +756 -0
- package/test/unit/utils/rate-limiter.test.ts +256 -0
- package/test/unit/utils/retry.test.ts +165 -0
- package/test/unit/utils/strip-ansi.ts +13 -0
- package/test/unit/utils/timeout.test.ts +93 -0
- package/tsconfig.json +29 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { useState, useEffect } from "react";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import type { GitforestConfig } from "../../types/index.ts";
|
|
4
|
+
import { WelcomeStep } from "./WelcomeStep.tsx";
|
|
5
|
+
import { DirectoriesStep } from "./DirectoriesStep.tsx";
|
|
6
|
+
import { GitHubAuthStep } from "./GitHubAuthStep.tsx";
|
|
7
|
+
import { CompleteStep } from "./CompleteStep.tsx";
|
|
8
|
+
|
|
9
|
+
type Step = "welcome" | "directories" | "github" | "complete";
|
|
10
|
+
|
|
11
|
+
interface DirectoryConfig {
|
|
12
|
+
path: string;
|
|
13
|
+
maxDepth: number;
|
|
14
|
+
label?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface GitHubAuthState {
|
|
18
|
+
authenticated: boolean;
|
|
19
|
+
user?: string;
|
|
20
|
+
skipped: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface OnboardingWizardProps {
|
|
24
|
+
onComplete: (config: GitforestConfig) => void;
|
|
25
|
+
onCancel: () => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function OnboardingWizard({ onComplete, onCancel }: OnboardingWizardProps) {
|
|
29
|
+
const [currentStep, setCurrentStep] = useState<Step>("welcome");
|
|
30
|
+
const [directories, setDirectories] = useState<DirectoryConfig[]>([]);
|
|
31
|
+
const [githubAuth, setGithubAuth] = useState<GitHubAuthState>({
|
|
32
|
+
authenticated: false,
|
|
33
|
+
skipped: false,
|
|
34
|
+
});
|
|
35
|
+
const [isCreatingConfig, setIsCreatingConfig] = useState(false);
|
|
36
|
+
const [configError, setConfigError] = useState<string | null>(null);
|
|
37
|
+
|
|
38
|
+
const handleWelcomeComplete = () => {
|
|
39
|
+
setCurrentStep("directories");
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const handleDirectoriesComplete = (dirs: DirectoryConfig[]) => {
|
|
43
|
+
setDirectories(dirs);
|
|
44
|
+
setCurrentStep("github");
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const handleGitHubComplete = (auth: GitHubAuthState) => {
|
|
48
|
+
setGithubAuth(auth);
|
|
49
|
+
// Immediately create config, then show complete step
|
|
50
|
+
setIsCreatingConfig(true);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Create config when GitHub step completes
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
if (isCreatingConfig && currentStep === "github" && !configError) {
|
|
56
|
+
void (async () => {
|
|
57
|
+
try {
|
|
58
|
+
const { createOnboardingConfig } = await import("../../config/onboarding.ts");
|
|
59
|
+
const config = await createOnboardingConfig({
|
|
60
|
+
directories,
|
|
61
|
+
githubAuth: {
|
|
62
|
+
authenticated: githubAuth.authenticated,
|
|
63
|
+
user: githubAuth.user,
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
// Config created successfully, show complete step
|
|
67
|
+
setCurrentStep("complete");
|
|
68
|
+
setIsCreatingConfig(false);
|
|
69
|
+
// Auto-complete after showing success
|
|
70
|
+
setTimeout(() => {
|
|
71
|
+
onComplete(config);
|
|
72
|
+
}, 2000);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
setConfigError(error instanceof Error ? error.message : "Failed to create config");
|
|
75
|
+
setIsCreatingConfig(false);
|
|
76
|
+
}
|
|
77
|
+
})();
|
|
78
|
+
}
|
|
79
|
+
}, [isCreatingConfig, currentStep, directories, githubAuth, onComplete, configError]);
|
|
80
|
+
|
|
81
|
+
const handleCancel = () => {
|
|
82
|
+
onCancel();
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const handleBack = () => {
|
|
86
|
+
switch (currentStep) {
|
|
87
|
+
case "directories":
|
|
88
|
+
setCurrentStep("welcome");
|
|
89
|
+
break;
|
|
90
|
+
case "github":
|
|
91
|
+
setCurrentStep("directories");
|
|
92
|
+
break;
|
|
93
|
+
case "complete":
|
|
94
|
+
setCurrentStep("github");
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<Box flexDirection="column">
|
|
101
|
+
{currentStep === "welcome" && (
|
|
102
|
+
<WelcomeStep onComplete={handleWelcomeComplete} onCancel={handleCancel} />
|
|
103
|
+
)}
|
|
104
|
+
|
|
105
|
+
{currentStep === "directories" && (
|
|
106
|
+
<DirectoriesStep
|
|
107
|
+
directories={directories}
|
|
108
|
+
onComplete={handleDirectoriesComplete}
|
|
109
|
+
onBack={handleBack}
|
|
110
|
+
onCancel={handleCancel}
|
|
111
|
+
/>
|
|
112
|
+
)}
|
|
113
|
+
|
|
114
|
+
{currentStep === "github" && (
|
|
115
|
+
<GitHubAuthStep
|
|
116
|
+
onComplete={handleGitHubComplete}
|
|
117
|
+
onBack={handleBack}
|
|
118
|
+
onCancel={handleCancel}
|
|
119
|
+
/>
|
|
120
|
+
)}
|
|
121
|
+
|
|
122
|
+
{currentStep === "complete" && (
|
|
123
|
+
<CompleteStep
|
|
124
|
+
directories={directories}
|
|
125
|
+
githubAuth={githubAuth}
|
|
126
|
+
/>
|
|
127
|
+
)}
|
|
128
|
+
</Box>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Box, Text, useInput } from "ink";
|
|
2
|
+
|
|
3
|
+
export interface WelcomeStepProps {
|
|
4
|
+
onComplete: () => void;
|
|
5
|
+
onCancel: () => void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function WelcomeStep({ onComplete, onCancel }: WelcomeStepProps) {
|
|
9
|
+
useInput((input, key) => {
|
|
10
|
+
if (key.return || input === " ") {
|
|
11
|
+
onComplete();
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (input === "q" || input === "Q" || key.escape) {
|
|
16
|
+
onCancel();
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<Box
|
|
23
|
+
flexDirection="column"
|
|
24
|
+
borderStyle="round"
|
|
25
|
+
borderColor="cyan"
|
|
26
|
+
paddingX={3}
|
|
27
|
+
paddingY={2}
|
|
28
|
+
width={70}
|
|
29
|
+
>
|
|
30
|
+
{/* Title */}
|
|
31
|
+
<Box marginBottom={1}>
|
|
32
|
+
<Text bold color="cyan" underline>
|
|
33
|
+
Welcome to Gitforest!
|
|
34
|
+
</Text>
|
|
35
|
+
</Box>
|
|
36
|
+
|
|
37
|
+
<Box marginBottom={1}>
|
|
38
|
+
<Text>Git Repository Manager for the Terminal</Text>
|
|
39
|
+
</Box>
|
|
40
|
+
|
|
41
|
+
{/* Description */}
|
|
42
|
+
<Box flexDirection="column" marginBottom={1}>
|
|
43
|
+
<Text dimColor>
|
|
44
|
+
This wizard will help you set up Gitforest for the first time.
|
|
45
|
+
</Text>
|
|
46
|
+
</Box>
|
|
47
|
+
|
|
48
|
+
{/* What we'll configure */}
|
|
49
|
+
<Box flexDirection="column" marginBottom={1}>
|
|
50
|
+
<Text>We'll configure:</Text>
|
|
51
|
+
<Text> • Project directories to scan for Git repositories</Text>
|
|
52
|
+
<Text> • GitHub authentication (optional)</Text>
|
|
53
|
+
</Box>
|
|
54
|
+
|
|
55
|
+
{/* Instructions */}
|
|
56
|
+
<Box marginTop={1} gap={1}>
|
|
57
|
+
<Text dimColor>Press </Text>
|
|
58
|
+
<Text color="green" bold>
|
|
59
|
+
Enter
|
|
60
|
+
</Text>
|
|
61
|
+
<Text dimColor> to continue, </Text>
|
|
62
|
+
<Text color="red" bold>
|
|
63
|
+
q
|
|
64
|
+
</Text>
|
|
65
|
+
<Text dimColor> to quit</Text>
|
|
66
|
+
</Box>
|
|
67
|
+
</Box>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { existsSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { mkdir } from "fs/promises";
|
|
4
|
+
import { homedir } from "os";
|
|
5
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
6
|
+
import { GitforestConfigSchema, type GitforestConfig } from "../types/index.ts";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Environment variable prefixes and mappings
|
|
10
|
+
*/
|
|
11
|
+
const ENV_PREFIX = "GITFOREST_";
|
|
12
|
+
|
|
13
|
+
interface EnvMapping {
|
|
14
|
+
env: string;
|
|
15
|
+
path: string[];
|
|
16
|
+
transform?: (value: string) => unknown;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const ENV_MAPPINGS: EnvMapping[] = [
|
|
20
|
+
// GitHub settings
|
|
21
|
+
{ env: "GITFOREST_GITHUB_VISIBILITY", path: ["github", "defaultVisibility"], transform: (v) => v },
|
|
22
|
+
|
|
23
|
+
// Scan settings
|
|
24
|
+
{ env: "GITFOREST_CONCURRENCY", path: ["scan", "concurrency"], transform: (v) => parseInt(v, 10) },
|
|
25
|
+
{ env: "GITFOREST_INCLUDE_HIDDEN", path: ["scan", "includeHidden"], transform: (v) => v === "true" },
|
|
26
|
+
|
|
27
|
+
// Display settings
|
|
28
|
+
{ env: "GITFOREST_SORT_BY", path: ["display", "sortBy"], transform: (v) => v },
|
|
29
|
+
{ env: "GITFOREST_SORT_DIR", path: ["display", "sortDirection"], transform: (v) => v },
|
|
30
|
+
{ env: "GITFOREST_SHOW_SUBMODULES", path: ["display", "showSubmodules"], transform: (v) => v === "true" },
|
|
31
|
+
|
|
32
|
+
// Cache settings
|
|
33
|
+
{ env: "GITFOREST_CACHE_TTL", path: ["cache", "ttlSeconds"], transform: (v) => parseInt(v, 10) },
|
|
34
|
+
{ env: "GITFOREST_GITHUB_CACHE_TTL", path: ["cache", "githubTtlSeconds"], transform: (v) => parseInt(v, 10) },
|
|
35
|
+
{ env: "GITFOREST_ENABLE_BACKGROUND_REFRESH", path: ["cache", "enableBackgroundRefresh"], transform: (v) => v === "true" },
|
|
36
|
+
{ env: "GITFOREST_BACKGROUND_REFRESH_INTERVAL", path: ["cache", "backgroundRefreshIntervalSeconds"], transform: (v) => parseInt(v, 10) },
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Set a nested value in an object using a path array
|
|
41
|
+
*/
|
|
42
|
+
function setNestedValue(obj: Record<string, unknown>, path: string[], value: unknown): void {
|
|
43
|
+
let current = obj;
|
|
44
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
45
|
+
const key = path[i]!;
|
|
46
|
+
if (!(key in current) || typeof current[key] !== "object" || current[key] === null) {
|
|
47
|
+
current[key] = {};
|
|
48
|
+
}
|
|
49
|
+
current = current[key] as Record<string, unknown>;
|
|
50
|
+
}
|
|
51
|
+
const lastKey = path[path.length - 1]!;
|
|
52
|
+
current[lastKey] = value;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Apply environment variable overrides to config
|
|
57
|
+
* Environment variables take precedence over file-based config
|
|
58
|
+
*/
|
|
59
|
+
export function applyEnvOverrides(config: GitforestConfig): GitforestConfig {
|
|
60
|
+
const result = JSON.parse(JSON.stringify(config)) as GitforestConfig;
|
|
61
|
+
|
|
62
|
+
for (const mapping of ENV_MAPPINGS) {
|
|
63
|
+
const envValue = process.env[mapping.env];
|
|
64
|
+
if (envValue !== undefined && envValue !== "") {
|
|
65
|
+
const transformedValue = mapping.transform ? mapping.transform(envValue) : envValue;
|
|
66
|
+
setNestedValue(result as unknown as Record<string, unknown>, mapping.path, transformedValue);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get list of supported environment variables
|
|
75
|
+
*/
|
|
76
|
+
export function getSupportedEnvVars(): { env: string; path: string; description: string }[] {
|
|
77
|
+
return ENV_MAPPINGS.map((m) => ({
|
|
78
|
+
env: m.env,
|
|
79
|
+
path: m.path.join("."),
|
|
80
|
+
description: `Override ${m.path.join(".")} config value`,
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Get possible config file locations in order of priority
|
|
86
|
+
*/
|
|
87
|
+
function getConfigPaths(cwd?: string): string[] {
|
|
88
|
+
const xdg = process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config");
|
|
89
|
+
const home = homedir();
|
|
90
|
+
const workingDir = cwd ?? process.cwd();
|
|
91
|
+
|
|
92
|
+
return [
|
|
93
|
+
// Current directory
|
|
94
|
+
join(workingDir, "gitforest.config.yaml"),
|
|
95
|
+
join(workingDir, "gitforest.config.yml"),
|
|
96
|
+
join(workingDir, "gitforest.config.json"),
|
|
97
|
+
join(workingDir, ".gitforest.yaml"),
|
|
98
|
+
join(workingDir, ".gitforest.yml"),
|
|
99
|
+
join(workingDir, ".gitforest.json"),
|
|
100
|
+
// XDG config directory
|
|
101
|
+
join(xdg, "gitforest", "config.yaml"),
|
|
102
|
+
join(xdg, "gitforest", "config.yml"),
|
|
103
|
+
join(xdg, "gitforest", "config.json"),
|
|
104
|
+
// Home directory
|
|
105
|
+
join(home, ".gitforest.yaml"),
|
|
106
|
+
join(home, ".gitforest.yml"),
|
|
107
|
+
join(home, ".gitforest.json"),
|
|
108
|
+
];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Find the first existing config file
|
|
113
|
+
*/
|
|
114
|
+
export function findConfigPath(cwd?: string): string | null {
|
|
115
|
+
for (const path of getConfigPaths(cwd)) {
|
|
116
|
+
if (existsSync(path)) {
|
|
117
|
+
return path;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Load and validate configuration from file
|
|
125
|
+
*/
|
|
126
|
+
export async function loadConfig(configPath?: string, cwd?: string): Promise<GitforestConfig> {
|
|
127
|
+
const path = configPath ?? findConfigPath(cwd);
|
|
128
|
+
|
|
129
|
+
if (!path) {
|
|
130
|
+
const xdg = process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config");
|
|
131
|
+
throw new Error(
|
|
132
|
+
`No config file found. Create one at:\n` +
|
|
133
|
+
` - ${join(xdg, "gitforest", "config.yaml")}\n\n` +
|
|
134
|
+
`Example:\n` +
|
|
135
|
+
`directories:\n` +
|
|
136
|
+
` - path: ~/projects\n` +
|
|
137
|
+
` maxDepth: 2\n` +
|
|
138
|
+
` - path: ~/.dotfiles\n` +
|
|
139
|
+
` maxDepth: 3\n`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const file = Bun.file(path);
|
|
144
|
+
const content = await file.text();
|
|
145
|
+
|
|
146
|
+
let parsed: unknown;
|
|
147
|
+
|
|
148
|
+
if (path.endsWith(".yaml") || path.endsWith(".yml")) {
|
|
149
|
+
parsed = parseYaml(content);
|
|
150
|
+
} else if (path.endsWith(".json")) {
|
|
151
|
+
parsed = JSON.parse(content);
|
|
152
|
+
} else {
|
|
153
|
+
throw new Error(`Unknown config file format: ${path}`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const result = GitforestConfigSchema.safeParse(parsed);
|
|
157
|
+
|
|
158
|
+
if (!result.success) {
|
|
159
|
+
const issues = result.error.issues
|
|
160
|
+
.map((issue) => ` - ${issue.path.join(".")}: ${issue.message}`)
|
|
161
|
+
.join("\n");
|
|
162
|
+
throw new Error(`Invalid config file at ${path}:\n${issues}`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Expand ~ in directory paths
|
|
166
|
+
let config = result.data;
|
|
167
|
+
config.directories = config.directories.map((dir) => ({
|
|
168
|
+
...dir,
|
|
169
|
+
path: dir.path.replace(/^~/, homedir()),
|
|
170
|
+
}));
|
|
171
|
+
|
|
172
|
+
// Apply environment variable overrides
|
|
173
|
+
config = applyEnvOverrides(config);
|
|
174
|
+
|
|
175
|
+
return config;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Get the default config path for creating a new config
|
|
180
|
+
*/
|
|
181
|
+
export function getDefaultConfigPath(): string {
|
|
182
|
+
const xdg = process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config");
|
|
183
|
+
return join(xdg, "gitforest", "config.yaml");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Create a default config file
|
|
188
|
+
*/
|
|
189
|
+
export async function createDefaultConfig(): Promise<string> {
|
|
190
|
+
const configPath = getDefaultConfigPath();
|
|
191
|
+
const configDir = join(configPath, "..");
|
|
192
|
+
|
|
193
|
+
// Ensure directory exists
|
|
194
|
+
await mkdir(configDir, { recursive: true });
|
|
195
|
+
|
|
196
|
+
const defaultConfig = `# Gitforest Configuration
|
|
197
|
+
# Directories to scan for projects
|
|
198
|
+
directories:
|
|
199
|
+
- path: ~/projects
|
|
200
|
+
maxDepth: 2
|
|
201
|
+
label: Projects
|
|
202
|
+
|
|
203
|
+
# Optional: Add more directories
|
|
204
|
+
# - path: ~/.dotfiles
|
|
205
|
+
# maxDepth: 3
|
|
206
|
+
# label: Dotfiles
|
|
207
|
+
|
|
208
|
+
# Scan settings
|
|
209
|
+
scan:
|
|
210
|
+
ignore:
|
|
211
|
+
- node_modules
|
|
212
|
+
- .git
|
|
213
|
+
- vendor
|
|
214
|
+
- __pycache__
|
|
215
|
+
- target
|
|
216
|
+
- dist
|
|
217
|
+
- build
|
|
218
|
+
includeHidden: false
|
|
219
|
+
concurrency: 5
|
|
220
|
+
|
|
221
|
+
# GitHub settings
|
|
222
|
+
github:
|
|
223
|
+
defaultVisibility: private
|
|
224
|
+
|
|
225
|
+
# Display settings
|
|
226
|
+
display:
|
|
227
|
+
showSubmodules: true
|
|
228
|
+
sortBy: status # name | status | lastActivity
|
|
229
|
+
sortDirection: desc
|
|
230
|
+
|
|
231
|
+
# Cache settings
|
|
232
|
+
cache:
|
|
233
|
+
ttlSeconds: 300 # 5 minutes for local projects
|
|
234
|
+
githubTtlSeconds: 600 # 10 minutes for GitHub repos
|
|
235
|
+
enableBackgroundRefresh: true # Refresh data in background
|
|
236
|
+
backgroundRefreshIntervalSeconds: 300 # 5 minutes
|
|
237
|
+
`;
|
|
238
|
+
|
|
239
|
+
await Bun.write(configPath, defaultConfig);
|
|
240
|
+
return configPath;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Save configuration to file
|
|
245
|
+
*/
|
|
246
|
+
export async function saveConfig(config: GitforestConfig, path?: string): Promise<void> {
|
|
247
|
+
const configPath = path ?? findConfigPath() ?? getDefaultConfigPath();
|
|
248
|
+
|
|
249
|
+
// Create backup of existing config if it exists
|
|
250
|
+
if (existsSync(configPath)) {
|
|
251
|
+
const backupPath = `${configPath}.bak`;
|
|
252
|
+
await Bun.write(backupPath, await Bun.file(configPath).text());
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
let content: string;
|
|
256
|
+
if (configPath.endsWith(".json")) {
|
|
257
|
+
content = JSON.stringify(config, null, 2);
|
|
258
|
+
} else {
|
|
259
|
+
content = stringifyYaml(config);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
await Bun.write(configPath, content);
|
|
263
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { mkdir } from "fs/promises";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
import type { GitforestConfig } from "../types/index.ts";
|
|
5
|
+
import { saveConfig, getDefaultConfigPath } from "./loader.ts";
|
|
6
|
+
|
|
7
|
+
export interface CreateOnboardingConfigOptions {
|
|
8
|
+
directories: Array<{ path: string; maxDepth: number; label?: string }>;
|
|
9
|
+
githubAuth?: {
|
|
10
|
+
authenticated: boolean;
|
|
11
|
+
user?: string;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Create a config file from onboarding wizard state
|
|
17
|
+
*/
|
|
18
|
+
export async function createOnboardingConfig(
|
|
19
|
+
options: CreateOnboardingConfigOptions
|
|
20
|
+
): Promise<GitforestConfig> {
|
|
21
|
+
const { directories, githubAuth } = options;
|
|
22
|
+
|
|
23
|
+
// Validate at least one directory
|
|
24
|
+
if (directories.length === 0) {
|
|
25
|
+
throw new Error("At least one directory is required");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Create config object with defaults
|
|
29
|
+
const config: GitforestConfig = {
|
|
30
|
+
directories: directories.map((dir) => ({
|
|
31
|
+
path: dir.path,
|
|
32
|
+
maxDepth: dir.maxDepth,
|
|
33
|
+
label: dir.label,
|
|
34
|
+
})),
|
|
35
|
+
scan: {
|
|
36
|
+
ignore: ["node_modules", ".git", "vendor", "__pycache__", "target", "dist", "build"],
|
|
37
|
+
includeHidden: false,
|
|
38
|
+
concurrency: 5,
|
|
39
|
+
},
|
|
40
|
+
github: {
|
|
41
|
+
defaultVisibility: "private",
|
|
42
|
+
},
|
|
43
|
+
display: {
|
|
44
|
+
showSubmodules: true,
|
|
45
|
+
showNonGitProjects: true,
|
|
46
|
+
sortBy: "status",
|
|
47
|
+
sortDirection: "desc",
|
|
48
|
+
},
|
|
49
|
+
cache: {
|
|
50
|
+
ttlSeconds: 300,
|
|
51
|
+
githubTtlSeconds: 600,
|
|
52
|
+
enableBackgroundRefresh: true,
|
|
53
|
+
backgroundRefreshIntervalSeconds: 300,
|
|
54
|
+
},
|
|
55
|
+
commands: [],
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// Ensure config directory exists
|
|
59
|
+
const configPath = getDefaultConfigPath();
|
|
60
|
+
const configDir = join(configPath, "..");
|
|
61
|
+
await mkdir(configDir, { recursive: true });
|
|
62
|
+
|
|
63
|
+
// Save config
|
|
64
|
+
await saveConfig(config, configPath);
|
|
65
|
+
|
|
66
|
+
return config;
|
|
67
|
+
}
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Application constants
|
|
3
|
+
* Centralized configuration values to avoid magic numbers
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// UI Constants
|
|
8
|
+
// ============================================================================
|
|
9
|
+
|
|
10
|
+
export const UI = {
|
|
11
|
+
/** Height reserved for header, filter bar, status bar, and padding */
|
|
12
|
+
LAYOUT_OVERHEAD: 6,
|
|
13
|
+
/** Minimum terminal height for proper display */
|
|
14
|
+
MIN_TERMINAL_HEIGHT: 24,
|
|
15
|
+
/** Default terminal height if detection fails */
|
|
16
|
+
DEFAULT_TERMINAL_HEIGHT: 24,
|
|
17
|
+
} as const;
|
|
18
|
+
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// Scanner Constants
|
|
21
|
+
// ============================================================================
|
|
22
|
+
|
|
23
|
+
export const SCANNER = {
|
|
24
|
+
/** Default maximum directory depth for scanning */
|
|
25
|
+
DEFAULT_MAX_DEPTH: 2,
|
|
26
|
+
/** Default concurrency for scanning operations */
|
|
27
|
+
DEFAULT_CONCURRENCY: 5,
|
|
28
|
+
/** Length of generated project IDs (MD5 hash truncation) */
|
|
29
|
+
PROJECT_ID_LENGTH: 12,
|
|
30
|
+
/** Default cache TTL in seconds */
|
|
31
|
+
DEFAULT_CACHE_TTL_SECONDS: 300,
|
|
32
|
+
} as const;
|
|
33
|
+
|
|
34
|
+
// ============================================================================
|
|
35
|
+
// Git Constants
|
|
36
|
+
// ============================================================================
|
|
37
|
+
|
|
38
|
+
export const GIT = {
|
|
39
|
+
/** Default remote name */
|
|
40
|
+
DEFAULT_REMOTE: "origin",
|
|
41
|
+
/** Default branch name */
|
|
42
|
+
DEFAULT_BRANCH: "main",
|
|
43
|
+
/** Default concurrency for git operations */
|
|
44
|
+
DEFAULT_CONCURRENCY: 5,
|
|
45
|
+
/** Higher concurrency for status refresh (read-only) */
|
|
46
|
+
STATUS_REFRESH_CONCURRENCY: 10,
|
|
47
|
+
} as const;
|
|
48
|
+
|
|
49
|
+
export const GIT_TIMEOUTS = {
|
|
50
|
+
CLONE: 120000, // 2 minutes for clone
|
|
51
|
+
PULL: 60000, // 1 minute for pull
|
|
52
|
+
PUSH: 60000, // 1 minute for push
|
|
53
|
+
FETCH: 60000, // 1 minute for fetch
|
|
54
|
+
STATUS: 10000, // 10 seconds for status
|
|
55
|
+
} as const;
|
|
56
|
+
|
|
57
|
+
// ============================================================================
|
|
58
|
+
// GitHub API Constants
|
|
59
|
+
// ============================================================================
|
|
60
|
+
|
|
61
|
+
export const GITHUB_API = {
|
|
62
|
+
/** Base URL for GitHub API */
|
|
63
|
+
BASE_URL: "https://api.github.com",
|
|
64
|
+
/** API version header */
|
|
65
|
+
API_VERSION: "2022-11-28",
|
|
66
|
+
/** Default page size for pagination */
|
|
67
|
+
PAGE_SIZE: 100,
|
|
68
|
+
/** Maximum retry attempts */
|
|
69
|
+
MAX_RETRIES: 3,
|
|
70
|
+
/** Initial retry delay in milliseconds */
|
|
71
|
+
INITIAL_RETRY_DELAY: 1000,
|
|
72
|
+
/** Maximum retry delay in milliseconds */
|
|
73
|
+
MAX_RETRY_DELAY: 30000,
|
|
74
|
+
/** Backoff multiplier for retries */
|
|
75
|
+
RETRY_BACKOFF_FACTOR: 2,
|
|
76
|
+
} as const;
|
|
77
|
+
|
|
78
|
+
// ============================================================================
|
|
79
|
+
// Background Fetch Constants
|
|
80
|
+
// ============================================================================
|
|
81
|
+
|
|
82
|
+
export const BACKGROUND_FETCH = {
|
|
83
|
+
/** Interval between background fetches in milliseconds (5 minutes) */
|
|
84
|
+
INTERVAL_MS: 5 * 60 * 1000,
|
|
85
|
+
} as const;
|
|
86
|
+
|
|
87
|
+
// ============================================================================
|
|
88
|
+
// Type Exports
|
|
89
|
+
// ============================================================================
|
|
90
|
+
|
|
91
|
+
export type UIConstants = typeof UI;
|
|
92
|
+
export type ScannerConstants = typeof SCANNER;
|
|
93
|
+
export type GitConstants = typeof GIT;
|
|
94
|
+
export type GitTimeouts = typeof GIT_TIMEOUTS;
|
|
95
|
+
export type GitHubAPIConstants = typeof GITHUB_API;
|
|
96
|
+
export type BackgroundFetchConstants = typeof BACKGROUND_FETCH;
|