btca-server 1.0.962 → 2.0.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/package.json +3 -3
- package/src/agent/agent.test.ts +31 -24
- package/src/agent/index.ts +8 -2
- package/src/agent/loop.ts +303 -346
- package/src/agent/service.ts +252 -233
- package/src/agent/types.ts +2 -2
- package/src/collections/index.ts +2 -1
- package/src/collections/service.ts +352 -345
- package/src/config/config.test.ts +3 -1
- package/src/config/index.ts +615 -727
- package/src/config/remote.ts +214 -369
- package/src/context/index.ts +6 -12
- package/src/context/transaction.ts +23 -30
- package/src/effect/errors.ts +45 -0
- package/src/effect/layers.ts +26 -0
- package/src/effect/runtime.ts +19 -0
- package/src/effect/services.ts +154 -0
- package/src/index.ts +291 -369
- package/src/metrics/index.ts +46 -46
- package/src/pricing/models-dev.ts +104 -106
- package/src/providers/auth.ts +159 -200
- package/src/providers/index.ts +19 -2
- package/src/providers/model.ts +115 -135
- package/src/providers/openai.ts +3 -3
- package/src/resources/impls/git.ts +123 -146
- package/src/resources/impls/npm.test.ts +16 -5
- package/src/resources/impls/npm.ts +66 -75
- package/src/resources/index.ts +6 -1
- package/src/resources/schema.ts +7 -6
- package/src/resources/service.test.ts +13 -12
- package/src/resources/service.ts +153 -112
- package/src/stream/index.ts +1 -1
- package/src/stream/service.test.ts +5 -5
- package/src/stream/service.ts +282 -293
- package/src/tools/glob.ts +126 -141
- package/src/tools/grep.ts +205 -210
- package/src/tools/index.ts +8 -4
- package/src/tools/list.ts +118 -140
- package/src/tools/read.ts +209 -235
- package/src/tools/virtual-sandbox.ts +91 -83
- package/src/validation/index.ts +18 -22
- package/src/vfs/virtual-fs.test.ts +37 -25
- package/src/vfs/virtual-fs.ts +218 -216
package/src/providers/model.ts
CHANGED
|
@@ -4,7 +4,12 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import type { LanguageModel } from 'ai';
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
getAuthStatus,
|
|
9
|
+
getAuthenticatedProviders,
|
|
10
|
+
getProviderAuthHint,
|
|
11
|
+
isAuthenticated
|
|
12
|
+
} from './auth.ts';
|
|
8
13
|
import {
|
|
9
14
|
getProviderFactory,
|
|
10
15
|
isProviderSupported,
|
|
@@ -12,162 +17,137 @@ import {
|
|
|
12
17
|
type ProviderOptions
|
|
13
18
|
} from './registry.ts';
|
|
14
19
|
|
|
15
|
-
export
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
this.
|
|
25
|
-
'Open an issue to request this provider: https://github.com/davis7dotsh/better-context/issues.';
|
|
26
|
-
}
|
|
20
|
+
export class ProviderNotFoundError extends Error {
|
|
21
|
+
readonly _tag = 'ProviderNotFoundError';
|
|
22
|
+
readonly providerId: string;
|
|
23
|
+
readonly hint: string;
|
|
24
|
+
|
|
25
|
+
constructor(providerId: string) {
|
|
26
|
+
super(`Provider "${providerId}" is not supported`);
|
|
27
|
+
this.providerId = providerId;
|
|
28
|
+
this.hint =
|
|
29
|
+
'Open an issue to request this provider: https://github.com/davis7dotsh/better-context/issues.';
|
|
27
30
|
}
|
|
31
|
+
}
|
|
28
32
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
export class ProviderNotAuthenticatedError extends Error {
|
|
34
|
+
readonly _tag = 'ProviderNotAuthenticatedError';
|
|
35
|
+
readonly providerId: string;
|
|
36
|
+
readonly hint: string;
|
|
33
37
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
38
|
+
constructor(providerId: string) {
|
|
39
|
+
super(`Provider "${providerId}" is not authenticated.`);
|
|
40
|
+
this.providerId = providerId;
|
|
41
|
+
this.hint = getProviderAuthHint(providerId);
|
|
39
42
|
}
|
|
43
|
+
}
|
|
40
44
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
45
|
+
export class ProviderAuthTypeError extends Error {
|
|
46
|
+
readonly _tag = 'ProviderAuthTypeError';
|
|
47
|
+
readonly providerId: string;
|
|
48
|
+
readonly authType: string;
|
|
49
|
+
readonly hint: string;
|
|
50
|
+
|
|
51
|
+
constructor(args: { providerId: string; authType: string }) {
|
|
52
|
+
super(`Provider "${args.providerId}" does not support "${args.authType}" auth.`);
|
|
53
|
+
this.providerId = args.providerId;
|
|
54
|
+
this.authType = args.authType;
|
|
55
|
+
this.hint = getProviderAuthHint(args.providerId);
|
|
53
56
|
}
|
|
57
|
+
}
|
|
54
58
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
export class ProviderOptionsError extends Error {
|
|
60
|
+
readonly _tag = 'ProviderOptionsError';
|
|
61
|
+
readonly providerId: string;
|
|
62
|
+
readonly hint: string;
|
|
59
63
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
64
|
+
constructor(args: { providerId: string; message: string; hint: string }) {
|
|
65
|
+
super(args.message);
|
|
66
|
+
this.providerId = args.providerId;
|
|
67
|
+
this.hint = args.hint;
|
|
65
68
|
}
|
|
69
|
+
}
|
|
66
70
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
*/
|
|
84
|
-
export async function getModel(
|
|
85
|
-
providerId: string,
|
|
86
|
-
modelId: string,
|
|
87
|
-
options: ModelOptions = {}
|
|
88
|
-
): Promise<LanguageModel> {
|
|
89
|
-
const normalizedProviderId = normalizeProviderId(providerId);
|
|
90
|
-
|
|
91
|
-
// Check if provider is supported
|
|
92
|
-
if (!isProviderSupported(normalizedProviderId)) {
|
|
93
|
-
throw new ProviderNotFoundError(providerId);
|
|
94
|
-
}
|
|
71
|
+
export type ModelOptions = {
|
|
72
|
+
providerOptions?: Partial<ProviderOptions>;
|
|
73
|
+
skipAuth?: boolean;
|
|
74
|
+
allowMissingAuth?: boolean;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const getModel = async (
|
|
78
|
+
providerId: string,
|
|
79
|
+
modelId: string,
|
|
80
|
+
options: ModelOptions = {}
|
|
81
|
+
): Promise<LanguageModel> => {
|
|
82
|
+
const normalizedProviderId = normalizeProviderId(providerId);
|
|
83
|
+
|
|
84
|
+
if (!isProviderSupported(normalizedProviderId)) {
|
|
85
|
+
throw new ProviderNotFoundError(providerId);
|
|
86
|
+
}
|
|
95
87
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
88
|
+
const factory = getProviderFactory(normalizedProviderId);
|
|
89
|
+
if (!factory) {
|
|
90
|
+
throw new ProviderNotFoundError(providerId);
|
|
91
|
+
}
|
|
101
92
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
let accountId: string | undefined;
|
|
93
|
+
let apiKey: string | undefined;
|
|
94
|
+
let accountId: string | undefined;
|
|
105
95
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
if (status.status === 'invalid') {
|
|
114
|
-
throw new ProviderAuthTypeError({ providerId, authType: status.authType });
|
|
115
|
-
}
|
|
116
|
-
if (status.status === 'ok') {
|
|
117
|
-
apiKey = status.apiKey;
|
|
118
|
-
accountId = status.accountId;
|
|
96
|
+
if (!options.skipAuth) {
|
|
97
|
+
const status = await getAuthStatus(normalizedProviderId);
|
|
98
|
+
if (status.status === 'missing') {
|
|
99
|
+
if (!options.allowMissingAuth) {
|
|
100
|
+
throw new ProviderNotAuthenticatedError(providerId);
|
|
119
101
|
}
|
|
120
102
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const providerOptions: ProviderOptions = {
|
|
124
|
-
...options.providerOptions,
|
|
125
|
-
...(accountId ? { accountId } : {})
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
if (apiKey) {
|
|
129
|
-
providerOptions.apiKey = apiKey;
|
|
103
|
+
if (status.status === 'invalid') {
|
|
104
|
+
throw new ProviderAuthTypeError({ providerId, authType: status.authType });
|
|
130
105
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const name = providerOptions.name?.trim();
|
|
135
|
-
if (!baseURL || !name) {
|
|
136
|
-
throw new ProviderOptionsError({
|
|
137
|
-
providerId: normalizedProviderId,
|
|
138
|
-
message: 'openai-compat requires baseURL and name',
|
|
139
|
-
hint: 'Run "btca connect -p openai-compat" to configure baseURL and name.'
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
providerOptions.baseURL = baseURL;
|
|
143
|
-
providerOptions.name = name;
|
|
106
|
+
if (status.status === 'ok') {
|
|
107
|
+
apiKey = status.apiKey;
|
|
108
|
+
accountId = status.accountId;
|
|
144
109
|
}
|
|
110
|
+
}
|
|
145
111
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
112
|
+
const providerOptions: ProviderOptions = {
|
|
113
|
+
...options.providerOptions,
|
|
114
|
+
...(accountId ? { accountId } : {})
|
|
115
|
+
};
|
|
149
116
|
|
|
150
|
-
|
|
117
|
+
if (apiKey) {
|
|
118
|
+
providerOptions.apiKey = apiKey;
|
|
151
119
|
}
|
|
152
120
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
121
|
+
if (normalizedProviderId === 'openai-compat') {
|
|
122
|
+
const baseURL = providerOptions.baseURL?.trim();
|
|
123
|
+
const name = providerOptions.name?.trim();
|
|
124
|
+
if (!baseURL || !name) {
|
|
125
|
+
throw new ProviderOptionsError({
|
|
126
|
+
providerId: normalizedProviderId,
|
|
127
|
+
message: 'openai-compat requires baseURL and name',
|
|
128
|
+
hint: 'Run "btca connect -p openai-compat" to configure baseURL and name.'
|
|
129
|
+
});
|
|
161
130
|
}
|
|
162
|
-
|
|
163
|
-
|
|
131
|
+
providerOptions.baseURL = baseURL;
|
|
132
|
+
providerOptions.name = name;
|
|
164
133
|
}
|
|
165
134
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
135
|
+
const provider = factory(providerOptions);
|
|
136
|
+
const model = provider(modelId);
|
|
137
|
+
return model as LanguageModel;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export const canUseModel = async (providerId: string): Promise<boolean> => {
|
|
141
|
+
const normalizedProviderId = normalizeProviderId(providerId);
|
|
142
|
+
|
|
143
|
+
if (!isProviderSupported(normalizedProviderId)) {
|
|
144
|
+
return false;
|
|
172
145
|
}
|
|
173
|
-
|
|
146
|
+
|
|
147
|
+
return isAuthenticated(normalizedProviderId);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
export const getAvailableProviders = async (): Promise<string[]> => {
|
|
151
|
+
const authenticatedProviders = await getAuthenticatedProviders();
|
|
152
|
+
return authenticatedProviders.filter((provider) => isProviderSupported(provider));
|
|
153
|
+
};
|
package/src/providers/openai.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createOpenAI } from '@ai-sdk/openai';
|
|
2
2
|
import * as os from 'node:os';
|
|
3
|
-
import {
|
|
3
|
+
import { getCredentials, setCredentials } from './auth.ts';
|
|
4
4
|
|
|
5
5
|
const CLIENT_ID = 'app_EMoamEEZ73f0CkXaXp7hrann';
|
|
6
6
|
const ISSUER = 'https://auth.openai.com';
|
|
@@ -165,7 +165,7 @@ export function createOpenAICodex(
|
|
|
165
165
|
} = {}
|
|
166
166
|
) {
|
|
167
167
|
const customFetch = (async (requestInput, init) => {
|
|
168
|
-
const storedAuth = await
|
|
168
|
+
const storedAuth = await getCredentials('openai');
|
|
169
169
|
let accessToken = options.apiKey;
|
|
170
170
|
let accountId = options.accountId;
|
|
171
171
|
|
|
@@ -178,7 +178,7 @@ export function createOpenAICodex(
|
|
|
178
178
|
const refreshedAccountId = extractAccountId(tokens) ?? accountId;
|
|
179
179
|
accessToken = tokens.access_token;
|
|
180
180
|
accountId = refreshedAccountId;
|
|
181
|
-
await
|
|
181
|
+
await setCredentials('openai', {
|
|
182
182
|
type: 'oauth',
|
|
183
183
|
refresh: tokens.refresh_token ?? storedAuth.refresh,
|
|
184
184
|
access: tokens.access_token,
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { promises as fs } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
import { Metrics } from '../../metrics/index.ts';
|
|
4
|
+
import { metricsInfo, withMetricsSpan } from '../../metrics/index.ts';
|
|
7
5
|
import { CommonHints } from '../../errors.ts';
|
|
8
6
|
import { ResourceError, resourceNameToKey } from '../helpers.ts';
|
|
9
7
|
import { GitResourceSchema } from '../schema.ts';
|
|
@@ -26,7 +24,11 @@ const isBranchNotFoundError = (cause: unknown) => {
|
|
|
26
24
|
};
|
|
27
25
|
|
|
28
26
|
const cleanupDirectory = async (pathToRemove: string) => {
|
|
29
|
-
|
|
27
|
+
try {
|
|
28
|
+
await fs.rm(pathToRemove, { recursive: true, force: true });
|
|
29
|
+
} catch {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
30
32
|
};
|
|
31
33
|
|
|
32
34
|
const validateGitUrl = (url: string): { success: true } | { success: false; error: string } => {
|
|
@@ -50,11 +52,12 @@ const validateSearchPath = (
|
|
|
50
52
|
};
|
|
51
53
|
|
|
52
54
|
const directoryExists = async (path: string): Promise<boolean> => {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
try {
|
|
56
|
+
const stat = await fs.stat(path);
|
|
57
|
+
return stat.isDirectory();
|
|
58
|
+
} catch {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
58
61
|
};
|
|
59
62
|
|
|
60
63
|
/**
|
|
@@ -172,10 +175,11 @@ const runGitChecked = async (
|
|
|
172
175
|
options: { cwd?: string; quiet: boolean },
|
|
173
176
|
buildError: (result: GitRunResult) => ResourceError
|
|
174
177
|
) => {
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
178
|
+
const runResult = await runGit(args, options);
|
|
179
|
+
if (runResult.exitCode !== 0) {
|
|
180
|
+
throw buildError(runResult);
|
|
181
|
+
}
|
|
182
|
+
return runResult;
|
|
179
183
|
};
|
|
180
184
|
|
|
181
185
|
const runGit = async (
|
|
@@ -268,62 +272,50 @@ const gitClone = async (args: {
|
|
|
268
272
|
]
|
|
269
273
|
: ['clone', '--depth', '1', '-b', args.repoBranch, args.repoUrl, args.localAbsolutePath];
|
|
270
274
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
url: args.repoUrl
|
|
279
|
-
});
|
|
275
|
+
await runGitChecked(cloneArgs, { quiet: args.quiet }, (cloneResult) => {
|
|
276
|
+
const errorType = detectGitErrorType(cloneResult.stderr);
|
|
277
|
+
const { message, hint } = getGitErrorDetails(errorType, {
|
|
278
|
+
operation: 'clone',
|
|
279
|
+
branch: args.repoBranch,
|
|
280
|
+
url: args.repoUrl
|
|
281
|
+
});
|
|
280
282
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
283
|
+
return new ResourceError({
|
|
284
|
+
message,
|
|
285
|
+
hint,
|
|
286
|
+
cause: new Error(
|
|
287
|
+
`git clone failed with exit code ${cloneResult.exitCode}: ${cloneResult.stderr}`
|
|
288
|
+
)
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
if (needsSparseCheckout) {
|
|
293
|
+
await runGitChecked(
|
|
294
|
+
['sparse-checkout', 'set', ...args.repoSubPaths],
|
|
295
|
+
{ cwd: args.localAbsolutePath, quiet: args.quiet },
|
|
296
|
+
(sparseResult) =>
|
|
297
|
+
new ResourceError({
|
|
298
|
+
message: `Failed to set sparse-checkout path(s): "${args.repoSubPaths.join(', ')}"`,
|
|
299
|
+
hint: 'Verify the search paths exist in the repository. Check the repository structure to find the correct path.',
|
|
284
300
|
cause: new Error(
|
|
285
|
-
`git
|
|
301
|
+
`git sparse-checkout failed with exit code ${sparseResult.exitCode}: ${sparseResult.stderr}`
|
|
286
302
|
)
|
|
287
|
-
})
|
|
288
|
-
})
|
|
303
|
+
})
|
|
289
304
|
);
|
|
290
305
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
)
|
|
305
|
-
);
|
|
306
|
-
|
|
307
|
-
yield* Result.await(
|
|
308
|
-
runGitChecked(
|
|
309
|
-
['checkout'],
|
|
310
|
-
{ cwd: args.localAbsolutePath, quiet: args.quiet },
|
|
311
|
-
(checkout) =>
|
|
312
|
-
new ResourceError({
|
|
313
|
-
message: 'Failed to checkout repository',
|
|
314
|
-
hint: CommonHints.CLEAR_CACHE,
|
|
315
|
-
cause: new Error(
|
|
316
|
-
`git checkout failed with exit code ${checkout.exitCode}: ${checkout.stderr}`
|
|
317
|
-
)
|
|
318
|
-
})
|
|
319
|
-
)
|
|
320
|
-
);
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
return Result.ok(undefined);
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
if (!Result.isOk(result)) throw result.error;
|
|
306
|
+
await runGitChecked(
|
|
307
|
+
['checkout'],
|
|
308
|
+
{ cwd: args.localAbsolutePath, quiet: args.quiet },
|
|
309
|
+
(checkout) =>
|
|
310
|
+
new ResourceError({
|
|
311
|
+
message: 'Failed to checkout repository',
|
|
312
|
+
hint: CommonHints.CLEAR_CACHE,
|
|
313
|
+
cause: new Error(
|
|
314
|
+
`git checkout failed with exit code ${checkout.exitCode}: ${checkout.stderr}`
|
|
315
|
+
)
|
|
316
|
+
})
|
|
317
|
+
);
|
|
318
|
+
}
|
|
327
319
|
};
|
|
328
320
|
|
|
329
321
|
const gitUpdate = async (args: {
|
|
@@ -332,80 +324,66 @@ const gitUpdate = async (args: {
|
|
|
332
324
|
repoSubPaths: readonly string[];
|
|
333
325
|
quiet: boolean;
|
|
334
326
|
}) => {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
branch: args.branch
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
return new ResourceError({
|
|
348
|
-
message,
|
|
349
|
-
hint,
|
|
350
|
-
cause: new Error(
|
|
351
|
-
`git fetch failed with exit code ${fetchResult.exitCode}: ${fetchResult.stderr}`
|
|
352
|
-
)
|
|
353
|
-
});
|
|
354
|
-
}
|
|
355
|
-
)
|
|
356
|
-
);
|
|
357
|
-
|
|
358
|
-
yield* Result.await(
|
|
359
|
-
runGitChecked(
|
|
360
|
-
['reset', '--hard', `origin/${args.branch}`],
|
|
361
|
-
{ cwd: args.localAbsolutePath, quiet: args.quiet },
|
|
362
|
-
(resetResult) =>
|
|
363
|
-
new ResourceError({
|
|
364
|
-
message: 'Failed to update local repository',
|
|
365
|
-
hint: `${CommonHints.CLEAR_CACHE} This will re-clone the repository from scratch.`,
|
|
366
|
-
cause: new Error(
|
|
367
|
-
`git reset failed with exit code ${resetResult.exitCode}: ${resetResult.stderr}`
|
|
368
|
-
)
|
|
369
|
-
})
|
|
370
|
-
)
|
|
371
|
-
);
|
|
327
|
+
await runGitChecked(
|
|
328
|
+
['fetch', '--depth', '1', 'origin', args.branch],
|
|
329
|
+
{ cwd: args.localAbsolutePath, quiet: args.quiet },
|
|
330
|
+
(fetchResult) => {
|
|
331
|
+
const errorType = detectGitErrorType(fetchResult.stderr);
|
|
332
|
+
const { message, hint } = getGitErrorDetails(errorType, {
|
|
333
|
+
operation: 'fetch',
|
|
334
|
+
branch: args.branch
|
|
335
|
+
});
|
|
372
336
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
(sparseResult) =>
|
|
379
|
-
new ResourceError({
|
|
380
|
-
message: `Failed to set sparse-checkout path(s): "${args.repoSubPaths.join(', ')}"`,
|
|
381
|
-
hint: 'Verify the search paths exist in the repository. Check the repository structure to find the correct path.',
|
|
382
|
-
cause: new Error(
|
|
383
|
-
`git sparse-checkout failed with exit code ${sparseResult.exitCode}: ${sparseResult.stderr}`
|
|
384
|
-
)
|
|
385
|
-
})
|
|
386
|
-
)
|
|
387
|
-
);
|
|
388
|
-
|
|
389
|
-
yield* Result.await(
|
|
390
|
-
runGitChecked(
|
|
391
|
-
['checkout'],
|
|
392
|
-
{ cwd: args.localAbsolutePath, quiet: args.quiet },
|
|
393
|
-
(checkoutResult) =>
|
|
394
|
-
new ResourceError({
|
|
395
|
-
message: 'Failed to checkout repository',
|
|
396
|
-
hint: CommonHints.CLEAR_CACHE,
|
|
397
|
-
cause: new Error(
|
|
398
|
-
`git checkout failed with exit code ${checkoutResult.exitCode}: ${checkoutResult.stderr}`
|
|
399
|
-
)
|
|
400
|
-
})
|
|
337
|
+
return new ResourceError({
|
|
338
|
+
message,
|
|
339
|
+
hint,
|
|
340
|
+
cause: new Error(
|
|
341
|
+
`git fetch failed with exit code ${fetchResult.exitCode}: ${fetchResult.stderr}`
|
|
401
342
|
)
|
|
402
|
-
);
|
|
343
|
+
});
|
|
403
344
|
}
|
|
345
|
+
);
|
|
404
346
|
|
|
405
|
-
|
|
406
|
-
|
|
347
|
+
await runGitChecked(
|
|
348
|
+
['reset', '--hard', `origin/${args.branch}`],
|
|
349
|
+
{ cwd: args.localAbsolutePath, quiet: args.quiet },
|
|
350
|
+
(resetResult) =>
|
|
351
|
+
new ResourceError({
|
|
352
|
+
message: 'Failed to update local repository',
|
|
353
|
+
hint: `${CommonHints.CLEAR_CACHE} This will re-clone the repository from scratch.`,
|
|
354
|
+
cause: new Error(
|
|
355
|
+
`git reset failed with exit code ${resetResult.exitCode}: ${resetResult.stderr}`
|
|
356
|
+
)
|
|
357
|
+
})
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
if (args.repoSubPaths.length > 0) {
|
|
361
|
+
await runGitChecked(
|
|
362
|
+
['sparse-checkout', 'set', ...args.repoSubPaths],
|
|
363
|
+
{ cwd: args.localAbsolutePath, quiet: args.quiet },
|
|
364
|
+
(sparseResult) =>
|
|
365
|
+
new ResourceError({
|
|
366
|
+
message: `Failed to set sparse-checkout path(s): "${args.repoSubPaths.join(', ')}"`,
|
|
367
|
+
hint: 'Verify the search paths exist in the repository. Check the repository structure to find the correct path.',
|
|
368
|
+
cause: new Error(
|
|
369
|
+
`git sparse-checkout failed with exit code ${sparseResult.exitCode}: ${sparseResult.stderr}`
|
|
370
|
+
)
|
|
371
|
+
})
|
|
372
|
+
);
|
|
407
373
|
|
|
408
|
-
|
|
374
|
+
await runGitChecked(
|
|
375
|
+
['checkout'],
|
|
376
|
+
{ cwd: args.localAbsolutePath, quiet: args.quiet },
|
|
377
|
+
(checkoutResult) =>
|
|
378
|
+
new ResourceError({
|
|
379
|
+
message: 'Failed to checkout repository',
|
|
380
|
+
hint: CommonHints.CLEAR_CACHE,
|
|
381
|
+
cause: new Error(
|
|
382
|
+
`git checkout failed with exit code ${checkoutResult.exitCode}: ${checkoutResult.stderr}`
|
|
383
|
+
)
|
|
384
|
+
})
|
|
385
|
+
);
|
|
386
|
+
}
|
|
409
387
|
};
|
|
410
388
|
|
|
411
389
|
/**
|
|
@@ -459,28 +437,27 @@ const ensureGitResource = async (config: BtcaGitResourceArgs): Promise<string> =
|
|
|
459
437
|
: config.resourcesDirectoryPath;
|
|
460
438
|
const localPath = path.join(basePath, resourceKey);
|
|
461
439
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
}
|
|
471
|
-
if (!Result.isOk(mkdirResult)) throw mkdirResult.error;
|
|
440
|
+
try {
|
|
441
|
+
await fs.mkdir(basePath, { recursive: true });
|
|
442
|
+
} catch (cause) {
|
|
443
|
+
throw new ResourceError({
|
|
444
|
+
message: 'Failed to create resources directory',
|
|
445
|
+
hint: 'Check that you have write permissions to the btca data directory.',
|
|
446
|
+
cause
|
|
447
|
+
});
|
|
448
|
+
}
|
|
472
449
|
|
|
473
450
|
if (config.ephemeral) {
|
|
474
451
|
await cleanupDirectory(localPath);
|
|
475
452
|
}
|
|
476
453
|
|
|
477
|
-
return
|
|
454
|
+
return withMetricsSpan(
|
|
478
455
|
'resource.git.ensure',
|
|
479
456
|
async () => {
|
|
480
457
|
const exists = await directoryExists(localPath);
|
|
481
458
|
|
|
482
459
|
if (exists && !config.ephemeral) {
|
|
483
|
-
|
|
460
|
+
metricsInfo('resource.git.update', {
|
|
484
461
|
name: config.name,
|
|
485
462
|
branch: config.branch,
|
|
486
463
|
repoSubPaths: config.repoSubPaths
|
|
@@ -497,7 +474,7 @@ const ensureGitResource = async (config: BtcaGitResourceArgs): Promise<string> =
|
|
|
497
474
|
return localPath;
|
|
498
475
|
}
|
|
499
476
|
|
|
500
|
-
|
|
477
|
+
metricsInfo('resource.git.clone', {
|
|
501
478
|
name: config.name,
|
|
502
479
|
branch: config.ephemeral ? 'fallback' : config.branch,
|
|
503
480
|
repoSubPaths: config.repoSubPaths
|