@blogic-cz/agent-tools 0.18.0 → 1.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/README.md +23 -6
- package/dist/az-tool/config.d.ts +60 -12
- package/dist/az-tool/config.d.ts.map +1 -1
- package/dist/az-tool/errors.d.ts +10 -1
- package/dist/az-tool/errors.d.ts.map +1 -1
- package/dist/az-tool/profile.d.ts +16 -0
- package/dist/az-tool/profile.d.ts.map +1 -0
- package/dist/az-tool/security.d.ts +20 -3
- package/dist/az-tool/security.d.ts.map +1 -1
- package/dist/az-tool/service.d.ts +9 -4
- package/dist/az-tool/service.d.ts.map +1 -1
- package/dist/az-tool/types.d.ts +10 -60
- package/dist/az-tool/types.d.ts.map +1 -1
- package/dist/{az-tool → azdo-tool}/build.d.ts +9 -9
- package/dist/azdo-tool/build.d.ts.map +1 -0
- package/dist/azdo-tool/config.d.ts +13 -0
- package/dist/azdo-tool/config.d.ts.map +1 -0
- package/dist/azdo-tool/errors.d.ts +43 -0
- package/dist/azdo-tool/errors.d.ts.map +1 -0
- package/dist/azdo-tool/extract-option-value.d.ts.map +1 -0
- package/dist/azdo-tool/index.d.ts +3 -0
- package/dist/azdo-tool/index.d.ts.map +1 -0
- package/dist/azdo-tool/security.d.ts +4 -0
- package/dist/azdo-tool/security.d.ts.map +1 -0
- package/dist/azdo-tool/service.d.ts +15 -0
- package/dist/azdo-tool/service.d.ts.map +1 -0
- package/dist/azdo-tool/transformers.d.ts.map +1 -0
- package/dist/azdo-tool/types.d.ts +64 -0
- package/dist/azdo-tool/types.d.ts.map +1 -0
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/loader.d.ts +1 -1
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/types.d.ts +18 -1
- package/dist/config/types.d.ts.map +1 -1
- package/dist/credential-guard/index.d.ts.map +1 -1
- package/dist/gh-tool/errors.d.ts +1 -1
- package/dist/gh-tool/errors.d.ts.map +1 -1
- package/dist/gh-tool/pr/core.d.ts.map +1 -1
- package/dist/shared/azure-credentials.d.ts +20 -0
- package/dist/shared/azure-credentials.d.ts.map +1 -0
- package/dist/shared/binary-preflight.d.ts +1 -1
- package/package.json +8 -2
- package/schemas/agent-tools.schema.json +42 -1
- package/src/az-tool/config.ts +135 -25
- package/src/az-tool/errors.ts +14 -1
- package/src/az-tool/index.ts +67 -134
- package/src/az-tool/profile.ts +48 -0
- package/src/az-tool/security.ts +248 -89
- package/src/az-tool/service.ts +137 -242
- package/src/az-tool/types.ts +11 -64
- package/src/{az-tool → azdo-tool}/build.ts +11 -11
- package/src/azdo-tool/config.ts +33 -0
- package/src/azdo-tool/errors.ts +41 -0
- package/src/azdo-tool/index.ts +222 -0
- package/src/azdo-tool/security.ts +157 -0
- package/src/azdo-tool/service.ts +322 -0
- package/src/azdo-tool/types.ts +67 -0
- package/src/config/index.ts +1 -0
- package/src/config/loader.ts +10 -1
- package/src/config/types.ts +19 -1
- package/src/credential-guard/index.ts +7 -2
- package/src/gh-tool/errors.ts +7 -1
- package/src/gh-tool/pr/core.ts +179 -71
- package/src/shared/azure-credentials.ts +50 -0
- package/src/shared/binary-preflight.ts +1 -1
- package/dist/az-tool/build.d.ts.map +0 -1
- package/dist/az-tool/extract-option-value.d.ts.map +0 -1
- package/dist/az-tool/transformers.d.ts.map +0 -1
- /package/dist/{az-tool → azdo-tool}/extract-option-value.d.ts +0 -0
- /package/dist/{az-tool → azdo-tool}/transformers.d.ts +0 -0
- /package/src/{az-tool → azdo-tool}/extract-option-value.ts +0 -0
- /package/src/{az-tool → azdo-tool}/transformers.ts +0 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
|
|
2
|
+
import { Context, Effect, Layer, Option, Stream } from "effect";
|
|
3
|
+
|
|
4
|
+
import type { InvokeParams } from "./types";
|
|
5
|
+
import type { AzureConfig } from "#config/types";
|
|
6
|
+
|
|
7
|
+
import { DIRECT_AZ_COMMANDS, STANDALONE_AZ_COMMANDS } from "./config";
|
|
8
|
+
import { AzdoSecurityError, AzdoCommandError, AzdoTimeoutError, AzdoParseError } from "./errors";
|
|
9
|
+
import { isCommandAllowed, isInvokeAllowed } from "./security";
|
|
10
|
+
import { transformCmdOutput } from "./transformers";
|
|
11
|
+
import { ConfigService, getToolConfig } from "#config";
|
|
12
|
+
import { renderCommandLine, tokenizeCommandLine } from "#shared/exec";
|
|
13
|
+
|
|
14
|
+
export class AzdoService extends Context.Service<
|
|
15
|
+
AzdoService,
|
|
16
|
+
{
|
|
17
|
+
readonly runCommand: (
|
|
18
|
+
cmd: string,
|
|
19
|
+
project?: string,
|
|
20
|
+
) => Effect.Effect<
|
|
21
|
+
string,
|
|
22
|
+
AzdoSecurityError | AzdoCommandError | AzdoTimeoutError | AzdoParseError
|
|
23
|
+
>;
|
|
24
|
+
readonly runInvoke: (
|
|
25
|
+
params: InvokeParams,
|
|
26
|
+
) => Effect.Effect<
|
|
27
|
+
unknown,
|
|
28
|
+
AzdoSecurityError | AzdoCommandError | AzdoTimeoutError | AzdoParseError
|
|
29
|
+
>;
|
|
30
|
+
}
|
|
31
|
+
>()("@agent-tools/AzdoService") {
|
|
32
|
+
static readonly layer = Layer.effect(
|
|
33
|
+
AzdoService,
|
|
34
|
+
Effect.gen(function* () {
|
|
35
|
+
const config = yield* ConfigService;
|
|
36
|
+
const azConfig = getToolConfig<AzureConfig>(config, "azure");
|
|
37
|
+
|
|
38
|
+
if (!azConfig) {
|
|
39
|
+
const noConfigError = new AzdoCommandError({
|
|
40
|
+
message: "No Azure configuration found. Add an 'azure' section to agent-tools.json5.",
|
|
41
|
+
command: "unknown",
|
|
42
|
+
exitCode: -1,
|
|
43
|
+
hint: "Create or update agent-tools.json5 with an 'azure' section containing organization and defaultProject",
|
|
44
|
+
});
|
|
45
|
+
return {
|
|
46
|
+
runCommand: (_cmd: string, _project?: string) => Effect.fail(noConfigError),
|
|
47
|
+
runInvoke: (_params: InvokeParams) => Effect.fail(noConfigError),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const executor = yield* ChildProcessSpawner.ChildProcessSpawner;
|
|
52
|
+
|
|
53
|
+
const runAzCommand = (argv: readonly string[], timeoutMs: number) =>
|
|
54
|
+
Effect.scoped(
|
|
55
|
+
Effect.gen(function* () {
|
|
56
|
+
const command = ChildProcess.make("az", argv, {
|
|
57
|
+
stdout: "pipe",
|
|
58
|
+
stderr: "pipe",
|
|
59
|
+
});
|
|
60
|
+
const process = yield* executor.spawn(command);
|
|
61
|
+
|
|
62
|
+
const stdoutChunk = yield* process.stdout.pipe(Stream.decodeText(), Stream.runCollect);
|
|
63
|
+
const stdout = stdoutChunk.join("");
|
|
64
|
+
|
|
65
|
+
const stderrChunk = yield* process.stderr.pipe(Stream.decodeText(), Stream.runCollect);
|
|
66
|
+
const stderr = stderrChunk.join("");
|
|
67
|
+
|
|
68
|
+
const exitCode = yield* process.exitCode;
|
|
69
|
+
|
|
70
|
+
return { stdout, stderr, exitCode };
|
|
71
|
+
}),
|
|
72
|
+
).pipe(
|
|
73
|
+
Effect.timeoutOption(timeoutMs),
|
|
74
|
+
Effect.mapError(
|
|
75
|
+
(platformError) =>
|
|
76
|
+
new AzdoCommandError({
|
|
77
|
+
message: `Command execution failed: ${platformError.message}`,
|
|
78
|
+
command: renderCommandLine(["az", ...argv]),
|
|
79
|
+
exitCode: -1,
|
|
80
|
+
hint: "Check that the az CLI is installed and authenticated",
|
|
81
|
+
nextCommand: "az login",
|
|
82
|
+
retryable: true,
|
|
83
|
+
}),
|
|
84
|
+
),
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const resolveProject = (project?: string) => project ?? azConfig.defaultProject;
|
|
88
|
+
|
|
89
|
+
const runCommand = Effect.fn("AzdoService.runCommand")(function* (
|
|
90
|
+
cmd: string,
|
|
91
|
+
project?: string,
|
|
92
|
+
) {
|
|
93
|
+
const projectName = resolveProject(project);
|
|
94
|
+
|
|
95
|
+
const securityCheck = isCommandAllowed(cmd);
|
|
96
|
+
if (!securityCheck.allowed) {
|
|
97
|
+
return yield* new AzdoSecurityError({
|
|
98
|
+
message: securityCheck.reason ?? "Command not allowed",
|
|
99
|
+
command: cmd,
|
|
100
|
+
hint: "Only read-only az devops commands are allowed. Use build helpers for common operations.",
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const invokeParams = parseInvokeFromCommand(cmd);
|
|
105
|
+
if (invokeParams) {
|
|
106
|
+
const invokeResult = yield* runInvoke({
|
|
107
|
+
...invokeParams,
|
|
108
|
+
project: projectName,
|
|
109
|
+
});
|
|
110
|
+
return JSON.stringify(invokeResult);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const cmdWords = cmd.trim().split(/\s+/);
|
|
114
|
+
const firstWord = cmdWords[0]?.toLowerCase() ?? "";
|
|
115
|
+
const isDirectCommand = DIRECT_AZ_COMMANDS.includes(
|
|
116
|
+
firstWord as (typeof DIRECT_AZ_COMMANDS)[number],
|
|
117
|
+
);
|
|
118
|
+
const isStandaloneCommand = STANDALONE_AZ_COMMANDS.includes(
|
|
119
|
+
firstWord as (typeof STANDALONE_AZ_COMMANDS)[number],
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const cmdArgv = tokenizeCommandLine(cmd);
|
|
123
|
+
const scopeArgv = [
|
|
124
|
+
"--organization",
|
|
125
|
+
azConfig.organization,
|
|
126
|
+
"--project",
|
|
127
|
+
projectName,
|
|
128
|
+
] as const;
|
|
129
|
+
|
|
130
|
+
const argv = isStandaloneCommand
|
|
131
|
+
? cmdArgv
|
|
132
|
+
: isDirectCommand
|
|
133
|
+
? [...cmdArgv, ...scopeArgv]
|
|
134
|
+
: ["devops", ...cmdArgv, ...scopeArgv];
|
|
135
|
+
|
|
136
|
+
const fullCommand = renderCommandLine(["az", ...argv]);
|
|
137
|
+
const resultOption = yield* runAzCommand(argv, azConfig.timeoutMs ?? 60000);
|
|
138
|
+
|
|
139
|
+
if (Option.isNone(resultOption)) {
|
|
140
|
+
return yield* new AzdoTimeoutError({
|
|
141
|
+
message: `Command timed out after ${azConfig.timeoutMs ?? 60000}ms`,
|
|
142
|
+
command: fullCommand,
|
|
143
|
+
timeoutMs: azConfig.timeoutMs ?? 60000,
|
|
144
|
+
retryable: true,
|
|
145
|
+
hint: "The command took too long. Retry or increase timeoutMs in azure config.",
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const result = resultOption.value;
|
|
150
|
+
|
|
151
|
+
if (result.exitCode !== 0) {
|
|
152
|
+
return yield* new AzdoCommandError({
|
|
153
|
+
message: result.stderr || `Command failed with exit code ${result.exitCode}`,
|
|
154
|
+
command: fullCommand,
|
|
155
|
+
exitCode: result.exitCode,
|
|
156
|
+
...(result.stderr ? { stderr: result.stderr } : {}),
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const output = result.stdout.trim();
|
|
161
|
+
const transformed = transformCmdOutput(output);
|
|
162
|
+
|
|
163
|
+
if (typeof transformed === "string") {
|
|
164
|
+
return transformed;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return JSON.stringify(transformed);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const runInvoke = Effect.fn("AzdoService.runInvoke")(function* (params: InvokeParams) {
|
|
171
|
+
const securityCheck = isInvokeAllowed(params);
|
|
172
|
+
if (!securityCheck.allowed) {
|
|
173
|
+
return yield* new AzdoSecurityError({
|
|
174
|
+
message: securityCheck.reason ?? "Invoke not allowed",
|
|
175
|
+
command: `invoke --area ${params.area} --resource ${params.resource}`,
|
|
176
|
+
hint: "Only allowed invoke areas/resources can be used. Check azdo-tool security config.",
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const argv = ["devops", "invoke", "--area", params.area, "--resource", params.resource];
|
|
181
|
+
|
|
182
|
+
const projectName = resolveProject(params.project);
|
|
183
|
+
|
|
184
|
+
const routeParameters = {
|
|
185
|
+
project: projectName,
|
|
186
|
+
...params.routeParameters,
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
if (Object.keys(routeParameters).length > 0) {
|
|
190
|
+
argv.push(
|
|
191
|
+
"--route-parameters",
|
|
192
|
+
...Object.entries(routeParameters).map(([k, v]) => `${k}=${v}`),
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (params.queryParameters) {
|
|
197
|
+
argv.push(
|
|
198
|
+
"--query-parameters",
|
|
199
|
+
...Object.entries(params.queryParameters).map(([k, v]) => `${k}=${v}`),
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
argv.push("--organization", azConfig.organization, "--output", "json");
|
|
204
|
+
|
|
205
|
+
const fullCommand = renderCommandLine(["az", ...argv]);
|
|
206
|
+
const resultOption = yield* runAzCommand(argv, azConfig.timeoutMs ?? 60000);
|
|
207
|
+
|
|
208
|
+
if (Option.isNone(resultOption)) {
|
|
209
|
+
return yield* new AzdoTimeoutError({
|
|
210
|
+
message: `Invoke timed out after ${azConfig.timeoutMs ?? 60000}ms`,
|
|
211
|
+
command: fullCommand,
|
|
212
|
+
timeoutMs: azConfig.timeoutMs ?? 60000,
|
|
213
|
+
retryable: true,
|
|
214
|
+
hint: "The invoke took too long. Retry or increase timeoutMs in azure config.",
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const result = resultOption.value;
|
|
219
|
+
|
|
220
|
+
if (result.exitCode !== 0) {
|
|
221
|
+
return yield* new AzdoCommandError({
|
|
222
|
+
message: result.stderr || `Invoke failed with exit code ${result.exitCode}`,
|
|
223
|
+
command: fullCommand,
|
|
224
|
+
exitCode: result.exitCode,
|
|
225
|
+
...(result.stderr ? { stderr: result.stderr } : {}),
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const jsonData = yield* Effect.try({
|
|
230
|
+
try: () => JSON.parse(result.stdout) as unknown,
|
|
231
|
+
catch: () =>
|
|
232
|
+
new AzdoParseError({
|
|
233
|
+
message: `Failed to parse JSON response from invoke`,
|
|
234
|
+
rawOutput: result.stdout.slice(0, 500),
|
|
235
|
+
hint: "The az CLI returned non-JSON output. Ensure --output json is used.",
|
|
236
|
+
}),
|
|
237
|
+
});
|
|
238
|
+
return jsonData;
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
return { runCommand, runInvoke };
|
|
242
|
+
}),
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export const AzdoServiceLayer = AzdoService.layer;
|
|
247
|
+
|
|
248
|
+
function parseInvokeFromCommand(cmd: string): InvokeParams | undefined {
|
|
249
|
+
const words = cmd.trim().split(/\s+/);
|
|
250
|
+
const loweredWords = words.map((word) => word.toLowerCase());
|
|
251
|
+
|
|
252
|
+
if (!loweredWords.includes("invoke")) {
|
|
253
|
+
return undefined;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const area = extractOptionValue(words, "--area");
|
|
257
|
+
const resource = extractOptionValue(words, "--resource");
|
|
258
|
+
|
|
259
|
+
if (!area || !resource) {
|
|
260
|
+
return undefined;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const routeParameters = extractParametersOption(words, "--route-parameters");
|
|
264
|
+
const queryParameters = extractParametersOption(words, "--query-parameters");
|
|
265
|
+
const apiVersion = extractOptionValue(words, "--api-version");
|
|
266
|
+
|
|
267
|
+
const mergedQueryParameters = apiVersion
|
|
268
|
+
? {
|
|
269
|
+
...queryParameters,
|
|
270
|
+
"api-version": apiVersion,
|
|
271
|
+
}
|
|
272
|
+
: queryParameters;
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
area,
|
|
276
|
+
resource,
|
|
277
|
+
...(routeParameters ? { routeParameters } : {}),
|
|
278
|
+
...(mergedQueryParameters ? { queryParameters: mergedQueryParameters } : {}),
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function extractOptionValue(args: readonly string[], optionName: string): string | undefined {
|
|
283
|
+
const optionIndex = args.findIndex((arg) => arg.toLowerCase() === optionName.toLowerCase());
|
|
284
|
+
|
|
285
|
+
if (optionIndex === -1) {
|
|
286
|
+
return undefined;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return args[optionIndex + 1];
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function extractParametersOption(
|
|
293
|
+
args: readonly string[],
|
|
294
|
+
optionName: string,
|
|
295
|
+
): Record<string, string | number> | undefined {
|
|
296
|
+
const optionIndex = args.findIndex((arg) => arg.toLowerCase() === optionName.toLowerCase());
|
|
297
|
+
|
|
298
|
+
if (optionIndex === -1) {
|
|
299
|
+
return undefined;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const result: Record<string, string | number> = {};
|
|
303
|
+
|
|
304
|
+
for (let i = optionIndex + 1; i < args.length; i++) {
|
|
305
|
+
const token = args[i];
|
|
306
|
+
if (!token || token.startsWith("--")) {
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const equalsIndex = token.indexOf("=");
|
|
311
|
+
if (equalsIndex === -1) {
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const key = token.slice(0, equalsIndex);
|
|
316
|
+
const rawValue = token.slice(equalsIndex + 1);
|
|
317
|
+
const parsedNumber = Number(rawValue);
|
|
318
|
+
result[key] = Number.isNaN(parsedNumber) ? rawValue : parsedNumber;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
322
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export type SecurityCheckResult = {
|
|
2
|
+
allowed: boolean;
|
|
3
|
+
command?: string;
|
|
4
|
+
reason?: string;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export type InvokeParams = {
|
|
8
|
+
area: string;
|
|
9
|
+
resource: string;
|
|
10
|
+
project?: string;
|
|
11
|
+
routeParameters?: Record<string, string | number>;
|
|
12
|
+
queryParameters?: Record<string, string | number>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type BuildJob = {
|
|
16
|
+
id: string;
|
|
17
|
+
parentId?: string | null;
|
|
18
|
+
type: "Job" | "Stage" | "Task" | "Phase" | "Checkpoint";
|
|
19
|
+
name: string;
|
|
20
|
+
state: "pending" | "inProgress" | "completed";
|
|
21
|
+
result?: "succeeded" | "failed" | "canceled" | "skipped" | null;
|
|
22
|
+
startTime?: string | null;
|
|
23
|
+
finishTime?: string | null;
|
|
24
|
+
errorCount?: number | null;
|
|
25
|
+
warningCount?: number | null;
|
|
26
|
+
log?: { id: number; url: string } | null;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type BuildTimeline = {
|
|
30
|
+
records: BuildJob[];
|
|
31
|
+
id: string;
|
|
32
|
+
changeId: number;
|
|
33
|
+
lastChangedBy: string;
|
|
34
|
+
lastChangedOn: string;
|
|
35
|
+
url: string;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type BuildLog = {
|
|
39
|
+
id: number;
|
|
40
|
+
type: string;
|
|
41
|
+
url: string;
|
|
42
|
+
lineCount?: number;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export type BuildLogs = {
|
|
46
|
+
count: number;
|
|
47
|
+
value: BuildLog[];
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type JobSummary = {
|
|
51
|
+
name: string;
|
|
52
|
+
state: string;
|
|
53
|
+
result?: string;
|
|
54
|
+
stage?: string;
|
|
55
|
+
duration?: string;
|
|
56
|
+
logId?: number;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export type PipelineRun = {
|
|
60
|
+
id: number;
|
|
61
|
+
buildNumber: string;
|
|
62
|
+
status: string;
|
|
63
|
+
result?: string;
|
|
64
|
+
sourceBranch: string;
|
|
65
|
+
startTime?: string;
|
|
66
|
+
finishTime?: string;
|
|
67
|
+
};
|
package/src/config/index.ts
CHANGED
package/src/config/loader.ts
CHANGED
|
@@ -88,6 +88,13 @@ const AzureConfigSchema = Schema.Struct({
|
|
|
88
88
|
timeoutMs: Schema.optionalKey(Schema.Number),
|
|
89
89
|
});
|
|
90
90
|
|
|
91
|
+
const AzurePlatformConfigSchema = Schema.Struct({
|
|
92
|
+
subscription: Schema.String,
|
|
93
|
+
timeoutMs: Schema.optionalKey(Schema.Number),
|
|
94
|
+
production: Schema.optionalKey(Schema.Boolean),
|
|
95
|
+
allowedResourceGroups: Schema.optionalKey(Schema.Array(Schema.String)),
|
|
96
|
+
});
|
|
97
|
+
|
|
91
98
|
const K8sConfigSchema = Schema.Struct({
|
|
92
99
|
kubeconfig: Schema.optionalKey(Schema.String),
|
|
93
100
|
clusterId: Schema.String,
|
|
@@ -178,6 +185,7 @@ const GitHubRepoConfigSchema = Schema.Struct({
|
|
|
178
185
|
const KNOWN_TOP_LEVEL_KEYS = new Set([
|
|
179
186
|
"$schema",
|
|
180
187
|
"azure",
|
|
188
|
+
"azurePlatform",
|
|
181
189
|
"vpns",
|
|
182
190
|
"kubernetes",
|
|
183
191
|
"database",
|
|
@@ -193,6 +201,7 @@ const KNOWN_TOP_LEVEL_KEYS = new Set([
|
|
|
193
201
|
const AgentToolsConfigSchema = Schema.Struct({
|
|
194
202
|
$schema: Schema.optionalKey(Schema.String),
|
|
195
203
|
azure: Schema.optionalKey(Schema.Record(Schema.String, AzureConfigSchema)),
|
|
204
|
+
azurePlatform: Schema.optionalKey(Schema.Record(Schema.String, AzurePlatformConfigSchema)),
|
|
196
205
|
vpns: Schema.optionalKey(Schema.Record(Schema.String, VpnConfigSchema)),
|
|
197
206
|
kubernetes: Schema.optionalKey(Schema.Record(Schema.String, K8sConfigSchema)),
|
|
198
207
|
database: Schema.optionalKey(Schema.Record(Schema.String, DatabaseConfigSchema)),
|
|
@@ -378,7 +387,7 @@ export const ConfigServiceLayer = Layer.effect(
|
|
|
378
387
|
|
|
379
388
|
type ProfiledSection = keyof Pick<
|
|
380
389
|
AgentToolsConfig,
|
|
381
|
-
"azure" | "kubernetes" | "database" | "observability" | "logs"
|
|
390
|
+
"azure" | "azurePlatform" | "kubernetes" | "database" | "observability" | "logs"
|
|
382
391
|
>;
|
|
383
392
|
|
|
384
393
|
export function getToolConfig<T>(
|
package/src/config/types.ts
CHANGED
|
@@ -1,12 +1,28 @@
|
|
|
1
1
|
import { Schema } from "effect";
|
|
2
2
|
|
|
3
|
-
/** Azure DevOps profile
|
|
3
|
+
/** Azure DevOps profile, consumed by azdo-tool. */
|
|
4
4
|
export type AzureConfig = {
|
|
5
5
|
organization: string;
|
|
6
6
|
defaultProject: string;
|
|
7
7
|
timeoutMs?: number;
|
|
8
8
|
};
|
|
9
9
|
|
|
10
|
+
/** Azure platform (PaaS) profile, consumed by az-tool. */
|
|
11
|
+
export type AzurePlatformConfig = {
|
|
12
|
+
subscription: string;
|
|
13
|
+
timeoutMs?: number;
|
|
14
|
+
/**
|
|
15
|
+
* Require --profile to be passed explicitly before this subscription is
|
|
16
|
+
* touched. Defaults to true for profiles keyed "prod" or "production".
|
|
17
|
+
*/
|
|
18
|
+
production?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Resource groups this profile may address. Empty or absent means every
|
|
21
|
+
* resource group in the subscription is allowed.
|
|
22
|
+
*/
|
|
23
|
+
allowedResourceGroups?: string[];
|
|
24
|
+
};
|
|
25
|
+
|
|
10
26
|
export type CleanupPolicy = "leave-running" | "stop-if-started";
|
|
11
27
|
|
|
12
28
|
export type VpnPrerequisite = {
|
|
@@ -206,6 +222,8 @@ export type AgentToolsConfig = {
|
|
|
206
222
|
$schema?: string;
|
|
207
223
|
/** Named Azure DevOps profiles. e.g. { default: { organization: "...", defaultProject: "..." } } */
|
|
208
224
|
azure?: Record<string, AzureConfig>;
|
|
225
|
+
/** Named Azure platform profiles. e.g. { default: { subscription: "..." } } */
|
|
226
|
+
azurePlatform?: Record<string, AzurePlatformConfig>;
|
|
209
227
|
/** Named VPN definitions referenced by profile prerequisites. */
|
|
210
228
|
vpns?: Record<string, VpnConfig>;
|
|
211
229
|
/** Named Kubernetes cluster profiles. e.g. { default: {...}, staging: {...} } */
|
|
@@ -173,6 +173,11 @@ const DEFAULT_BLOCKED_CLI_TOOLS: BlockedCliTool[] = [
|
|
|
173
173
|
name: "psql",
|
|
174
174
|
wrapper: "agent-tools-db",
|
|
175
175
|
},
|
|
176
|
+
{
|
|
177
|
+
pattern: /(?:^|[;&|]\s*)az\s+(?:devops|pipelines|repos|boards|artifacts)\b/,
|
|
178
|
+
name: "az (Azure DevOps)",
|
|
179
|
+
wrapper: "agent-tools-azdo",
|
|
180
|
+
},
|
|
176
181
|
{
|
|
177
182
|
pattern: /(?:^|[;&|]\s*)az\s/,
|
|
178
183
|
name: "az",
|
|
@@ -181,7 +186,7 @@ const DEFAULT_BLOCKED_CLI_TOOLS: BlockedCliTool[] = [
|
|
|
181
186
|
{
|
|
182
187
|
pattern: /(?:^|[;&|]\s*)curl\s.*dev\.azure\.com/,
|
|
183
188
|
name: "curl (Azure DevOps)",
|
|
184
|
-
wrapper: "agent-tools-
|
|
189
|
+
wrapper: "agent-tools-azdo",
|
|
185
190
|
},
|
|
186
191
|
];
|
|
187
192
|
|
|
@@ -209,7 +214,7 @@ const DEFAULT_POLLING_DETECTION_RULES: PollingDetectionRule[] = [
|
|
|
209
214
|
},
|
|
210
215
|
{
|
|
211
216
|
pattern: /\bpipelines?\s+runs?\b/,
|
|
212
|
-
suggestion: "bun agent-tools-
|
|
217
|
+
suggestion: "bun agent-tools-azdo build summary --build-id <ID>",
|
|
213
218
|
},
|
|
214
219
|
];
|
|
215
220
|
|
package/src/gh-tool/errors.ts
CHANGED
|
@@ -34,7 +34,13 @@ export class GitHubAuthError extends Schema.TaggedError<GitHubAuthError>()("GitH
|
|
|
34
34
|
|
|
35
35
|
export class GitHubMergeError extends Schema.TaggedError<GitHubMergeError>()("GitHubMergeError", {
|
|
36
36
|
message: Schema.String,
|
|
37
|
-
reason: Schema.Literals([
|
|
37
|
+
reason: Schema.Literals([
|
|
38
|
+
"conflicts",
|
|
39
|
+
"checks_failing",
|
|
40
|
+
"branch_protected",
|
|
41
|
+
"merge_queue",
|
|
42
|
+
"unknown",
|
|
43
|
+
]),
|
|
38
44
|
hint: Schema.optionalKey(Schema.String),
|
|
39
45
|
nextCommand: Schema.optionalKey(Schema.String),
|
|
40
46
|
retryable: Schema.optionalKey(Schema.Boolean),
|