@skyramp/mcp 0.2.5 → 0.2.7
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/build/commands/commandLibrary.d.ts +12 -0
- package/build/commands/recommendTestsAndExecuteCommand.d.ts +2 -0
- package/build/commands/testThisEndpointCommand.d.ts +2 -0
- package/build/index.d.ts +2 -0
- package/build/index.js +1 -2
- package/build/playwright/PlaywrightTraceService.d.ts +32 -0
- package/build/playwright/index.d.ts +3 -0
- package/build/playwright/registerPlaywrightTools.d.ts +8 -0
- package/build/playwright/registerPlaywrightTools.js +12 -11
- package/build/playwright/traceRecordingPrompt.d.ts +13 -0
- package/build/playwright/traceRecordingPrompt.js +3 -11
- package/build/prompts/code-reuse.d.ts +1 -0
- package/build/prompts/enhance-assertions/contractProviderAssertionsPrompt.d.ts +2 -0
- package/build/prompts/enhance-assertions/integrationAssertionsPrompt.d.ts +2 -0
- package/build/prompts/enhance-assertions/sharedAssertionRules.d.ts +15 -0
- package/build/prompts/enhance-assertions/uiAssertionsPrompt.d.ts +2 -0
- package/build/prompts/enhance-assertions/uiAssertionsPrompt.js +14 -3
- package/build/prompts/fix-error-prompt.d.ts +1 -0
- package/build/prompts/initialize-workspace/initializeWorkspacePrompt.d.ts +1 -0
- package/build/prompts/modularization/integration-test-modularization.d.ts +1 -0
- package/build/prompts/modularization/ui-test-modularization.d.ts +1 -0
- package/build/prompts/personas.d.ts +21 -0
- package/build/prompts/pom-aware-code-reuse.d.ts +1 -0
- package/build/prompts/startTraceCollectionPrompts.d.ts +2 -0
- package/build/prompts/test-maintenance/actionsInstructions.d.ts +35 -0
- package/build/prompts/test-maintenance/drift-analysis-prompt.d.ts +4 -0
- package/build/prompts/test-maintenance/driftAnalysisSections.d.ts +13 -0
- package/build/prompts/test-recommendation/analysisOutputPrompt.d.ts +84 -0
- package/build/prompts/test-recommendation/analysisOutputPrompt.test.d.ts +1 -0
- package/build/prompts/test-recommendation/analysisOutputPrompt.test.js +1 -0
- package/build/prompts/test-recommendation/diffExecutionPlan.d.ts +17 -0
- package/build/prompts/test-recommendation/diffExecutionPlan.js +7 -16
- package/build/prompts/test-recommendation/fullRepoCatalog.d.ts +7 -0
- package/build/prompts/test-recommendation/mergeEnrichedScenarios.test.d.ts +1 -0
- package/build/prompts/test-recommendation/mergeEnrichedScenarios.test.js +1 -0
- package/build/prompts/test-recommendation/promptPlan.d.ts +203 -0
- package/build/prompts/test-recommendation/promptPlan.test.d.ts +1 -0
- package/build/prompts/test-recommendation/recommendationSections.d.ts +35 -0
- package/build/prompts/test-recommendation/recommendationShared.d.ts +31 -0
- package/build/prompts/test-recommendation/registerRecommendTestsPrompt.d.ts +7 -0
- package/build/prompts/test-recommendation/scopeAssessment.d.ts +55 -0
- package/build/prompts/test-recommendation/scopeAssessment.js +2 -2
- package/build/prompts/test-recommendation/scopeAssessment.test.d.ts +1 -0
- package/build/prompts/test-recommendation/scopeAssessment.test.js +1 -0
- package/build/prompts/test-recommendation/test-recommendation-prompt.d.ts +6 -0
- package/build/prompts/test-recommendation/test-recommendation-prompt.js +4 -4
- package/build/prompts/test-recommendation/test-recommendation-prompt.test.d.ts +1 -0
- package/build/prompts/test-recommendation/test-recommendation-prompt.test.js +6 -99
- package/build/prompts/testbot/testbot-prompts.d.ts +11 -0
- package/build/prompts/testbot/testbot-prompts.js +198 -34
- package/build/prompts/testbot/testbot-prompts.test.d.ts +1 -0
- package/build/prompts/testbot/testbot-prompts.test.js +15 -73
- package/build/resources/analysisResources.d.ts +17 -0
- package/build/resources/progressResource.d.ts +2 -0
- package/build/resources/testbotResource.d.ts +2 -0
- package/build/services/AnalyticsService.d.ts +13 -0
- package/build/services/AnalyticsService.test.d.ts +1 -0
- package/build/services/AnalyticsService.test.js +86 -0
- package/build/services/ModularizationService.d.ts +11 -0
- package/build/services/ScenarioGenerationService.d.ts +37 -0
- package/build/services/ScenarioGenerationService.integration.test.d.ts +1 -0
- package/build/services/ScenarioGenerationService.integration.test.js +4 -0
- package/build/services/ScenarioGenerationService.test.d.ts +1 -0
- package/build/services/TestDiscoveryService.d.ts +128 -0
- package/build/services/TestDiscoveryService.test.d.ts +1 -0
- package/build/services/TestDiscoveryService.test.js +4 -3
- package/build/services/TestExecutionService.d.ts +46 -0
- package/build/services/TestExecutionService.test.d.ts +1 -0
- package/build/services/TestExecutionService.test.js +33 -25
- package/build/services/TestGenerationService.d.ts +56 -0
- package/build/services/TestGenerationService.test.d.ts +1 -0
- package/build/services/TestGenerationService.test.js +10 -8
- package/build/services/containerEnv.d.ts +14 -0
- package/build/tool-phase-coverage.test.d.ts +1 -0
- package/build/tool-phase-coverage.test.js +2 -0
- package/build/tool-phases.d.ts +38 -0
- package/build/tools/auth/loginTool.d.ts +2 -0
- package/build/tools/auth/logoutTool.d.ts +2 -0
- package/build/tools/code-refactor/codeReuseTool.d.ts +2 -0
- package/build/tools/code-refactor/enhanceAssertionsTool.d.ts +2 -0
- package/build/tools/code-refactor/modularizationTool.d.ts +2 -0
- package/build/tools/executeSkyrampTestTool.d.ts +2 -0
- package/build/tools/fixErrorTool.d.ts +2 -0
- package/build/tools/generate-tests/generateBatchScenarioRestTool.d.ts +45 -0
- package/build/tools/generate-tests/generateBatchScenarioRestTool.test.d.ts +1 -0
- package/build/tools/generate-tests/generateBatchScenarioRestTool.test.js +11 -9
- package/build/tools/generate-tests/generateContractRestTool.d.ts +31 -0
- package/build/tools/generate-tests/generateE2ERestTool.d.ts +13 -0
- package/build/tools/generate-tests/generateFuzzRestTool.d.ts +10 -0
- package/build/tools/generate-tests/generateIntegrationRestTool.d.ts +16 -0
- package/build/tools/generate-tests/generateLoadRestTool.d.ts +18 -0
- package/build/tools/generate-tests/generateLoadRestTool.test.d.ts +1 -0
- package/build/tools/generate-tests/generateMockRestTool.d.ts +64 -0
- package/build/tools/generate-tests/generateSmokeRestTool.d.ts +8 -0
- package/build/tools/generate-tests/generateUIRestTool.d.ts +14 -0
- package/build/tools/generate-tests/generateUIRestTool.js +1 -1
- package/build/tools/generate-tests/loadTestSchema.d.ts +39 -0
- package/build/tools/one-click/oneClickTool.d.ts +11 -0
- package/build/tools/submitReportTool.d.ts +384 -0
- package/build/tools/submitReportTool.js +144 -2
- package/build/tools/submitReportTool.test.d.ts +1 -0
- package/build/tools/submitReportTool.test.js +215 -5
- package/build/tools/test-management/actionsTool.d.ts +10 -0
- package/build/tools/test-management/analyzeChangesTool.d.ts +27 -0
- package/build/tools/test-management/analyzeChangesTool.js +44 -7
- package/build/tools/test-management/analyzeChangesTool.test.d.ts +1 -0
- package/build/tools/test-management/analyzeChangesTool.test.js +76 -39
- package/build/tools/test-management/analyzeTestHealthTool.d.ts +2 -0
- package/build/tools/test-management/index.d.ts +3 -0
- package/build/tools/test-management/index.js +0 -1
- package/build/tools/trace/resolveSaveStoragePath.d.ts +7 -0
- package/build/tools/trace/resolveSaveStoragePath.test.d.ts +1 -0
- package/build/tools/trace/resolveSessionPaths.d.ts +40 -0
- package/build/tools/trace/resolveSessionPaths.test.d.ts +1 -0
- package/build/tools/trace/sessionState.d.ts +2 -0
- package/build/tools/trace/sessionState.test.d.ts +1 -0
- package/build/tools/trace/startTraceCollectionTool.d.ts +2 -0
- package/build/tools/trace/stopTraceCollectionTool.d.ts +2 -0
- package/build/tools/workspace/initScanWorkspaceTool.d.ts +4 -0
- package/build/tools/workspace/initializeWorkspaceTool.d.ts +2 -0
- package/build/tools/workspace/initializeWorkspaceTool.test.d.ts +1 -0
- package/build/tools/workspace/initializeWorkspaceTool.test.js +134 -0
- package/build/types/OneClickCommands.d.ts +68 -0
- package/build/types/RepositoryAnalysis.d.ts +3004 -0
- package/build/types/TestAnalysis.d.ts +96 -0
- package/build/types/TestExecution.d.ts +53 -0
- package/build/types/TestRecommendation.d.ts +12 -0
- package/build/types/TestTypes.d.ts +234 -0
- package/build/utils/AnalysisStateManager.d.ts +182 -0
- package/build/utils/AnalysisStateManager.test.d.ts +1 -0
- package/build/utils/analyze-openapi.d.ts +1 -0
- package/build/utils/branchDiff.d.ts +18 -0
- package/build/utils/dartRouteExtractor.d.ts +45 -0
- package/build/utils/dartRouteExtractor.test.d.ts +1 -0
- package/build/utils/docker.d.ts +19 -0
- package/build/utils/docker.test.d.ts +1 -0
- package/build/utils/docker.test.js +10 -9
- package/build/utils/featureFlags.d.ts +37 -0
- package/build/utils/featureFlags.test.d.ts +1 -0
- package/build/utils/gitStaging.d.ts +24 -0
- package/build/utils/gitStaging.test.d.ts +1 -0
- package/build/utils/gitStaging.test.js +13 -7
- package/build/utils/httpDefaults.d.ts +10 -0
- package/build/utils/httpDefaults.test.d.ts +1 -0
- package/build/utils/initAgent.d.ts +36 -0
- package/build/utils/language-helper.d.ts +6 -0
- package/build/utils/logger.d.ts +11 -0
- package/build/utils/normalizeParams.d.ts +14 -0
- package/build/utils/normalizeSkyrampImports.d.ts +13 -0
- package/build/utils/pr-comment-parser.d.ts +31 -0
- package/build/utils/pr-comment-parser.test.d.ts +1 -0
- package/build/utils/pr-comment-parser.test.js +6 -5
- package/build/utils/projectMetadata.d.ts +15 -0
- package/build/utils/projectMetadata.test.d.ts +1 -0
- package/build/utils/proxy-terminal.d.ts +2 -0
- package/build/utils/repoScanner.d.ts +27 -0
- package/build/utils/repoScanner.test.d.ts +1 -0
- package/build/utils/routeParsers.d.ts +63 -0
- package/build/utils/routeParsers.test.d.ts +1 -0
- package/build/utils/scenarioDrafting.d.ts +71 -0
- package/build/utils/scenarioDrafting.test.d.ts +1 -0
- package/build/utils/skyrampMdContent.d.ts +5 -0
- package/build/utils/sourceRouteExtractor.d.ts +48 -0
- package/build/utils/sourceRouteExtractor.test.d.ts +1 -0
- package/build/utils/telemetry.d.ts +32 -0
- package/build/utils/telemetry.test.d.ts +1 -0
- package/build/utils/trace-parser.d.ts +32 -0
- package/build/utils/trace-parser.test.d.ts +6 -0
- package/build/utils/uiPageEnumerator.d.ts +146 -0
- package/build/utils/uiPageEnumerator.js +58 -14
- package/build/utils/uiPageEnumerator.test.d.ts +1 -0
- package/build/utils/uiPageEnumerator.test.js +223 -43
- package/build/utils/utils.d.ts +39 -0
- package/build/utils/utils.test.d.ts +1 -0
- package/build/utils/versions.d.ts +3 -0
- package/build/utils/versions.js +1 -1
- package/build/utils/workspaceAuth.d.ts +127 -0
- package/build/utils/workspaceAuth.test.d.ts +1 -0
- package/build/utils/workspaceAuth.test.js +25 -25
- package/build/workspace/index.d.ts +1 -0
- package/build/workspace/workspace.d.ts +390 -0
- package/build/workspace/workspace.test.d.ts +1 -0
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +14 -2
- package/package.json +8 -3
- package/build/tools/test-management/uiAnalyzeChangesTool.js +0 -155
- package/build/tools/test-management/uiAnalyzeChangesTool.test.js +0 -147
|
@@ -1,13 +1,20 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
jest.mock("@skyramp/skyramp", () => ({
|
|
4
|
-
WorkspaceConfigManager: { create: jest.fn() },
|
|
5
|
-
}));
|
|
1
|
+
// @ts-nocheck - Jest ESM type inference issues
|
|
2
|
+
import { jest } from "@jest/globals";
|
|
6
3
|
import * as fs from "fs";
|
|
7
4
|
import * as os from "os";
|
|
8
5
|
import * as path from "path";
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
// Under native ESM, imported module namespaces are read-only, so jest.spyOn on
|
|
7
|
+
// workspaceAuth fails. Partial-mock the module: keep every real export the
|
|
8
|
+
// subject's import graph needs and override only readWorkspaceConfigRaw.
|
|
9
|
+
// jest.requireActual can't load an ESM module synchronously, so import the real
|
|
10
|
+
// module first (unstable_mockModule is not hoisted) and spread its namespace.
|
|
11
|
+
const realWorkspaceAuth = await import("./workspaceAuth.js");
|
|
12
|
+
const readWorkspaceConfigRaw = jest.fn();
|
|
13
|
+
jest.unstable_mockModule("./workspaceAuth.js", () => ({
|
|
14
|
+
...realWorkspaceAuth,
|
|
15
|
+
readWorkspaceConfigRaw,
|
|
16
|
+
}));
|
|
17
|
+
const { frameworkFileToUrlPath, findCandidatePagesByFrameworkRoute, findCandidatePagesBySourceRoute, findRootFallbackPage, enumerateCandidateUiPages, pickFrontendBaseUrl, detectsFilesystemRouting, } = await import("./uiPageEnumerator.js");
|
|
11
18
|
// Helper to create a tmp repo dir with optional config files; returns the path.
|
|
12
19
|
function makeTmpRepo(configFiles = []) {
|
|
13
20
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "skyramp-test-repo-"));
|
|
@@ -191,25 +198,21 @@ export default function App() {
|
|
|
191
198
|
// pickFrontendBaseUrl
|
|
192
199
|
// ---------------------------------------------------------------------------
|
|
193
200
|
describe("pickFrontendBaseUrl", () => {
|
|
194
|
-
let readSpy;
|
|
195
|
-
beforeEach(() => {
|
|
196
|
-
readSpy = jest.spyOn(workspaceAuth, "readWorkspaceConfigRaw");
|
|
197
|
-
});
|
|
198
201
|
afterEach(() => {
|
|
199
|
-
|
|
202
|
+
readWorkspaceConfigRaw.mockReset();
|
|
200
203
|
});
|
|
201
204
|
it("returns null when workspace config is missing", async () => {
|
|
202
|
-
|
|
205
|
+
readWorkspaceConfigRaw.mockResolvedValue(null);
|
|
203
206
|
expect(await pickFrontendBaseUrl("/repo")).toBeNull();
|
|
204
207
|
});
|
|
205
208
|
it("returns null when no services have baseUrl", async () => {
|
|
206
|
-
|
|
209
|
+
readWorkspaceConfigRaw.mockResolvedValue({
|
|
207
210
|
services: [{ serviceName: "backend", language: "python" }],
|
|
208
211
|
});
|
|
209
212
|
expect(await pickFrontendBaseUrl("/repo")).toBeNull();
|
|
210
213
|
});
|
|
211
214
|
it("picks the frontend service by name suffix", async () => {
|
|
212
|
-
|
|
215
|
+
readWorkspaceConfigRaw.mockResolvedValue({
|
|
213
216
|
services: [
|
|
214
217
|
{ serviceName: "demoshop-backend", api: { baseUrl: "http://localhost:8000" } },
|
|
215
218
|
{ serviceName: "demoshop-frontend", api: { baseUrl: "http://localhost:5173" } },
|
|
@@ -218,7 +221,7 @@ describe("pickFrontendBaseUrl", () => {
|
|
|
218
221
|
expect(await pickFrontendBaseUrl("/repo")).toBe("http://localhost:5173");
|
|
219
222
|
});
|
|
220
223
|
it("picks the frontend service by framework when name doesn't signal it", async () => {
|
|
221
|
-
|
|
224
|
+
readWorkspaceConfigRaw.mockResolvedValue({
|
|
222
225
|
services: [
|
|
223
226
|
{ serviceName: "api", language: "python", api: { baseUrl: "http://localhost:8000" } },
|
|
224
227
|
{
|
|
@@ -232,7 +235,7 @@ describe("pickFrontendBaseUrl", () => {
|
|
|
232
235
|
expect(await pickFrontendBaseUrl("/repo")).toBe("http://localhost:3000");
|
|
233
236
|
});
|
|
234
237
|
it("falls back to first service with baseUrl when no frontend signal", async () => {
|
|
235
|
-
|
|
238
|
+
readWorkspaceConfigRaw.mockResolvedValue({
|
|
236
239
|
services: [
|
|
237
240
|
{ serviceName: "svc-a", language: "go", api: { baseUrl: "http://localhost:8000" } },
|
|
238
241
|
{ serviceName: "svc-b", language: "go", api: { baseUrl: "http://localhost:8001" } },
|
|
@@ -247,7 +250,7 @@ describe("pickFrontendBaseUrl", () => {
|
|
|
247
250
|
// frameworks. Without this, the agent never reaches the browser regardless
|
|
248
251
|
// of the .dart classifier fix.
|
|
249
252
|
it("picks the Flutter playwright-framework service by name suffix", async () => {
|
|
250
|
-
|
|
253
|
+
readWorkspaceConfigRaw.mockResolvedValue({
|
|
251
254
|
services: [
|
|
252
255
|
{
|
|
253
256
|
serviceName: "birdle-frontend",
|
|
@@ -264,7 +267,7 @@ describe("pickFrontendBaseUrl", () => {
|
|
|
264
267
|
// allow-list. The chosen URL must come from H3 (first service with baseUrl).
|
|
265
268
|
// We add a second service to prove H3 picked the first, not H1 by accident.
|
|
266
269
|
it("falls back to first service when name+framework don't match H1 or H2", async () => {
|
|
267
|
-
|
|
270
|
+
readWorkspaceConfigRaw.mockResolvedValue({
|
|
268
271
|
services: [
|
|
269
272
|
{
|
|
270
273
|
serviceName: "birdle",
|
|
@@ -282,24 +285,205 @@ describe("pickFrontendBaseUrl", () => {
|
|
|
282
285
|
});
|
|
283
286
|
expect(await pickFrontendBaseUrl("/repo")).toBe("http://localhost:8080");
|
|
284
287
|
});
|
|
288
|
+
// SKYR-3812: Environment variable override tests
|
|
289
|
+
describe("env var overrides (SKYR-3812)", () => {
|
|
290
|
+
let originalGlobal;
|
|
291
|
+
let originalPerSvc = {};
|
|
292
|
+
beforeEach(() => {
|
|
293
|
+
// Snapshot env vars this suite may touch so we can restore them exactly.
|
|
294
|
+
originalGlobal = process.env.SKYRAMP_TEST_BASE_URL;
|
|
295
|
+
originalPerSvc = {};
|
|
296
|
+
for (const key of Object.keys(process.env)) {
|
|
297
|
+
if (key.startsWith("SKYRAMP_TEST_SERVICE_URL_")) {
|
|
298
|
+
originalPerSvc[key] = process.env[key];
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
afterEach(() => {
|
|
303
|
+
// Restore the global override.
|
|
304
|
+
if (originalGlobal === undefined) {
|
|
305
|
+
delete process.env.SKYRAMP_TEST_BASE_URL;
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
process.env.SKYRAMP_TEST_BASE_URL = originalGlobal;
|
|
309
|
+
}
|
|
310
|
+
// Restore per-service vars: reset the union of keys that existed before
|
|
311
|
+
// the test and keys the test set, so neither pre-existing values nor
|
|
312
|
+
// test-set values leak across tests or out of the suite.
|
|
313
|
+
const keys = new Set([
|
|
314
|
+
...Object.keys(originalPerSvc),
|
|
315
|
+
...Object.keys(process.env).filter((k) => k.startsWith("SKYRAMP_TEST_SERVICE_URL_")),
|
|
316
|
+
]);
|
|
317
|
+
for (const key of keys) {
|
|
318
|
+
if (originalPerSvc[key] === undefined) {
|
|
319
|
+
delete process.env[key];
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
process.env[key] = originalPerSvc[key];
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
readWorkspaceConfigRaw.mockReset();
|
|
326
|
+
});
|
|
327
|
+
it("SKYRAMP_TEST_BASE_URL (global) overrides workspace.yml baseUrl", async () => {
|
|
328
|
+
readWorkspaceConfigRaw.mockResolvedValue({
|
|
329
|
+
services: [
|
|
330
|
+
{
|
|
331
|
+
serviceName: "frontend",
|
|
332
|
+
api: { baseUrl: "http://localhost:3000" },
|
|
333
|
+
},
|
|
334
|
+
],
|
|
335
|
+
});
|
|
336
|
+
process.env.SKYRAMP_TEST_BASE_URL = "https://preview.example.com";
|
|
337
|
+
expect(await pickFrontendBaseUrl("/repo")).toBe("https://preview.example.com");
|
|
338
|
+
});
|
|
339
|
+
it("per-service SKYRAMP_TEST_SERVICE_URL_<NAME> overrides for the picked frontend service", async () => {
|
|
340
|
+
readWorkspaceConfigRaw.mockResolvedValue({
|
|
341
|
+
services: [
|
|
342
|
+
{ serviceName: "backend", api: { baseUrl: "http://localhost:8000" } },
|
|
343
|
+
{
|
|
344
|
+
serviceName: "demoshop-frontend",
|
|
345
|
+
api: { baseUrl: "http://localhost:5173" },
|
|
346
|
+
},
|
|
347
|
+
],
|
|
348
|
+
});
|
|
349
|
+
process.env.SKYRAMP_TEST_SERVICE_URL_DEMOSHOP_FRONTEND =
|
|
350
|
+
"https://frontend-preview.example.com";
|
|
351
|
+
expect(await pickFrontendBaseUrl("/repo")).toBe("https://frontend-preview.example.com");
|
|
352
|
+
});
|
|
353
|
+
it("sanitizes service name: hyphens, dots, colons, slashes to underscores", async () => {
|
|
354
|
+
readWorkspaceConfigRaw.mockResolvedValue({
|
|
355
|
+
services: [
|
|
356
|
+
{
|
|
357
|
+
serviceName: "supabase-nextjs",
|
|
358
|
+
api: { baseUrl: "http://localhost:3000" },
|
|
359
|
+
},
|
|
360
|
+
],
|
|
361
|
+
});
|
|
362
|
+
process.env.SKYRAMP_TEST_SERVICE_URL_SUPABASE_NEXTJS =
|
|
363
|
+
"https://supabase-preview.example.com";
|
|
364
|
+
expect(await pickFrontendBaseUrl("/repo")).toBe("https://supabase-preview.example.com");
|
|
365
|
+
});
|
|
366
|
+
it("sanitizes service name with dot: my.app -> MY_APP", async () => {
|
|
367
|
+
readWorkspaceConfigRaw.mockResolvedValue({
|
|
368
|
+
services: [
|
|
369
|
+
{ serviceName: "my.app", api: { baseUrl: "http://localhost:3000" } },
|
|
370
|
+
],
|
|
371
|
+
});
|
|
372
|
+
process.env.SKYRAMP_TEST_SERVICE_URL_MY_APP =
|
|
373
|
+
"https://myapp-preview.example.com";
|
|
374
|
+
expect(await pickFrontendBaseUrl("/repo")).toBe("https://myapp-preview.example.com");
|
|
375
|
+
});
|
|
376
|
+
it("sanitizes service name with colon: proto://app -> PROTO___APP", async () => {
|
|
377
|
+
readWorkspaceConfigRaw.mockResolvedValue({
|
|
378
|
+
services: [
|
|
379
|
+
{
|
|
380
|
+
serviceName: "proto://app",
|
|
381
|
+
api: { baseUrl: "http://localhost:3000" },
|
|
382
|
+
},
|
|
383
|
+
],
|
|
384
|
+
});
|
|
385
|
+
process.env.SKYRAMP_TEST_SERVICE_URL_PROTO___APP =
|
|
386
|
+
"https://proto-preview.example.com";
|
|
387
|
+
expect(await pickFrontendBaseUrl("/repo")).toBe("https://proto-preview.example.com");
|
|
388
|
+
});
|
|
389
|
+
it("per-service takes precedence over global when both are set", async () => {
|
|
390
|
+
readWorkspaceConfigRaw.mockResolvedValue({
|
|
391
|
+
services: [
|
|
392
|
+
{
|
|
393
|
+
serviceName: "frontend",
|
|
394
|
+
api: { baseUrl: "http://localhost:3000" },
|
|
395
|
+
},
|
|
396
|
+
],
|
|
397
|
+
});
|
|
398
|
+
process.env.SKYRAMP_TEST_BASE_URL = "https://global-preview.example.com";
|
|
399
|
+
process.env.SKYRAMP_TEST_SERVICE_URL_FRONTEND =
|
|
400
|
+
"https://per-service-preview.example.com";
|
|
401
|
+
expect(await pickFrontendBaseUrl("/repo")).toBe("https://per-service-preview.example.com");
|
|
402
|
+
});
|
|
403
|
+
it("empty/whitespace global env var is ignored, falls back to workspace.yml", async () => {
|
|
404
|
+
readWorkspaceConfigRaw.mockResolvedValue({
|
|
405
|
+
services: [
|
|
406
|
+
{
|
|
407
|
+
serviceName: "frontend",
|
|
408
|
+
api: { baseUrl: "http://localhost:3000" },
|
|
409
|
+
},
|
|
410
|
+
],
|
|
411
|
+
});
|
|
412
|
+
process.env.SKYRAMP_TEST_BASE_URL = " ";
|
|
413
|
+
expect(await pickFrontendBaseUrl("/repo")).toBe("http://localhost:3000");
|
|
414
|
+
});
|
|
415
|
+
it("empty/whitespace per-service env var is ignored, falls back to global", async () => {
|
|
416
|
+
readWorkspaceConfigRaw.mockResolvedValue({
|
|
417
|
+
services: [
|
|
418
|
+
{
|
|
419
|
+
serviceName: "frontend",
|
|
420
|
+
api: { baseUrl: "http://localhost:3000" },
|
|
421
|
+
},
|
|
422
|
+
],
|
|
423
|
+
});
|
|
424
|
+
process.env.SKYRAMP_TEST_BASE_URL = "https://global-preview.example.com";
|
|
425
|
+
process.env.SKYRAMP_TEST_SERVICE_URL_FRONTEND = " ";
|
|
426
|
+
expect(await pickFrontendBaseUrl("/repo")).toBe("https://global-preview.example.com");
|
|
427
|
+
});
|
|
428
|
+
it("global override returned even with no matching service", async () => {
|
|
429
|
+
readWorkspaceConfigRaw.mockResolvedValue({
|
|
430
|
+
services: [
|
|
431
|
+
{
|
|
432
|
+
serviceName: "backend",
|
|
433
|
+
language: "python",
|
|
434
|
+
api: { baseUrl: "http://localhost:8000" },
|
|
435
|
+
},
|
|
436
|
+
],
|
|
437
|
+
});
|
|
438
|
+
process.env.SKYRAMP_TEST_BASE_URL = "https://global-preview.example.com";
|
|
439
|
+
// pickFrontendBaseUrl will select the first service with baseUrl (H3), but the override takes precedence
|
|
440
|
+
expect(await pickFrontendBaseUrl("/repo")).toBe("https://global-preview.example.com");
|
|
441
|
+
});
|
|
442
|
+
it("existing no-override behavior still returns workspace.yml value", async () => {
|
|
443
|
+
readWorkspaceConfigRaw.mockResolvedValue({
|
|
444
|
+
services: [
|
|
445
|
+
{
|
|
446
|
+
serviceName: "frontend",
|
|
447
|
+
api: { baseUrl: "http://localhost:3000" },
|
|
448
|
+
},
|
|
449
|
+
],
|
|
450
|
+
});
|
|
451
|
+
// No env vars set
|
|
452
|
+
expect(await pickFrontendBaseUrl("/repo")).toBe("http://localhost:3000");
|
|
453
|
+
});
|
|
454
|
+
it("does not throw when picked service has a non-string serviceName (unvalidated YAML)", async () => {
|
|
455
|
+
// readWorkspaceConfigRaw can fall back to an unvalidated YAML parse where
|
|
456
|
+
// serviceName is e.g. a number. The per-service guard must skip it rather
|
|
457
|
+
// than calling sanitizeServiceName on a non-string and throwing.
|
|
458
|
+
readWorkspaceConfigRaw.mockResolvedValue({
|
|
459
|
+
services: [
|
|
460
|
+
{
|
|
461
|
+
serviceName: 123,
|
|
462
|
+
language: "typescript",
|
|
463
|
+
framework: "next",
|
|
464
|
+
api: { baseUrl: "http://localhost:3000" },
|
|
465
|
+
},
|
|
466
|
+
],
|
|
467
|
+
});
|
|
468
|
+
process.env.SKYRAMP_TEST_BASE_URL = "https://global-preview.example.com";
|
|
469
|
+
// Falls through the per-service branch to the global override.
|
|
470
|
+
expect(await pickFrontendBaseUrl("/repo")).toBe("https://global-preview.example.com");
|
|
471
|
+
});
|
|
472
|
+
});
|
|
285
473
|
});
|
|
286
474
|
// ---------------------------------------------------------------------------
|
|
287
475
|
// findRootFallbackPage
|
|
288
476
|
// ---------------------------------------------------------------------------
|
|
289
477
|
describe("findRootFallbackPage", () => {
|
|
290
|
-
let readSpy;
|
|
291
|
-
beforeEach(() => {
|
|
292
|
-
readSpy = jest.spyOn(workspaceAuth, "readWorkspaceConfigRaw");
|
|
293
|
-
});
|
|
294
478
|
afterEach(() => {
|
|
295
|
-
|
|
479
|
+
readWorkspaceConfigRaw.mockReset();
|
|
296
480
|
});
|
|
297
481
|
it("returns null when no frontend baseUrl resolvable", async () => {
|
|
298
|
-
|
|
482
|
+
readWorkspaceConfigRaw.mockResolvedValue(null);
|
|
299
483
|
expect(await findRootFallbackPage("/repo")).toBeNull();
|
|
300
484
|
});
|
|
301
485
|
it("returns root-fallback page when frontend baseUrl resolves", async () => {
|
|
302
|
-
|
|
486
|
+
readWorkspaceConfigRaw.mockResolvedValue({
|
|
303
487
|
services: [{ serviceName: "frontend", api: { baseUrl: "http://localhost:3000" } }],
|
|
304
488
|
});
|
|
305
489
|
const page = await findRootFallbackPage("/repo");
|
|
@@ -314,22 +498,18 @@ describe("findRootFallbackPage", () => {
|
|
|
314
498
|
// enumerateCandidateUiPages
|
|
315
499
|
// ---------------------------------------------------------------------------
|
|
316
500
|
describe("enumerateCandidateUiPages", () => {
|
|
317
|
-
let readSpy;
|
|
318
|
-
beforeEach(() => {
|
|
319
|
-
readSpy = jest.spyOn(workspaceAuth, "readWorkspaceConfigRaw");
|
|
320
|
-
});
|
|
321
501
|
afterEach(() => {
|
|
322
|
-
|
|
502
|
+
readWorkspaceConfigRaw.mockReset();
|
|
323
503
|
});
|
|
324
504
|
it("returns empty for empty file list", async () => {
|
|
325
505
|
expect(await enumerateCandidateUiPages("/repo", [])).toEqual([]);
|
|
326
506
|
});
|
|
327
507
|
it("returns empty when workspace has no resolvable baseUrl", async () => {
|
|
328
|
-
|
|
508
|
+
readWorkspaceConfigRaw.mockResolvedValue(null);
|
|
329
509
|
expect(await enumerateCandidateUiPages("/repo", ["app/orders/page.tsx"])).toEqual([]);
|
|
330
510
|
});
|
|
331
511
|
it("uses strategy 1 (framework-route-grep) when route files match", async () => {
|
|
332
|
-
|
|
512
|
+
readWorkspaceConfigRaw.mockResolvedValue({
|
|
333
513
|
services: [{ serviceName: "frontend", api: { baseUrl: "http://localhost:3000" } }],
|
|
334
514
|
});
|
|
335
515
|
const repo = makeTmpRepo(["next.config.js"]);
|
|
@@ -345,7 +525,7 @@ describe("enumerateCandidateUiPages", () => {
|
|
|
345
525
|
fs.rmSync(repo, { recursive: true, force: true });
|
|
346
526
|
});
|
|
347
527
|
it("falls back to strategy 3 (root-fallback) when no route files match", async () => {
|
|
348
|
-
|
|
528
|
+
readWorkspaceConfigRaw.mockResolvedValue({
|
|
349
529
|
services: [{ serviceName: "frontend", api: { baseUrl: "http://localhost:3000" } }],
|
|
350
530
|
});
|
|
351
531
|
const pages = await enumerateCandidateUiPages("/repo", [
|
|
@@ -359,7 +539,7 @@ describe("enumerateCandidateUiPages", () => {
|
|
|
359
539
|
});
|
|
360
540
|
});
|
|
361
541
|
it("emits multiple route pages when several match strategy 1", async () => {
|
|
362
|
-
|
|
542
|
+
readWorkspaceConfigRaw.mockResolvedValue({
|
|
363
543
|
services: [{ serviceName: "frontend", api: { baseUrl: "http://localhost:3000" } }],
|
|
364
544
|
});
|
|
365
545
|
// Need a real repoPath with framework config so detectsFilesystemRouting passes.
|
|
@@ -377,7 +557,7 @@ describe("enumerateCandidateUiPages", () => {
|
|
|
377
557
|
fs.rmSync(repo, { recursive: true, force: true });
|
|
378
558
|
});
|
|
379
559
|
it("uses strategy 2 (source-grounded-routes) when strategy 1 doesn't match but routes are declared in source", async () => {
|
|
380
|
-
|
|
560
|
+
readWorkspaceConfigRaw.mockResolvedValue({
|
|
381
561
|
services: [{ serviceName: "frontend", api: { baseUrl: "http://localhost:5173" } }],
|
|
382
562
|
});
|
|
383
563
|
// Repo with no framework config (so strategy 1 won't fire) but with a
|
|
@@ -401,7 +581,7 @@ export default function App() { return <Routes><Route path="/cart" element={<Car
|
|
|
401
581
|
fs.rmSync(repo, { recursive: true, force: true });
|
|
402
582
|
});
|
|
403
583
|
it("falls back to root when repo lacks a framework config (no false positives)", async () => {
|
|
404
|
-
|
|
584
|
+
readWorkspaceConfigRaw.mockResolvedValue({
|
|
405
585
|
services: [{ serviceName: "frontend", api: { baseUrl: "http://localhost:3000" } }],
|
|
406
586
|
});
|
|
407
587
|
// Repo has files matching the Pages Router pattern but no next.config —
|
|
@@ -423,7 +603,7 @@ export default function App() { return <Routes><Route path="/cart" element={<Car
|
|
|
423
603
|
// (no framework config; TS extractor doesn't see .dart; no GoRoute calls).
|
|
424
604
|
// Result: root-fallback with the workspace baseUrl.
|
|
425
605
|
it("returns root-fallback for a Flutter repo with no GoRoute declarations", async () => {
|
|
426
|
-
|
|
606
|
+
readWorkspaceConfigRaw.mockResolvedValue({
|
|
427
607
|
services: [
|
|
428
608
|
{
|
|
429
609
|
serviceName: "birdle-frontend",
|
|
@@ -449,7 +629,7 @@ export default function App() { return <Routes><Route path="/cart" element={<Car
|
|
|
449
629
|
// surfaces every absolute route as a candidate URL via the all-routes
|
|
450
630
|
// fallback. Diff-match narrowing is exercised by the next test.
|
|
451
631
|
it("returns dart-go-router URLs for a Flutter repo with GoRoute declarations", async () => {
|
|
452
|
-
|
|
632
|
+
readWorkspaceConfigRaw.mockResolvedValue({
|
|
453
633
|
services: [
|
|
454
634
|
{
|
|
455
635
|
serviceName: "books-frontend",
|
|
@@ -482,7 +662,7 @@ final router = GoRouter(routes: [
|
|
|
482
662
|
// whose screen file is in frontendFiles. The fixture has 3 routes; only
|
|
483
663
|
// /authors's screen file (lib/src/screens/authors.dart) is in the diff.
|
|
484
664
|
it("narrows GoRouter candidates to routes whose screen file is in the diff", async () => {
|
|
485
|
-
|
|
665
|
+
readWorkspaceConfigRaw.mockResolvedValue({
|
|
486
666
|
services: [
|
|
487
667
|
{
|
|
488
668
|
serviceName: "books-frontend",
|
|
@@ -519,7 +699,7 @@ final router = GoRouter(routes: [
|
|
|
519
699
|
// route is redirect-only, so we should fall back to the non-redirect-only
|
|
520
700
|
// routes.
|
|
521
701
|
it("filters out redirect-only GoRoutes", async () => {
|
|
522
|
-
|
|
702
|
+
readWorkspaceConfigRaw.mockResolvedValue({
|
|
523
703
|
services: [
|
|
524
704
|
{
|
|
525
705
|
serviceName: "frontend",
|
|
@@ -547,7 +727,7 @@ final router = GoRouter(routes: [
|
|
|
547
727
|
// file edited but our import-resolution heuristic missed the screen), surface
|
|
548
728
|
// ALL renderable routes rather than collapsing to empty.
|
|
549
729
|
it("falls back to all renderable routes when no screen file matches the diff", async () => {
|
|
550
|
-
|
|
730
|
+
readWorkspaceConfigRaw.mockResolvedValue({
|
|
551
731
|
services: [
|
|
552
732
|
{
|
|
553
733
|
serviceName: "frontend",
|
|
@@ -580,7 +760,7 @@ final router = GoRouter(routes: [
|
|
|
580
760
|
// (e.g. shelf_router uses similar names in tests) must NOT trigger Strategy
|
|
581
761
|
// 2.5 — pubspec.yaml lacks `sdk: flutter`, hasFlutterSdkDep returns false.
|
|
582
762
|
it("does not invoke Dart extractor when pubspec.yaml lacks sdk: flutter", async () => {
|
|
583
|
-
|
|
763
|
+
readWorkspaceConfigRaw.mockResolvedValue({
|
|
584
764
|
services: [
|
|
585
765
|
{
|
|
586
766
|
serviceName: "server-frontend",
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
export declare function readDiffFile(diffFilePath: string | undefined): string | undefined;
|
|
3
|
+
export declare function toolError(message: string): CallToolResult;
|
|
4
|
+
/**
|
|
5
|
+
* Happy-path counterpart to `toolError`. Wraps a single text payload in the
|
|
6
|
+
* MCP tool-result shape so handlers don't have to construct
|
|
7
|
+
* `{ content: [{ type: "text", text }] }` boilerplate themselves.
|
|
8
|
+
*/
|
|
9
|
+
export declare function toolText(text: string): CallToolResult;
|
|
10
|
+
export declare const OUTPUT_DIR_FIELD_NAME = "outputDir";
|
|
11
|
+
export declare const TRACE_OUTPUT_FILE_FIELD_NAME = "traceOutputFile";
|
|
12
|
+
export declare const PLAYWRIGHT_OUTPUT_FILE_FIELD_NAME = "playwrightOutput";
|
|
13
|
+
export declare const PATH_PARAMS_FIELD_NAME = "pathParams";
|
|
14
|
+
export declare const QUERY_PARAMS_FIELD_NAME = "queryParams";
|
|
15
|
+
export declare const FORM_PARAMS_FIELD_NAME = "formParams";
|
|
16
|
+
export declare function IsValidKeyValueList(input: string): boolean;
|
|
17
|
+
export declare const SKYRAMP_UTILS_HEADER = "Generated by Skyramp";
|
|
18
|
+
export declare function getPathParameterValidationError(requiredPathParams: string): string;
|
|
19
|
+
export declare function validatePath(dir: string, fieldName: string): CallToolResult | null;
|
|
20
|
+
export declare function validateParams(params: string, fieldName: string): CallToolResult | null;
|
|
21
|
+
export declare function validateRequestData(value: string): string | null;
|
|
22
|
+
/**
|
|
23
|
+
* Generates a Skyramp header for generated files
|
|
24
|
+
* @param language The programming language for the file
|
|
25
|
+
* @returns The formatted header string
|
|
26
|
+
*/
|
|
27
|
+
export declare function generateSkyrampHeader(language: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* Returns prompt phrasing for where the agent should find service details.
|
|
30
|
+
*
|
|
31
|
+
* - Testbot mode: agent receives a `<services>` XML block — no workspace.yml exists.
|
|
32
|
+
* - Normal MCP mode: agent reads `.skyramp/workspace.yml`.
|
|
33
|
+
*/
|
|
34
|
+
export declare function resolveServiceDetailsRef(): {
|
|
35
|
+
testDirRef: string;
|
|
36
|
+
frontendTestDirRef: string;
|
|
37
|
+
baseUrlRef: string;
|
|
38
|
+
authSourceRef: string;
|
|
39
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/build/utils/versions.js
CHANGED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads `.skyramp/workspace.yml`.
|
|
3
|
+
*
|
|
4
|
+
* Tries the library's Zod-validated `WorkspaceConfigManager.read()` first so
|
|
5
|
+
* well-formed workspaces produce a normalized object. When validation fails
|
|
6
|
+
* (e.g. a hand-rolled fixture that omits `metadata.createdAt` /`updatedAt`,
|
|
7
|
+
* or any new field added by a future schema), falls back to a raw YAML parse.
|
|
8
|
+
*
|
|
9
|
+
* Callers are expected to read only the fields they need (`services`,
|
|
10
|
+
* `services[].api.baseUrl`, etc.) — they should not depend on the strict
|
|
11
|
+
* shape across both paths.
|
|
12
|
+
*
|
|
13
|
+
* Returns null if the file does not exist OR cannot be parsed as YAML at all.
|
|
14
|
+
*/
|
|
15
|
+
export declare function readWorkspaceConfigRaw(workspacePath: string): Promise<any | null>;
|
|
16
|
+
export interface WorkspaceServiceCandidate {
|
|
17
|
+
serviceName: string;
|
|
18
|
+
baseUrl: string;
|
|
19
|
+
}
|
|
20
|
+
export interface WorkspaceBaseUrlResult {
|
|
21
|
+
/** Resolved scheme+host+port when exactly one service matched. */
|
|
22
|
+
baseUrl: string | undefined;
|
|
23
|
+
/** Docker network from the matched service's runtimeDetails. */
|
|
24
|
+
dockerNetwork: string | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* When baseUrl is undefined and candidates is non-empty, multiple services
|
|
27
|
+
* matched the test file but could not be disambiguated. Surface these to the
|
|
28
|
+
* LLM so it can ask the user or re-invoke with SKYRAMP_TEST_BASE_URL set.
|
|
29
|
+
*/
|
|
30
|
+
candidates: WorkspaceServiceCandidate[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Returns scheme+host+port for the service that owns the given test file.
|
|
34
|
+
*
|
|
35
|
+
* Matching strategy (in order):
|
|
36
|
+
* 1. Filter services whose `testDirectory` is a path prefix of `testFile`.
|
|
37
|
+
* 2. If multiple services match (shared testDirectory), narrow by `language`.
|
|
38
|
+
* 3. If still ambiguous, return all candidates so the LLM can resolve it —
|
|
39
|
+
* never guess when multiple services are plausible owners.
|
|
40
|
+
*
|
|
41
|
+
* e.g. repositoryPath=/repo, testDirectory=backend/tests, testFile=/repo/backend/tests/test_api.py
|
|
42
|
+
* → { baseUrl: "http://localhost:8000", candidates: [] }
|
|
43
|
+
*/
|
|
44
|
+
export declare function getWorkspaceBaseUrl(repositoryPath: string, testFile?: string, language?: string): Promise<WorkspaceBaseUrlResult>;
|
|
45
|
+
/**
|
|
46
|
+
* Known auth-related HTTP header names (lowercase).
|
|
47
|
+
* Single source of truth used for both:
|
|
48
|
+
* - trace redaction (trace-parser.ts — headers to scrub from recorded traces)
|
|
49
|
+
* - auth extraction (TestGenerationService — headers to pull out as auth credentials)
|
|
50
|
+
*/
|
|
51
|
+
export declare const KNOWN_AUTH_HEADERS: Set<string>;
|
|
52
|
+
/**
|
|
53
|
+
* Cross-framework auth middleware signal patterns used by both the testbot and
|
|
54
|
+
* recommendation prompts to detect auth when workspace config says authType: none.
|
|
55
|
+
*
|
|
56
|
+
* Format: plain strings (e.g. "HTTPBearer", "@PreAuthorize")
|
|
57
|
+
*
|
|
58
|
+
* This is the single source of truth — prompts must import and use this list
|
|
59
|
+
* instead of hardcoding pattern names inline.
|
|
60
|
+
*/
|
|
61
|
+
export declare const AUTH_MIDDLEWARE_PATTERNS: string[];
|
|
62
|
+
/** Rendered as a comma-separated string for use in prompts. */
|
|
63
|
+
export declare const AUTH_MIDDLEWARE_PATTERNS_STR: string;
|
|
64
|
+
export declare enum WorkspaceAuthType {
|
|
65
|
+
Bearer = "bearer",
|
|
66
|
+
Basic = "basic",
|
|
67
|
+
OAuth = "oauth",
|
|
68
|
+
/** Generic custom-header auth (X-API-Key, X-Admin-Key, etc.) */
|
|
69
|
+
ApiKey = "apiKey",
|
|
70
|
+
/** Session cookie auth — header is typically Cookie: <name>=<value> */
|
|
71
|
+
Cookie = "cookie",
|
|
72
|
+
/** Alias for Cookie; some workspace configs write authType: session */
|
|
73
|
+
Session = "session",
|
|
74
|
+
/** Django REST Framework / token-based: Authorization: Token <value> */
|
|
75
|
+
Token = "token",
|
|
76
|
+
None = "none"
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* All valid authType values as a tuple for use in zod enums.
|
|
80
|
+
* Single source of truth — tools and prompts should use this instead of hardcoding.
|
|
81
|
+
*/
|
|
82
|
+
export declare const ALL_AUTH_TYPES: [string, ...string[]];
|
|
83
|
+
/**
|
|
84
|
+
* Formatted list of authType values for use in prompt text.
|
|
85
|
+
* Example: "`bearer`, `basic`, `oauth`, `token`, `cookie`, `session`, `apiKey`, or `none`"
|
|
86
|
+
*/
|
|
87
|
+
export declare const AUTH_TYPES_PROMPT_LIST: string;
|
|
88
|
+
export declare function parseWorkspaceAuthType(value: string | undefined): WorkspaceAuthType | undefined;
|
|
89
|
+
/**
|
|
90
|
+
* Returns the conventional HTTP header name for an auth type when authHeader
|
|
91
|
+
* is not explicitly configured in workspace.yml.
|
|
92
|
+
*
|
|
93
|
+
* This is the single source of truth for the authType → authHeader mapping.
|
|
94
|
+
* All prompt builders and tools must use this instead of hardcoding header names.
|
|
95
|
+
*
|
|
96
|
+
* - cookie / session → "Cookie" (always goes in Cookie header)
|
|
97
|
+
* - apiKey → "" (custom header; caller must read source)
|
|
98
|
+
* - none → ""
|
|
99
|
+
* - "" / undefined → "" (do not infer a header when authType is missing)
|
|
100
|
+
* - bearer / basic / oauth / token → "Authorization"
|
|
101
|
+
*/
|
|
102
|
+
export declare function getDefaultAuthHeader(authType: WorkspaceAuthType | "" | undefined): string;
|
|
103
|
+
/**
|
|
104
|
+
* Returns true when the given header NAME string is the Authorization header.
|
|
105
|
+
*
|
|
106
|
+
* Use this instead of sprinkling /^authorization$/i regexes across prompts and tools.
|
|
107
|
+
*/
|
|
108
|
+
export declare function isAuthorizationHeaderName(headerName: string): boolean;
|
|
109
|
+
/**
|
|
110
|
+
* Maps a WorkspaceAuthType to the canonical HTTP Authorization scheme prefix
|
|
111
|
+
* (the word that appears before the token in "Authorization: <scheme> <token>").
|
|
112
|
+
*
|
|
113
|
+
* If `explicitScheme` is provided (from workspace.yml api.authScheme), it takes
|
|
114
|
+
* precedence over the inferred scheme. This allows custom schemes like "Ghost",
|
|
115
|
+
* "Hasura", or any internal API without hardcoding them in the codebase.
|
|
116
|
+
*
|
|
117
|
+
* Returns undefined for auth types that do NOT use the Authorization header
|
|
118
|
+
* (Cookie, ApiKey, None) — callers should not pass authScheme in those cases.
|
|
119
|
+
*/
|
|
120
|
+
export declare function getAuthScheme(authType: WorkspaceAuthType | undefined, explicitScheme?: string): string | undefined;
|
|
121
|
+
export interface WorkspaceAuthConfig {
|
|
122
|
+
authHeader?: string;
|
|
123
|
+
authType?: WorkspaceAuthType;
|
|
124
|
+
/** Explicit Authorization scheme from workspace.yml (e.g., "Ghost", "Hasura", custom). */
|
|
125
|
+
authScheme?: string;
|
|
126
|
+
}
|
|
127
|
+
export declare function getWorkspaceAuthConfig(repositoryPath: string): Promise<WorkspaceAuthConfig>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
|
|
1
|
+
// @ts-nocheck - Jest ESM mock type inference issues
|
|
2
|
+
import { jest } from "@jest/globals";
|
|
2
3
|
const REPO = "/some/repo";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
});
|
|
4
|
+
const twoServiceConfig = {
|
|
5
|
+
services: [
|
|
6
|
+
{
|
|
7
|
+
serviceName: "backend",
|
|
8
|
+
language: "python",
|
|
9
|
+
testDirectory: "backend/tests",
|
|
10
|
+
api: { baseUrl: "http://localhost:8000" },
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
serviceName: "frontend",
|
|
14
|
+
language: "typescript",
|
|
15
|
+
testDirectory: "frontend/tests",
|
|
16
|
+
api: { baseUrl: "http://localhost:5173" },
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
};
|
|
20
|
+
jest.unstable_mockModule("../workspace/workspace.js", () => ({
|
|
21
|
+
WorkspaceConfigManager: jest.fn().mockImplementation(() => ({
|
|
22
|
+
exists: jest.fn().mockResolvedValue(true),
|
|
23
|
+
read: jest.fn().mockResolvedValue(twoServiceConfig),
|
|
24
|
+
})),
|
|
25
|
+
}));
|
|
26
|
+
const { getWorkspaceBaseUrl } = await import("./workspaceAuth.js");
|
|
27
27
|
describe("getWorkspaceBaseUrl", () => {
|
|
28
28
|
it("should return no match when testFile is not provided", async () => {
|
|
29
29
|
const result = await getWorkspaceBaseUrl(REPO);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./workspace.js";
|