@contractspec/bundle.workspace 1.52.0 → 1.53.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/dist/adapters/ai.d.mts +2 -2
- package/dist/adapters/ai.d.mts.map +1 -1
- package/dist/adapters/ai.mjs.map +1 -1
- package/dist/adapters/factory.d.mts +2 -2
- package/dist/adapters/factory.d.mts.map +1 -1
- package/dist/adapters/factory.mjs +3 -14
- package/dist/adapters/factory.mjs.map +1 -1
- package/dist/adapters/fs.bun.d.mts +11 -0
- package/dist/adapters/fs.bun.d.mts.map +1 -0
- package/dist/adapters/fs.bun.mjs +81 -0
- package/dist/adapters/fs.bun.mjs.map +1 -0
- package/dist/adapters/fs.d.mts +2 -8
- package/dist/adapters/fs.d.mts.map +1 -1
- package/dist/adapters/fs.mjs +4 -88
- package/dist/adapters/fs.mjs.map +1 -1
- package/dist/adapters/fs.node.d.mts +11 -0
- package/dist/adapters/fs.node.d.mts.map +1 -0
- package/dist/adapters/fs.node.mjs +84 -0
- package/dist/adapters/fs.node.mjs.map +1 -0
- package/dist/adapters/index.d.mts +3 -1
- package/dist/adapters/index.mjs +3 -1
- package/dist/ai/agents/index.d.mts +6 -0
- package/dist/ai/agents/orchestrator.d.mts +4 -4
- package/dist/ai/agents/orchestrator.d.mts.map +1 -1
- package/dist/ai/agents/orchestrator.mjs +5 -0
- package/dist/ai/agents/orchestrator.mjs.map +1 -1
- package/dist/ai/agents/simple-agent.d.mts +2 -2
- package/dist/ai/agents/simple-agent.d.mts.map +1 -1
- package/dist/ai/agents/simple-agent.mjs.map +1 -1
- package/dist/ai/agents/types.d.mts +4 -5
- package/dist/ai/agents/types.d.mts.map +1 -1
- package/dist/ai/agents/unified-adapter.mjs +70 -0
- package/dist/ai/agents/unified-adapter.mjs.map +1 -0
- package/dist/ai/index.d.mts +3 -3
- package/dist/ai/index.mjs +1 -2
- package/dist/ai/prompts/spec-creation.mjs +1 -1
- package/dist/ai/providers.d.mts +3 -18
- package/dist/ai/providers.d.mts.map +1 -1
- package/dist/ai/providers.mjs +2 -22
- package/dist/ai/providers.mjs.map +1 -1
- package/dist/index.d.mts +13 -8
- package/dist/index.mjs +11 -7
- package/dist/ports/ai.d.mts +7 -3
- package/dist/ports/ai.d.mts.map +1 -1
- package/dist/ports/index.d.mts +1 -1
- package/dist/registry.d.mts +15 -0
- package/dist/registry.d.mts.map +1 -0
- package/dist/registry.mjs +19 -0
- package/dist/registry.mjs.map +1 -0
- package/dist/services/action-drift/service.d.mts +11 -0
- package/dist/services/action-drift/service.d.mts.map +1 -0
- package/dist/services/action-drift/service.mjs +43 -0
- package/dist/services/action-drift/service.mjs.map +1 -0
- package/dist/services/action-drift/types.d.mts +23 -0
- package/dist/services/action-drift/types.d.mts.map +1 -0
- package/dist/services/action-pr/service.d.mts +17 -0
- package/dist/services/action-pr/service.d.mts.map +1 -0
- package/dist/services/action-pr/service.mjs +205 -0
- package/dist/services/action-pr/service.mjs.map +1 -0
- package/dist/services/action-pr/types.d.mts +67 -0
- package/dist/services/action-pr/types.d.mts.map +1 -0
- package/dist/services/build.d.mts +3 -2
- package/dist/services/build.d.mts.map +1 -1
- package/dist/services/build.mjs.map +1 -1
- package/dist/services/ci-check/checks/coverage.mjs +30 -41
- package/dist/services/ci-check/checks/coverage.mjs.map +1 -1
- package/dist/services/ci-check/checks/handlers.mjs +3 -3
- package/dist/services/ci-check/checks/handlers.mjs.map +1 -1
- package/dist/services/ci-check/checks/implementation.mjs +1 -1
- package/dist/services/ci-check/checks/implementation.mjs.map +1 -1
- package/dist/services/ci-check/checks/structure.mjs +5 -6
- package/dist/services/ci-check/checks/structure.mjs.map +1 -1
- package/dist/services/ci-check/checks/test-refs.mjs +19 -29
- package/dist/services/ci-check/checks/test-refs.mjs.map +1 -1
- package/dist/services/ci-check/checks/tests.mjs +3 -3
- package/dist/services/ci-check/checks/tests.mjs.map +1 -1
- package/dist/services/ci-check/ci-check-service.d.mts.map +1 -1
- package/dist/services/ci-check/ci-check-service.mjs +7 -12
- package/dist/services/ci-check/ci-check-service.mjs.map +1 -1
- package/dist/services/config.d.mts +3 -3
- package/dist/services/config.d.mts.map +1 -1
- package/dist/services/config.mjs +12 -34
- package/dist/services/config.mjs.map +1 -1
- package/dist/services/create/ai-generator.d.mts +3 -3
- package/dist/services/create/ai-generator.d.mts.map +1 -1
- package/dist/services/create/ai-generator.mjs +1 -1
- package/dist/services/create/ai-generator.mjs.map +1 -1
- package/dist/services/create/index.d.mts +4 -4
- package/dist/services/create/index.d.mts.map +1 -1
- package/dist/services/create/index.mjs.map +1 -1
- package/dist/services/docs/docs-service.mjs +16 -13
- package/dist/services/docs/docs-service.mjs.map +1 -1
- package/dist/services/extract.mjs +2 -10
- package/dist/services/extract.mjs.map +1 -1
- package/dist/services/implementation/discovery.d.mts.map +1 -1
- package/dist/services/implementation/discovery.mjs +6 -14
- package/dist/services/implementation/discovery.mjs.map +1 -1
- package/dist/services/implementation/index.d.mts +1 -1
- package/dist/services/implementation/index.mjs +1 -1
- package/dist/services/implementation/resolver/index.d.mts +8 -6
- package/dist/services/implementation/resolver/index.d.mts.map +1 -1
- package/dist/services/implementation/resolver/index.mjs +33 -31
- package/dist/services/implementation/resolver/index.mjs.map +1 -1
- package/dist/services/implementation/resolver/parsers.d.mts +1 -5
- package/dist/services/implementation/resolver/parsers.d.mts.map +1 -1
- package/dist/services/implementation/resolver/parsers.mjs +1 -19
- package/dist/services/implementation/resolver/parsers.mjs.map +1 -1
- package/dist/services/implementation/types.d.mts +3 -1
- package/dist/services/implementation/types.d.mts.map +1 -1
- package/dist/services/index.d.mts +5 -1
- package/dist/services/index.mjs +4 -2
- package/dist/services/list.d.mts +4 -3
- package/dist/services/list.d.mts.map +1 -1
- package/dist/services/list.mjs +4 -2
- package/dist/services/list.mjs.map +1 -1
- package/dist/services/sync.d.mts +2 -2
- package/dist/services/sync.d.mts.map +1 -1
- package/dist/services/sync.mjs.map +1 -1
- package/dist/services/test/test-generator-service.d.mts +1 -1
- package/dist/services/test/test-service.mjs +1 -1
- package/dist/services/validate/blueprint-validator.mjs +1 -1
- package/dist/services/validate/implementation-agent-validator.d.mts +2 -2
- package/dist/services/validate/implementation-agent-validator.d.mts.map +1 -1
- package/dist/services/validate/implementation-agent-validator.mjs.map +1 -1
- package/dist/services/validate/implementation-validator.d.mts +4 -3
- package/dist/services/validate/implementation-validator.d.mts.map +1 -1
- package/dist/services/validate/implementation-validator.mjs +4 -13
- package/dist/services/validate/implementation-validator.mjs.map +1 -1
- package/dist/services/validate/spec-validator.d.mts +1 -1
- package/dist/services/validate/spec-validator.d.mts.map +1 -1
- package/dist/services/validate/spec-validator.mjs +11 -7
- package/dist/services/validate/spec-validator.mjs.map +1 -1
- package/dist/services/validate/tenant-validator.mjs +1 -1
- package/dist/services/watch.d.mts +2 -2
- package/dist/services/watch.d.mts.map +1 -1
- package/dist/services/watch.mjs.map +1 -1
- package/dist/utils/filter.d.mts +10 -1
- package/dist/utils/filter.d.mts.map +1 -1
- package/dist/utils/filter.mjs +28 -1
- package/dist/utils/filter.mjs.map +1 -1
- package/dist/utils/index.d.mts +2 -2
- package/dist/utils/index.mjs +2 -1
- package/package.json +11 -9
- package/dist/ai/client.d.mts +0 -97
- package/dist/ai/client.d.mts.map +0 -1
- package/dist/ai/client.mjs +0 -189
- package/dist/ai/client.mjs.map +0 -1
- package/dist/types/config.d.mts +0 -34
- package/dist/types/config.d.mts.map +0 -1
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ContractVerificationStatus, ReportData } from "../action-pr/types.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/services/action-drift/types.d.ts
|
|
4
|
+
interface DriftActionOptions {
|
|
5
|
+
workingDirectory: string;
|
|
6
|
+
generateCommand: string;
|
|
7
|
+
onDrift?: 'fail' | 'issue' | 'pr';
|
|
8
|
+
driftPathsAllowlist?: string;
|
|
9
|
+
}
|
|
10
|
+
interface DriftReportInputs {
|
|
11
|
+
contractsJson: ContractVerificationStatus[];
|
|
12
|
+
driftStatus: string;
|
|
13
|
+
driftFiles: string[];
|
|
14
|
+
reportDataExists: boolean;
|
|
15
|
+
existingReportData?: ReportData;
|
|
16
|
+
}
|
|
17
|
+
interface FinalizeDriftResult {
|
|
18
|
+
driftDetected: boolean;
|
|
19
|
+
shouldFail: boolean;
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
export { DriftActionOptions, DriftReportInputs, FinalizeDriftResult };
|
|
23
|
+
//# sourceMappingURL=types.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.mts","names":[],"sources":["../../../src/services/action-drift/types.ts"],"sourcesContent":[],"mappings":";;;UAKiB,kBAAA;;EAAA,eAAA,EAAA,MAAkB;EAOlB,OAAA,CAAA,EAAA,MAAA,GAAA,OAAiB,GAAA,IACjB;EAOA,mBAAA,CAAA,EAAmB,MAAA;;UARnB,iBAAA;iBACA;;;;uBAIM;;UAGN,mBAAA"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { FinalizeResult, ReportData, ReportInputs } from "./types.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/services/action-pr/service.d.ts
|
|
4
|
+
declare class PrActionService {
|
|
5
|
+
collectChanges(contractsDir: string, contractsGlob: string, changedFiles: string[]): string[];
|
|
6
|
+
detectDrift(statusLines: string[]): {
|
|
7
|
+
files: string[];
|
|
8
|
+
status: 'pass' | 'fail';
|
|
9
|
+
};
|
|
10
|
+
private deriveImpactStatus;
|
|
11
|
+
buildReportData(inputs: ReportInputs): ReportData;
|
|
12
|
+
finalizeResults(data: ReportData, failOn: string): FinalizeResult;
|
|
13
|
+
generateReportMarkdown(data: ReportData, fileContentProvider: (path: string) => string): string;
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
export { PrActionService };
|
|
17
|
+
//# sourceMappingURL=service.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.d.mts","names":[],"sources":["../../../src/services/action-pr/service.ts"],"sourcesContent":[],"mappings":";;;cAsBa,eAAA;;EAAA,WAAA,CAAA,WAAe,EAAA,MAAA,EAAA,CAAA,EAAA;IAwFF,KAAA,EAAA,MAAA,EAAA;IAAe,MAAA,EAAA,MAAA,GAAA,MAAA;EA+DjB,CAAA;EAA6B,QAAA,kBAAA;EAyC3C,eAAA,CAAA,MAAA,EAxGgB,YAwGhB,CAAA,EAxG+B,UAwG/B;EAAU,eAAA,CAAA,IAAA,EAzCI,UAyCJ,EAAA,MAAA,EAAA,MAAA,CAAA,EAzCiC,cAyCjC;+BAAV"}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
//#region src/services/action-pr/service.ts
|
|
2
|
+
const MAX_DETAIL_CHARS = 6e4;
|
|
3
|
+
const MAX_LIST_ITEMS = 20;
|
|
4
|
+
function formatAge(dateStr) {
|
|
5
|
+
if (!dateStr) return "—";
|
|
6
|
+
const date = new Date(dateStr);
|
|
7
|
+
const diffMs = (/* @__PURE__ */ new Date()).getTime() - date.getTime();
|
|
8
|
+
const diffDays = Math.floor(diffMs / (1e3 * 60 * 60 * 24));
|
|
9
|
+
if (diffDays < 1) return "today";
|
|
10
|
+
if (diffDays === 1) return "yesterday";
|
|
11
|
+
return `${diffDays} days ago`;
|
|
12
|
+
}
|
|
13
|
+
var PrActionService = class {
|
|
14
|
+
collectChanges(contractsDir, contractsGlob, changedFiles) {
|
|
15
|
+
if (contractsDir) {
|
|
16
|
+
const prefix = contractsDir.endsWith("/") ? contractsDir : `${contractsDir}/`;
|
|
17
|
+
return changedFiles.filter((path) => path.startsWith(prefix));
|
|
18
|
+
}
|
|
19
|
+
if (contractsGlob) {
|
|
20
|
+
const re = /* @__PURE__ */ new RegExp("^" + contractsGlob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".") + "$");
|
|
21
|
+
return changedFiles.filter((path) => re.test(path));
|
|
22
|
+
}
|
|
23
|
+
return changedFiles;
|
|
24
|
+
}
|
|
25
|
+
detectDrift(statusLines) {
|
|
26
|
+
const files = statusLines.map((line) => line.slice(3)).filter((path) => path && path.trim().length > 0).filter((path) => path !== ".contractspec-ci" && !path.startsWith(".contractspec-ci/"));
|
|
27
|
+
return {
|
|
28
|
+
files,
|
|
29
|
+
status: files.length > 0 ? "fail" : "pass"
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
deriveImpactStatus(impactJson) {
|
|
33
|
+
const summary = impactJson?.summary || {};
|
|
34
|
+
const breakingCount = summary.breaking || 0;
|
|
35
|
+
const nonBreakingCount = summary.nonBreaking || 0;
|
|
36
|
+
let totalCount = summary.total;
|
|
37
|
+
if (totalCount === void 0) totalCount = breakingCount + nonBreakingCount;
|
|
38
|
+
let breaking = impactJson?.breaking || false;
|
|
39
|
+
let impactStatus = "unknown";
|
|
40
|
+
if (impactJson) {
|
|
41
|
+
if (breaking || breakingCount > 0) {
|
|
42
|
+
impactStatus = "breaking";
|
|
43
|
+
breaking = true;
|
|
44
|
+
} else if (nonBreakingCount > 0) impactStatus = "non-breaking";
|
|
45
|
+
else if (totalCount === 0) impactStatus = "no-impact";
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
status: impactStatus,
|
|
49
|
+
breaking,
|
|
50
|
+
breakingCount,
|
|
51
|
+
nonBreakingCount,
|
|
52
|
+
total: totalCount || 0
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
buildReportData(inputs) {
|
|
56
|
+
const changesSummary = inputs.contractChanges.length === 0 ? "No contract files changed." : `${inputs.contractChanges.length} contract file(s) changed.`;
|
|
57
|
+
const impactDerived = this.deriveImpactStatus(inputs.impactJson);
|
|
58
|
+
const reportData = {
|
|
59
|
+
contracts: inputs.contractsJson,
|
|
60
|
+
whatChanged: {
|
|
61
|
+
summary: changesSummary,
|
|
62
|
+
detailsPath: ".contractspec-ci/product-view.md"
|
|
63
|
+
},
|
|
64
|
+
risk: {
|
|
65
|
+
status: impactDerived.status,
|
|
66
|
+
breaking: impactDerived.breakingCount,
|
|
67
|
+
nonBreaking: impactDerived.nonBreakingCount
|
|
68
|
+
},
|
|
69
|
+
validation: {
|
|
70
|
+
status: inputs.validationStatus,
|
|
71
|
+
outputPath: ".contractspec-ci/validation.txt"
|
|
72
|
+
},
|
|
73
|
+
drift: {
|
|
74
|
+
status: inputs.driftStatus,
|
|
75
|
+
files: inputs.driftFiles
|
|
76
|
+
},
|
|
77
|
+
nextSteps: []
|
|
78
|
+
};
|
|
79
|
+
if (reportData.validation?.status === "fail") reportData.nextSteps?.push("Fix validation errors and rerun the workflow.");
|
|
80
|
+
if (reportData.risk?.status === "breaking") reportData.nextSteps?.push("Review breaking changes and plan a migration.");
|
|
81
|
+
if (reportData.risk?.status === "unknown") reportData.nextSteps?.push("Re-run impact detection or check contractspec impact output.");
|
|
82
|
+
if (reportData.drift?.status === "fail") reportData.nextSteps?.push("Run the generate command locally and commit drift fixes.");
|
|
83
|
+
const failureCheck = this.finalizeResults(reportData, inputs.failOn);
|
|
84
|
+
if (failureCheck.shouldFail) reportData.nextSteps?.unshift(`CI failed: ${failureCheck.failReasons.join(", ")} (fail_on=${inputs.failOn}).`);
|
|
85
|
+
return reportData;
|
|
86
|
+
}
|
|
87
|
+
finalizeResults(data, failOn) {
|
|
88
|
+
const breakingDetected = data.risk?.status === "breaking" || (data.risk?.breaking || 0) > 0;
|
|
89
|
+
const driftFailed = data.drift?.status === "fail";
|
|
90
|
+
const validationFailed = data.validation?.status === "fail";
|
|
91
|
+
const impactUnknown = data.risk?.status === "unknown";
|
|
92
|
+
const reasons = [];
|
|
93
|
+
let shouldFail = false;
|
|
94
|
+
if (failOn !== "never") {
|
|
95
|
+
if (failOn === "breaking") {
|
|
96
|
+
if (breakingDetected) reasons.push("breaking changes detected");
|
|
97
|
+
if (impactUnknown) reasons.push("impact status unknown");
|
|
98
|
+
} else if (failOn === "drift") {
|
|
99
|
+
if (driftFailed) reasons.push("drift detected");
|
|
100
|
+
if (impactUnknown) reasons.push("impact status unknown");
|
|
101
|
+
} else if (failOn === "any") {
|
|
102
|
+
if (breakingDetected) reasons.push("breaking changes detected");
|
|
103
|
+
if (driftFailed) reasons.push("drift detected");
|
|
104
|
+
if (validationFailed) reasons.push("validation failed");
|
|
105
|
+
if (impactUnknown) reasons.push("impact status unknown");
|
|
106
|
+
} else reasons.push(`invalid fail_on value (${failOn})`);
|
|
107
|
+
if (reasons.length > 0) shouldFail = true;
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
driftDetected: driftFailed,
|
|
111
|
+
breakingChangeDetected: breakingDetected,
|
|
112
|
+
validationFailed,
|
|
113
|
+
shouldFail,
|
|
114
|
+
failReasons: reasons
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
generateReportMarkdown(data, fileContentProvider) {
|
|
118
|
+
const truncate = (value, maxChars = MAX_DETAIL_CHARS) => {
|
|
119
|
+
if (value.length <= maxChars) return value;
|
|
120
|
+
return `${value.slice(0, maxChars)}\n\n*(output truncated)*`;
|
|
121
|
+
};
|
|
122
|
+
const formatList = (items) => {
|
|
123
|
+
if (!items || items.length === 0) return "- None";
|
|
124
|
+
return items.slice(0, MAX_LIST_ITEMS).map((item) => `- ${item}`).join("\n");
|
|
125
|
+
};
|
|
126
|
+
const viewContent = truncate(data.whatChanged?.detailsPath ? fileContentProvider(data.whatChanged.detailsPath) : "");
|
|
127
|
+
const validationOutput = truncate(data.validation?.outputPath ? fileContentProvider(data.validation.outputPath) : "");
|
|
128
|
+
const driftFiles = data.drift?.files ?? [];
|
|
129
|
+
const lines = [];
|
|
130
|
+
lines.push("## ContractSpec Report");
|
|
131
|
+
lines.push("");
|
|
132
|
+
if (data.contracts && data.contracts.length > 0) {
|
|
133
|
+
lines.push("### Overall verification status");
|
|
134
|
+
lines.push("");
|
|
135
|
+
lines.push("| Contract / Endpoint / Event | Time since verified | Drift debt | Surfaces covered | Last verified commit |");
|
|
136
|
+
lines.push("| --- | --- | --- | --- | --- |");
|
|
137
|
+
for (const c of data.contracts) {
|
|
138
|
+
const sha = c.lastVerifiedSha ?? "—";
|
|
139
|
+
const time = formatAge(c.lastVerifiedDate);
|
|
140
|
+
const surfaces = c.surfaces.join(", ");
|
|
141
|
+
lines.push(`| ${c.name} | ${time} | ${c.driftMismatches} | ${surfaces} | ${sha} |`);
|
|
142
|
+
}
|
|
143
|
+
lines.push("");
|
|
144
|
+
}
|
|
145
|
+
lines.push("### 1) What changed");
|
|
146
|
+
if (data.whatChanged?.summary) lines.push(data.whatChanged.summary);
|
|
147
|
+
else lines.push("No contract changes detected.");
|
|
148
|
+
if (viewContent.trim().length > 0) {
|
|
149
|
+
lines.push("");
|
|
150
|
+
lines.push("<details>");
|
|
151
|
+
lines.push("<summary>Contract view (product)</summary>");
|
|
152
|
+
lines.push("");
|
|
153
|
+
lines.push(viewContent);
|
|
154
|
+
lines.push("");
|
|
155
|
+
lines.push("</details>");
|
|
156
|
+
}
|
|
157
|
+
lines.push("");
|
|
158
|
+
lines.push("### 2) Risk classification");
|
|
159
|
+
if (data.risk?.status) {
|
|
160
|
+
const riskSummary = [
|
|
161
|
+
`Status: ${data.risk.status}`,
|
|
162
|
+
data.risk.breaking !== void 0 ? `Breaking: ${data.risk.breaking}` : null,
|
|
163
|
+
data.risk.nonBreaking !== void 0 ? `Non-breaking: ${data.risk.nonBreaking}` : null
|
|
164
|
+
].filter(Boolean);
|
|
165
|
+
lines.push(riskSummary.join(" | "));
|
|
166
|
+
} else lines.push("Impact analysis unavailable.");
|
|
167
|
+
lines.push("");
|
|
168
|
+
lines.push("### 3) Validation results");
|
|
169
|
+
if (data.validation?.status) {
|
|
170
|
+
lines.push(`Status: ${data.validation.status}`);
|
|
171
|
+
if (validationOutput.trim().length > 0) {
|
|
172
|
+
lines.push("");
|
|
173
|
+
lines.push("<details>");
|
|
174
|
+
lines.push("<summary>Validation output</summary>");
|
|
175
|
+
lines.push("");
|
|
176
|
+
lines.push(validationOutput);
|
|
177
|
+
lines.push("");
|
|
178
|
+
lines.push("</details>");
|
|
179
|
+
}
|
|
180
|
+
} else lines.push("Validation step not run.");
|
|
181
|
+
lines.push("");
|
|
182
|
+
lines.push("### 4) Drift results");
|
|
183
|
+
if (data.drift?.status) {
|
|
184
|
+
lines.push(`Status: ${data.drift.status}`);
|
|
185
|
+
if (driftFiles.length > 0) {
|
|
186
|
+
lines.push("");
|
|
187
|
+
lines.push("<details>");
|
|
188
|
+
lines.push("<summary>Drifted files</summary>");
|
|
189
|
+
lines.push("");
|
|
190
|
+
lines.push(formatList(driftFiles));
|
|
191
|
+
lines.push("");
|
|
192
|
+
lines.push("</details>");
|
|
193
|
+
}
|
|
194
|
+
} else lines.push("Drift check not run.");
|
|
195
|
+
lines.push("");
|
|
196
|
+
lines.push("### 5) Next steps");
|
|
197
|
+
if (data.nextSteps && data.nextSteps.length > 0) lines.push(formatList(data.nextSteps));
|
|
198
|
+
else lines.push("- No action required.");
|
|
199
|
+
return `${lines.join("\n")}\n`;
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
//#endregion
|
|
204
|
+
export { PrActionService };
|
|
205
|
+
//# sourceMappingURL=service.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.mjs","names":[],"sources":["../../../src/services/action-pr/service.ts"],"sourcesContent":["import type {\n FinalizeResult,\n ImpactJson,\n ReportData,\n ReportInputs,\n} from './types';\n\nconst MAX_DETAIL_CHARS = 60000;\nconst MAX_LIST_ITEMS = 20;\n\nfunction formatAge(dateStr?: string): string {\n if (!dateStr) return '\\u2014';\n const date = new Date(dateStr);\n const now = new Date();\n const diffMs = now.getTime() - date.getTime();\n const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));\n\n if (diffDays < 1) return 'today';\n if (diffDays === 1) return 'yesterday';\n return `${diffDays} days ago`;\n}\n\nexport class PrActionService {\n collectChanges(\n contractsDir: string,\n contractsGlob: string,\n changedFiles: string[]\n ): string[] {\n if (contractsDir) {\n const prefix = contractsDir.endsWith('/')\n ? contractsDir\n : `${contractsDir}/`;\n return changedFiles.filter((path) => path.startsWith(prefix));\n }\n\n if (contractsGlob) {\n // Simple glob to regex conversion\n const re = new RegExp(\n '^' +\n contractsGlob\n .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&') // escape regex chars\n .replace(/\\*/g, '.*')\n .replace(/\\?/g, '.') +\n '$'\n );\n return changedFiles.filter((path) => re.test(path));\n }\n\n return changedFiles;\n }\n\n detectDrift(statusLines: string[]): {\n files: string[];\n status: 'pass' | 'fail';\n } {\n // lines are like \" M packages/...\" or \"?? packages/...\"\n // Python code: files = [line[3:] for line in status_lines if line.strip()]\n // and filter out .contractspec-ci\n const files = statusLines\n .map((line) => line.slice(3))\n .filter((path) => path && path.trim().length > 0)\n .filter(\n (path) =>\n path !== '.contractspec-ci' && !path.startsWith('.contractspec-ci/')\n );\n\n return {\n files,\n status: files.length > 0 ? 'fail' : 'pass',\n };\n }\n\n private deriveImpactStatus(impactJson: ImpactJson | undefined): {\n status: string;\n breaking: boolean;\n breakingCount: number;\n nonBreakingCount: number;\n total: number;\n } {\n const summary = impactJson?.summary || {};\n const breakingCount = summary.breaking || 0;\n const nonBreakingCount = summary.nonBreaking || 0;\n let totalCount = summary.total;\n if (totalCount === undefined) {\n totalCount = breakingCount + nonBreakingCount;\n }\n\n let breaking = impactJson?.breaking || false;\n let impactStatus = 'unknown';\n\n if (impactJson) {\n if (breaking || breakingCount > 0) {\n impactStatus = 'breaking';\n breaking = true;\n } else if (nonBreakingCount > 0) {\n impactStatus = 'non-breaking';\n } else if (totalCount === 0) {\n impactStatus = 'no-impact';\n }\n }\n\n return {\n status: impactStatus,\n breaking,\n breakingCount,\n nonBreakingCount,\n total: totalCount || 0,\n };\n }\n\n buildReportData(inputs: ReportInputs): ReportData {\n const changesSummary =\n inputs.contractChanges.length === 0\n ? 'No contract files changed.'\n : `${inputs.contractChanges.length} contract file(s) changed.`;\n\n const impactDerived = this.deriveImpactStatus(inputs.impactJson);\n\n const reportData: ReportData = {\n contracts: inputs.contractsJson,\n whatChanged: {\n summary: changesSummary,\n detailsPath: '.contractspec-ci/product-view.md',\n },\n risk: {\n status: impactDerived.status,\n breaking: impactDerived.breakingCount,\n nonBreaking: impactDerived.nonBreakingCount,\n },\n validation: {\n status: inputs.validationStatus,\n outputPath: '.contractspec-ci/validation.txt',\n },\n drift: {\n status: inputs.driftStatus,\n files: inputs.driftFiles,\n },\n nextSteps: [],\n };\n\n if (reportData.validation?.status === 'fail') {\n reportData.nextSteps?.push(\n 'Fix validation errors and rerun the workflow.'\n );\n }\n if (reportData.risk?.status === 'breaking') {\n reportData.nextSteps?.push(\n 'Review breaking changes and plan a migration.'\n );\n }\n if (reportData.risk?.status === 'unknown') {\n reportData.nextSteps?.push(\n 'Re-run impact detection or check contractspec impact output.'\n );\n }\n if (reportData.drift?.status === 'fail') {\n reportData.nextSteps?.push(\n 'Run the generate command locally and commit drift fixes.'\n );\n }\n\n // Add CI failure reasons logic here for \"nextSteps\" prepend?\n // In Python it was done separately. Here is fine too.\n const failureCheck = this.finalizeResults(reportData, inputs.failOn);\n if (failureCheck.shouldFail) {\n reportData.nextSteps?.unshift(\n `CI failed: ${failureCheck.failReasons.join(', ')} (fail_on=${inputs.failOn}).`\n );\n }\n\n return reportData;\n }\n\n finalizeResults(data: ReportData, failOn: string): FinalizeResult {\n const breakingDetected =\n data.risk?.status === 'breaking' || (data.risk?.breaking || 0) > 0;\n const driftFailed = data.drift?.status === 'fail';\n const validationFailed = data.validation?.status === 'fail';\n const impactUnknown = data.risk?.status === 'unknown';\n\n const reasons: string[] = [];\n let shouldFail = false;\n\n if (failOn !== 'never') {\n if (failOn === 'breaking') {\n if (breakingDetected) reasons.push('breaking changes detected');\n if (impactUnknown) reasons.push('impact status unknown');\n } else if (failOn === 'drift') {\n if (driftFailed) reasons.push('drift detected');\n if (impactUnknown) reasons.push('impact status unknown');\n } else if (failOn === 'any') {\n if (breakingDetected) reasons.push('breaking changes detected');\n if (driftFailed) reasons.push('drift detected');\n if (validationFailed) reasons.push('validation failed');\n if (impactUnknown) reasons.push('impact status unknown');\n } else {\n reasons.push(`invalid fail_on value (${failOn})`);\n }\n\n if (reasons.length > 0) {\n shouldFail = true;\n }\n }\n\n return {\n driftDetected: driftFailed,\n breakingChangeDetected: breakingDetected,\n validationFailed: validationFailed,\n shouldFail,\n failReasons: reasons,\n };\n }\n\n generateReportMarkdown(\n data: ReportData,\n fileContentProvider: (path: string) => string\n ): string {\n const truncate = (value: string, maxChars = MAX_DETAIL_CHARS): string => {\n if (value.length <= maxChars) return value;\n return `${value.slice(0, maxChars)}\\n\\n*(output truncated)*`;\n };\n\n const formatList = (items?: string[]): string => {\n if (!items || items.length === 0) return '- None';\n return items\n .slice(0, MAX_LIST_ITEMS)\n .map((item) => `- ${item}`)\n .join('\\n');\n };\n\n const viewContent = truncate(\n data.whatChanged?.detailsPath\n ? fileContentProvider(data.whatChanged.detailsPath)\n : ''\n );\n const validationOutput = truncate(\n data.validation?.outputPath\n ? fileContentProvider(data.validation.outputPath)\n : ''\n );\n const driftFiles = data.drift?.files ?? [];\n\n const lines: string[] = [];\n\n lines.push('## ContractSpec Report');\n lines.push('');\n\n if (data.contracts && data.contracts.length > 0) {\n lines.push('### Overall verification status');\n lines.push('');\n lines.push(\n '| Contract / Endpoint / Event | Time since verified | Drift debt | Surfaces covered | Last verified commit |'\n );\n lines.push('| --- | --- | --- | --- | --- |');\n for (const c of data.contracts) {\n const sha = c.lastVerifiedSha ?? '\\u2014';\n const time = formatAge(c.lastVerifiedDate);\n const surfaces = c.surfaces.join(', ');\n lines.push(\n `| ${c.name} | ${time} | ${c.driftMismatches} | ${surfaces} | ${sha} |`\n );\n }\n lines.push('');\n }\n\n lines.push('### 1) What changed');\n if (data.whatChanged?.summary) {\n lines.push(data.whatChanged.summary);\n } else {\n lines.push('No contract changes detected.');\n }\n if (viewContent.trim().length > 0) {\n lines.push('');\n lines.push('<details>');\n lines.push('<summary>Contract view (product)</summary>');\n lines.push('');\n lines.push(viewContent);\n lines.push('');\n lines.push('</details>');\n }\n\n lines.push('');\n lines.push('### 2) Risk classification');\n if (data.risk?.status) {\n const riskSummary = [\n `Status: ${data.risk.status}`,\n data.risk.breaking !== undefined\n ? `Breaking: ${data.risk.breaking}`\n : null,\n data.risk.nonBreaking !== undefined\n ? `Non-breaking: ${data.risk.nonBreaking}`\n : null,\n ].filter(Boolean);\n lines.push(riskSummary.join(' | '));\n } else {\n lines.push('Impact analysis unavailable.');\n }\n\n lines.push('');\n lines.push('### 3) Validation results');\n if (data.validation?.status) {\n lines.push(`Status: ${data.validation.status}`);\n if (validationOutput.trim().length > 0) {\n lines.push('');\n lines.push('<details>');\n lines.push('<summary>Validation output</summary>');\n lines.push('');\n lines.push(validationOutput);\n lines.push('');\n lines.push('</details>');\n }\n } else {\n lines.push('Validation step not run.');\n }\n\n lines.push('');\n lines.push('### 4) Drift results');\n if (data.drift?.status) {\n lines.push(`Status: ${data.drift.status}`);\n if (driftFiles.length > 0) {\n lines.push('');\n lines.push('<details>');\n lines.push('<summary>Drifted files</summary>');\n lines.push('');\n lines.push(formatList(driftFiles));\n lines.push('');\n lines.push('</details>');\n }\n } else {\n lines.push('Drift check not run.');\n }\n\n lines.push('');\n lines.push('### 5) Next steps');\n if (data.nextSteps && data.nextSteps.length > 0) {\n lines.push(formatList(data.nextSteps));\n } else {\n lines.push('- No action required.');\n }\n\n return `${lines.join('\\n')}\\n`;\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
//#region src/services/action-pr/types.d.ts
|
|
2
|
+
interface PrActionOptions {
|
|
3
|
+
workingDirectory: string;
|
|
4
|
+
contractsDir?: string;
|
|
5
|
+
contractsGlob?: string;
|
|
6
|
+
failOn?: string;
|
|
7
|
+
enableDrift?: boolean;
|
|
8
|
+
}
|
|
9
|
+
interface WhatChangedData {
|
|
10
|
+
summary?: string;
|
|
11
|
+
detailsPath?: string;
|
|
12
|
+
}
|
|
13
|
+
interface RiskData {
|
|
14
|
+
status?: string;
|
|
15
|
+
breaking?: number;
|
|
16
|
+
nonBreaking?: number;
|
|
17
|
+
}
|
|
18
|
+
interface ValidationData {
|
|
19
|
+
status?: string;
|
|
20
|
+
outputPath?: string;
|
|
21
|
+
}
|
|
22
|
+
interface DriftData {
|
|
23
|
+
status?: string;
|
|
24
|
+
files?: string[];
|
|
25
|
+
}
|
|
26
|
+
interface ContractVerificationStatus {
|
|
27
|
+
name: string;
|
|
28
|
+
lastVerifiedSha?: string;
|
|
29
|
+
lastVerifiedDate?: string;
|
|
30
|
+
surfaces: string[];
|
|
31
|
+
driftMismatches: number;
|
|
32
|
+
}
|
|
33
|
+
interface ReportData {
|
|
34
|
+
whatChanged?: WhatChangedData;
|
|
35
|
+
risk?: RiskData;
|
|
36
|
+
validation?: ValidationData;
|
|
37
|
+
drift?: DriftData;
|
|
38
|
+
nextSteps?: string[];
|
|
39
|
+
contracts?: ContractVerificationStatus[];
|
|
40
|
+
}
|
|
41
|
+
interface ReportInputs {
|
|
42
|
+
contractChanges: string[];
|
|
43
|
+
impactJson?: ImpactJson;
|
|
44
|
+
contractsJson: ContractVerificationStatus[];
|
|
45
|
+
validationStatus: string;
|
|
46
|
+
driftStatus: string;
|
|
47
|
+
driftFiles: string[];
|
|
48
|
+
failOn: string;
|
|
49
|
+
}
|
|
50
|
+
interface ImpactJson {
|
|
51
|
+
summary?: {
|
|
52
|
+
breaking?: number;
|
|
53
|
+
nonBreaking?: number;
|
|
54
|
+
total?: number;
|
|
55
|
+
};
|
|
56
|
+
breaking?: boolean;
|
|
57
|
+
}
|
|
58
|
+
interface FinalizeResult {
|
|
59
|
+
driftDetected: boolean;
|
|
60
|
+
breakingChangeDetected: boolean;
|
|
61
|
+
validationFailed: boolean;
|
|
62
|
+
shouldFail: boolean;
|
|
63
|
+
failReasons: string[];
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
66
|
+
export { ContractVerificationStatus, DriftData, FinalizeResult, ImpactJson, PrActionOptions, ReportData, ReportInputs, RiskData, ValidationData, WhatChangedData };
|
|
67
|
+
//# sourceMappingURL=types.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.mts","names":[],"sources":["../../../src/services/action-pr/types.ts"],"sourcesContent":[],"mappings":";UAAiB,eAAA;EAAA,gBAAA,EAAe,MAAA;EAQf,YAAA,CAAA,EAAA,MAAe;EAKf,aAAQ,CAAA,EAAA,MAAA;EAMR,MAAA,CAAA,EAAA,MAAA;EAKA,WAAA,CAAS,EAAA,OAAA;AAK1B;AAQiB,UA7BA,eAAA,CA6BU;EACX,OAAA,CAAA,EAAA,MAAA;EACP,WAAA,CAAA,EAAA,MAAA;;AAEC,UA5BO,QAAA,CA4BP;EAEI,MAAA,CAAA,EAAA,MAAA;EAA0B,QAAA,CAAA,EAAA,MAAA;EAGvB,WAAA,CAAA,EAAA,MAAY;AAU7B;AASiB,UA9CA,cAAA,CA8Cc;;;;UAzCd,SAAA;;;;UAKA,0BAAA;;;;;;;UAQA,UAAA;gBACD;SACP;eACM;UACL;;cAEI;;UAGG,YAAA;;eAEF;iBACE;;;;;;UAOA,UAAA;;;;;;;;UASA,cAAA"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { FsAdapter } from "../ports/fs.mjs";
|
|
2
2
|
import { LoggerAdapter } from "../ports/logger.mjs";
|
|
3
3
|
import * as _contractspec_module_workspace0 from "@contractspec/module.workspace";
|
|
4
|
-
import { SpecScanResult
|
|
4
|
+
import { SpecScanResult } from "@contractspec/module.workspace";
|
|
5
|
+
import { ResolvedContractsrcConfig } from "@contractspec/lib.contracts";
|
|
5
6
|
|
|
6
7
|
//#region src/services/build.d.ts
|
|
7
8
|
|
|
@@ -55,7 +56,7 @@ declare function buildSpec(specPath: string, adapters: {
|
|
|
55
56
|
fs: FsAdapter;
|
|
56
57
|
logger: LoggerAdapter;
|
|
57
58
|
workspace?: typeof _contractspec_module_workspace0;
|
|
58
|
-
}, config:
|
|
59
|
+
}, config: ResolvedContractsrcConfig, options?: BuildSpecOptions): Promise<BuildSpecResult>;
|
|
59
60
|
//#endregion
|
|
60
61
|
export { BuildSpecOptions, BuildSpecResult, BuildTarget, BuildTargetResult, buildSpec };
|
|
61
62
|
//# sourceMappingURL=build.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.d.mts","names":[],"sources":["../../src/services/build.ts"],"sourcesContent":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"build.d.mts","names":[],"sources":["../../src/services/build.ts"],"sourcesContent":[],"mappings":";;;;;;;;AA+DA;AASA;;AAIY,KAtDA,WAAA,GAsDA,SAAA,GAAA,WAAA,GAAA,MAAA;;;;AAKD,UAtDM,gBAAA,CAsDN;EAAR;;;YAlDS;;;;;;;;;;;;;;;;;UAqBK,iBAAA;UACP;;;;;;;;;UAUO,eAAA;;YAEL;WACD;;;;;iBAMW,SAAA;MAGd;UACI;qBAAa;WAGf,qCACC,mBACR,QAAQ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.mjs","names":[],"sources":["../../src/services/build.ts"],"sourcesContent":["/**\n * Build/scaffold service for generating implementation files from specs.\n *\n * Uses templates from @contractspec/module.workspace to generate\n * handler, component, and test skeletons without requiring AI.\n */\n\nimport {\n generateComponentTemplate,\n generateHandlerTemplate,\n generateTestTemplate,\n inferSpecTypeFromFilePath,\n scanSpecSource,\n type SpecScanResult,\n type WorkspaceConfig,\n} from '@contractspec/module.workspace';\nimport type { FsAdapter } from '../ports/fs';\nimport type { LoggerAdapter } from '../ports/logger';\n\n/**\n * Build target types.\n */\nexport type BuildTarget = 'handler' | 'component' | 'test';\n\n/**\n * Options for building from a spec.\n */\nexport interface BuildSpecOptions {\n /**\n * Which artifacts to generate.\n */\n targets?: BuildTarget[];\n\n /**\n * Override output directory.\n */\n outputDir?: string;\n\n /**\n * Whether to overwrite existing files.\n */\n overwrite?: boolean;\n\n /**\n * Skip writing files (dry run).\n */\n dryRun?: boolean;\n}\n\n/**\n * Result of a single build target.\n */\nexport interface BuildTargetResult {\n target: BuildTarget;\n outputPath: string;\n success: boolean;\n skipped?: boolean;\n error?: string;\n}\n\n/**\n * Result of building from a spec.\n */\nexport interface BuildSpecResult {\n specPath: string;\n specInfo: SpecScanResult;\n results: BuildTargetResult[];\n}\n\n/**\n * Build implementation files from a spec.\n */\nexport async function buildSpec(\n specPath: string,\n adapters: {\n fs: FsAdapter;\n logger: LoggerAdapter;\n workspace?: typeof import('@contractspec/module.workspace');\n },\n config: WorkspaceConfig,\n options: BuildSpecOptions = {}\n): Promise<BuildSpecResult> {\n const { fs, logger, workspace } = adapters;\n\n // Use injected modules or defaults\n const scan = workspace?.scanSpecSource ?? scanSpecSource;\n const inferType =\n workspace?.inferSpecTypeFromFilePath ?? inferSpecTypeFromFilePath;\n const genHandler =\n workspace?.generateHandlerTemplate ?? generateHandlerTemplate;\n const genComponent =\n workspace?.generateComponentTemplate ?? generateComponentTemplate;\n const genTest = workspace?.generateTestTemplate ?? generateTestTemplate;\n\n const {\n targets = detectDefaultTargets(specPath, inferType),\n outputDir = config.outputDir,\n overwrite = false,\n dryRun = false,\n } = options;\n\n // Read and scan spec\n const specCode = await fs.readFile(specPath);\n const specInfo = scan(specCode, specPath);\n const specType = inferType(specPath);\n\n logger.info(`Building from spec: ${specPath}`, { specType });\n\n const results: BuildTargetResult[] = [];\n\n for (const target of targets) {\n try {\n const result = await buildTarget(\n target,\n specPath,\n specCode,\n specInfo,\n specType,\n { fs, logger },\n outputDir,\n overwrite,\n dryRun,\n { genHandler, genComponent, genTest }\n );\n results.push(result);\n } catch (error) {\n results.push({\n target,\n outputPath: '',\n success: false,\n error: error instanceof Error ? error.message : String(error),\n skipped: false, // Default to false unless overridden\n });\n }\n }\n\n return {\n specPath,\n specInfo,\n results,\n };\n}\n\n/**\n * Build a single target from a spec.\n */\nasync function buildTarget(\n target: BuildTarget,\n specPath: string,\n _specCode: string,\n specInfo: SpecScanResult,\n specType: string,\n adapters: { fs: FsAdapter; logger: LoggerAdapter },\n outputDir: string,\n overwrite: boolean,\n dryRun: boolean,\n generators: {\n genHandler: typeof generateHandlerTemplate;\n genComponent: typeof generateComponentTemplate;\n genTest: typeof generateTestTemplate;\n }\n): Promise<BuildTargetResult> {\n const { fs, logger } = adapters;\n const { genHandler, genComponent, genTest } = generators;\n\n let code: string;\n let outputPath: string;\n\n switch (target) {\n case 'handler': {\n if (specType !== 'operation') {\n return {\n target,\n outputPath: '',\n success: false,\n skipped: true,\n error: `Handler generation only supported for operation specs (got ${specType})`,\n };\n }\n\n const kind =\n specInfo.kind === 'command' || specInfo.kind === 'query'\n ? specInfo.kind\n : 'command';\n\n code = genHandler(specInfo.key ?? 'unknown', kind);\n outputPath = resolveOutputPath(\n specPath,\n outputDir,\n 'handlers',\n specInfo.key ?? 'unknown',\n '.handler.ts',\n adapters.fs\n );\n break;\n }\n\n case 'component': {\n if (specType !== 'presentation') {\n return {\n target,\n outputPath: '',\n success: false,\n skipped: true,\n error: `Component generation only supported for presentation specs (got ${specType})`,\n };\n }\n\n code = genComponent(\n specInfo.key ?? 'unknown',\n specInfo.description ?? ''\n );\n outputPath = resolveOutputPath(\n specPath,\n outputDir,\n 'components',\n specInfo.key ?? 'unknown',\n '.tsx',\n adapters.fs\n );\n break;\n }\n\n case 'test': {\n const testType = specType === 'operation' ? 'handler' : 'component';\n code = genTest(specInfo.key ?? 'unknown', testType);\n outputPath = resolveOutputPath(\n specPath,\n outputDir,\n '__tests__',\n specInfo.key ?? 'unknown',\n '.test.ts',\n adapters.fs\n );\n break;\n }\n\n default:\n return {\n target,\n outputPath: '',\n success: false,\n error: `Unknown target: ${target}`,\n };\n }\n\n // Check if file exists\n const exists = await fs.exists(outputPath);\n if (exists && !overwrite) {\n return {\n target,\n outputPath,\n success: false,\n skipped: true,\n error: 'File already exists (use overwrite option)',\n };\n }\n\n if (dryRun) {\n logger.info(`[dry-run] Would write: ${outputPath}`);\n return {\n target,\n outputPath,\n success: true,\n };\n }\n\n // Ensure directory exists\n const dirPath = fs.dirname(outputPath);\n await fs.mkdir(dirPath);\n\n // Write file\n await fs.writeFile(outputPath, code);\n logger.info(`Generated: ${outputPath}`);\n\n return {\n target,\n outputPath,\n success: true,\n };\n}\n\n/**\n * Detect default targets based on spec type.\n */\nfunction detectDefaultTargets(\n specPath: string,\n inferType: typeof inferSpecTypeFromFilePath\n): BuildTarget[] {\n const specType = inferType(specPath);\n\n switch (specType) {\n case 'operation':\n return ['handler'];\n case 'presentation':\n return ['component'];\n default:\n return [];\n }\n}\n\n/**\n * Resolve output path for generated file.\n */\nfunction resolveOutputPath(\n specPath: string,\n outputDir: string,\n subdir: string,\n specName: string,\n extension: string,\n fs: FsAdapter\n): string {\n const sanitizedName = toKebabCase(specName.split('.').pop() ?? 'unknown');\n\n let baseDir: string;\n if (outputDir.startsWith('.')) {\n // Relative to spec file location\n baseDir = fs.resolve(fs.dirname(specPath), '..', outputDir, subdir);\n } else {\n // Absolute or relative to cwd\n baseDir = fs.resolve(outputDir, subdir);\n }\n\n return fs.join(baseDir, `${sanitizedName}${extension}`);\n}\n\n/**\n * Convert string to kebab-case.\n */\nfunction toKebabCase(str: string): string {\n return str\n .replace(/\\./g, '-')\n .replace(/([a-z])([A-Z])/g, '$1-$2')\n .toLowerCase();\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"build.mjs","names":[],"sources":["../../src/services/build.ts"],"sourcesContent":["/**\n * Build/scaffold service for generating implementation files from specs.\n *\n * Uses templates from @contractspec/module.workspace to generate\n * handler, component, and test skeletons without requiring AI.\n */\n\nimport {\n generateComponentTemplate,\n generateHandlerTemplate,\n generateTestTemplate,\n inferSpecTypeFromFilePath,\n scanSpecSource,\n type SpecScanResult,\n} from '@contractspec/module.workspace';\nimport type { FsAdapter } from '../ports/fs';\nimport type { LoggerAdapter } from '../ports/logger';\nimport type { ResolvedContractsrcConfig } from '@contractspec/lib.contracts';\n\n/**\n * Build target types.\n */\nexport type BuildTarget = 'handler' | 'component' | 'test';\n\n/**\n * Options for building from a spec.\n */\nexport interface BuildSpecOptions {\n /**\n * Which artifacts to generate.\n */\n targets?: BuildTarget[];\n\n /**\n * Override output directory.\n */\n outputDir?: string;\n\n /**\n * Whether to overwrite existing files.\n */\n overwrite?: boolean;\n\n /**\n * Skip writing files (dry run).\n */\n dryRun?: boolean;\n}\n\n/**\n * Result of a single build target.\n */\nexport interface BuildTargetResult {\n target: BuildTarget;\n outputPath: string;\n success: boolean;\n skipped?: boolean;\n error?: string;\n}\n\n/**\n * Result of building from a spec.\n */\nexport interface BuildSpecResult {\n specPath: string;\n specInfo: SpecScanResult;\n results: BuildTargetResult[];\n}\n\n/**\n * Build implementation files from a spec.\n */\nexport async function buildSpec(\n specPath: string,\n adapters: {\n fs: FsAdapter;\n logger: LoggerAdapter;\n workspace?: typeof import('@contractspec/module.workspace');\n },\n config: ResolvedContractsrcConfig,\n options: BuildSpecOptions = {}\n): Promise<BuildSpecResult> {\n const { fs, logger, workspace } = adapters;\n\n // Use injected modules or defaults\n const scan = workspace?.scanSpecSource ?? scanSpecSource;\n const inferType =\n workspace?.inferSpecTypeFromFilePath ?? inferSpecTypeFromFilePath;\n const genHandler =\n workspace?.generateHandlerTemplate ?? generateHandlerTemplate;\n const genComponent =\n workspace?.generateComponentTemplate ?? generateComponentTemplate;\n const genTest = workspace?.generateTestTemplate ?? generateTestTemplate;\n\n const {\n targets = detectDefaultTargets(specPath, inferType),\n outputDir = config.outputDir,\n overwrite = false,\n dryRun = false,\n } = options;\n\n // Read and scan spec\n const specCode = await fs.readFile(specPath);\n const specInfo = scan(specCode, specPath);\n const specType = inferType(specPath);\n\n logger.info(`Building from spec: ${specPath}`, { specType });\n\n const results: BuildTargetResult[] = [];\n\n for (const target of targets) {\n try {\n const result = await buildTarget(\n target,\n specPath,\n specCode,\n specInfo,\n specType,\n { fs, logger },\n outputDir,\n overwrite,\n dryRun,\n { genHandler, genComponent, genTest }\n );\n results.push(result);\n } catch (error) {\n results.push({\n target,\n outputPath: '',\n success: false,\n error: error instanceof Error ? error.message : String(error),\n skipped: false, // Default to false unless overridden\n });\n }\n }\n\n return {\n specPath,\n specInfo,\n results,\n };\n}\n\n/**\n * Build a single target from a spec.\n */\nasync function buildTarget(\n target: BuildTarget,\n specPath: string,\n _specCode: string,\n specInfo: SpecScanResult,\n specType: string,\n adapters: { fs: FsAdapter; logger: LoggerAdapter },\n outputDir: string,\n overwrite: boolean,\n dryRun: boolean,\n generators: {\n genHandler: typeof generateHandlerTemplate;\n genComponent: typeof generateComponentTemplate;\n genTest: typeof generateTestTemplate;\n }\n): Promise<BuildTargetResult> {\n const { fs, logger } = adapters;\n const { genHandler, genComponent, genTest } = generators;\n\n let code: string;\n let outputPath: string;\n\n switch (target) {\n case 'handler': {\n if (specType !== 'operation') {\n return {\n target,\n outputPath: '',\n success: false,\n skipped: true,\n error: `Handler generation only supported for operation specs (got ${specType})`,\n };\n }\n\n const kind =\n specInfo.kind === 'command' || specInfo.kind === 'query'\n ? specInfo.kind\n : 'command';\n\n code = genHandler(specInfo.key ?? 'unknown', kind);\n outputPath = resolveOutputPath(\n specPath,\n outputDir,\n 'handlers',\n specInfo.key ?? 'unknown',\n '.handler.ts',\n adapters.fs\n );\n break;\n }\n\n case 'component': {\n if (specType !== 'presentation') {\n return {\n target,\n outputPath: '',\n success: false,\n skipped: true,\n error: `Component generation only supported for presentation specs (got ${specType})`,\n };\n }\n\n code = genComponent(\n specInfo.key ?? 'unknown',\n specInfo.description ?? ''\n );\n outputPath = resolveOutputPath(\n specPath,\n outputDir,\n 'components',\n specInfo.key ?? 'unknown',\n '.tsx',\n adapters.fs\n );\n break;\n }\n\n case 'test': {\n const testType = specType === 'operation' ? 'handler' : 'component';\n code = genTest(specInfo.key ?? 'unknown', testType);\n outputPath = resolveOutputPath(\n specPath,\n outputDir,\n '__tests__',\n specInfo.key ?? 'unknown',\n '.test.ts',\n adapters.fs\n );\n break;\n }\n\n default:\n return {\n target,\n outputPath: '',\n success: false,\n error: `Unknown target: ${target}`,\n };\n }\n\n // Check if file exists\n const exists = await fs.exists(outputPath);\n if (exists && !overwrite) {\n return {\n target,\n outputPath,\n success: false,\n skipped: true,\n error: 'File already exists (use overwrite option)',\n };\n }\n\n if (dryRun) {\n logger.info(`[dry-run] Would write: ${outputPath}`);\n return {\n target,\n outputPath,\n success: true,\n };\n }\n\n // Ensure directory exists\n const dirPath = fs.dirname(outputPath);\n await fs.mkdir(dirPath);\n\n // Write file\n await fs.writeFile(outputPath, code);\n logger.info(`Generated: ${outputPath}`);\n\n return {\n target,\n outputPath,\n success: true,\n };\n}\n\n/**\n * Detect default targets based on spec type.\n */\nfunction detectDefaultTargets(\n specPath: string,\n inferType: typeof inferSpecTypeFromFilePath\n): BuildTarget[] {\n const specType = inferType(specPath);\n\n switch (specType) {\n case 'operation':\n return ['handler'];\n case 'presentation':\n return ['component'];\n default:\n return [];\n }\n}\n\n/**\n * Resolve output path for generated file.\n */\nfunction resolveOutputPath(\n specPath: string,\n outputDir: string,\n subdir: string,\n specName: string,\n extension: string,\n fs: FsAdapter\n): string {\n const sanitizedName = toKebabCase(specName.split('.').pop() ?? 'unknown');\n\n let baseDir: string;\n if (outputDir.startsWith('.')) {\n // Relative to spec file location\n baseDir = fs.resolve(fs.dirname(specPath), '..', outputDir, subdir);\n } else {\n // Absolute or relative to cwd\n baseDir = fs.resolve(outputDir, subdir);\n }\n\n return fs.join(baseDir, `${sanitizedName}${extension}`);\n}\n\n/**\n * Convert string to kebab-case.\n */\nfunction toKebabCase(str: string): string {\n return str\n .replace(/\\./g, '-')\n .replace(/([a-z])([A-Z])/g, '$1-$2')\n .toLowerCase();\n}\n"],"mappings":"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"}
|
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
import { createParser, detectFormat } from "../../coverage/parsers/index.mjs";
|
|
2
2
|
import { validateCoverage } from "../../coverage/validator.mjs";
|
|
3
|
-
import
|
|
3
|
+
import "@contractspec/module.workspace";
|
|
4
4
|
|
|
5
5
|
//#region src/services/ci-check/checks/coverage.ts
|
|
6
6
|
/**
|
|
7
|
-
* Coverage goal enforcement checks.
|
|
8
|
-
*
|
|
9
|
-
* Validates that TestSpec.coverage requirements are met by actual coverage data.
|
|
10
|
-
* Requires a coverage report file to exist (from a previous test run).
|
|
11
|
-
*/
|
|
12
|
-
/**
|
|
13
7
|
* Run coverage goal enforcement checks.
|
|
14
8
|
*/
|
|
15
9
|
async function runCoverageChecks(adapters, specFiles, options) {
|
|
@@ -34,40 +28,35 @@ async function runCoverageChecks(adapters, specFiles, options) {
|
|
|
34
28
|
}
|
|
35
29
|
const coverageReport = createParser(detectFormat(coveragePath ?? "coverage.json")).parse(coverageContent);
|
|
36
30
|
for (const specFile of specFiles) {
|
|
37
|
-
if (
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
required: failure.required,
|
|
67
|
-
actual: failure.actual
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
}
|
|
31
|
+
if (specFile.specType !== "test-spec" || !specFile.key || !specFile.version || !specFile.sourceBlock) continue;
|
|
32
|
+
const coverageMatch = specFile.sourceBlock.match(/coverage\s*:\s*\{([^}]+)\}/);
|
|
33
|
+
if (!coverageMatch || !coverageMatch[1]) continue;
|
|
34
|
+
const coverageBlock = coverageMatch[1];
|
|
35
|
+
const requirements = {};
|
|
36
|
+
const statementsMatch = coverageBlock.match(/statements\s*:\s*(\d+)/);
|
|
37
|
+
if (statementsMatch && statementsMatch[1]) requirements.statements = parseInt(statementsMatch[1], 10);
|
|
38
|
+
const branchesMatch = coverageBlock.match(/branches\s*:\s*(\d+)/);
|
|
39
|
+
if (branchesMatch && branchesMatch[1]) requirements.branches = parseInt(branchesMatch[1], 10);
|
|
40
|
+
const functionsMatch = coverageBlock.match(/functions\s*:\s*(\d+)/);
|
|
41
|
+
if (functionsMatch && functionsMatch[1]) requirements.functions = parseInt(functionsMatch[1], 10);
|
|
42
|
+
const linesMatch = coverageBlock.match(/lines\s*:\s*(\d+)/);
|
|
43
|
+
if (linesMatch && linesMatch[1]) requirements.lines = parseInt(linesMatch[1], 10);
|
|
44
|
+
if (Object.keys(requirements).length === 0) continue;
|
|
45
|
+
const result = validateCoverage(specFile.key, specFile.version, requirements, coverageReport.total);
|
|
46
|
+
for (const failure of result.failures) issues.push({
|
|
47
|
+
ruleId: "coverage-below-threshold",
|
|
48
|
+
severity: "error",
|
|
49
|
+
message: failure.message,
|
|
50
|
+
category: "coverage",
|
|
51
|
+
file: specFile.filePath,
|
|
52
|
+
context: {
|
|
53
|
+
specKey: specFile.key,
|
|
54
|
+
specVersion: specFile.version,
|
|
55
|
+
metric: failure.metric,
|
|
56
|
+
required: failure.required,
|
|
57
|
+
actual: failure.actual
|
|
58
|
+
}
|
|
59
|
+
});
|
|
71
60
|
}
|
|
72
61
|
return issues;
|
|
73
62
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"coverage.mjs","names":[],"sources":["../../../../src/services/ci-check/checks/coverage.ts"],"sourcesContent":["/**\n * Coverage goal enforcement checks.\n *\n * Validates that TestSpec.coverage requirements are met by actual coverage data.\n * Requires a coverage report file to exist (from a previous test run).\n */\n\nimport {
|
|
1
|
+
{"version":3,"file":"coverage.mjs","names":[],"sources":["../../../../src/services/ci-check/checks/coverage.ts"],"sourcesContent":["/**\n * Coverage goal enforcement checks.\n *\n * Validates that TestSpec.coverage requirements are met by actual coverage data.\n * Requires a coverage report file to exist (from a previous test run).\n */\n\nimport { type SpecScanResult } from '@contractspec/module.workspace';\nimport type { FsAdapter } from '../../../ports/fs';\nimport type { LoggerAdapter } from '../../../ports/logger';\nimport { createParser, detectFormat, validateCoverage } from '../../coverage';\nimport type { CICheckOptions, CIIssue } from '../types';\n\n/**\n * Run coverage goal enforcement checks.\n */\nexport async function runCoverageChecks(\n adapters: { fs: FsAdapter; logger: LoggerAdapter },\n specFiles: SpecScanResult[],\n options: CICheckOptions\n): Promise<CIIssue[]> {\n const { fs, logger } = adapters;\n const issues: CIIssue[] = [];\n\n // Try to find coverage report\n const coverageDir = options.workspaceRoot\n ? `${options.workspaceRoot}/coverage`\n : './coverage';\n\n const possiblePaths = [\n `${coverageDir}/coverage-final.json`,\n `${coverageDir}/coverage.json`,\n `${coverageDir}/coverage-summary.json`,\n ];\n\n let coverageContent: string | undefined;\n let coveragePath: string | undefined;\n\n for (const path of possiblePaths) {\n const exists = await fs.exists(path);\n if (exists) {\n coverageContent = await fs.readFile(path);\n coveragePath = path;\n break;\n }\n }\n\n if (!coverageContent) {\n logger.info('No coverage report found, skipping coverage checks');\n return issues;\n }\n\n // Parse coverage report\n const format = detectFormat(coveragePath ?? 'coverage.json');\n const parser = createParser(format);\n const coverageReport = parser.parse(coverageContent);\n\n // Scan for test specs with coverage requirements\n for (const specFile of specFiles) {\n if (\n specFile.specType !== 'test-spec' ||\n !specFile.key ||\n !specFile.version ||\n !specFile.sourceBlock\n )\n continue;\n\n // Extract coverage requirements (simplified - actual extraction would need more parsing)\n const coverageMatch = specFile.sourceBlock.match(\n /coverage\\s*:\\s*\\{([^}]+)\\}/\n );\n if (!coverageMatch || !coverageMatch[1]) continue;\n\n // Parse coverage requirements\n const coverageBlock = coverageMatch[1];\n const requirements: Record<string, number> = {};\n\n const statementsMatch = coverageBlock.match(/statements\\s*:\\s*(\\d+)/);\n if (statementsMatch && statementsMatch[1])\n requirements.statements = parseInt(statementsMatch[1], 10);\n\n const branchesMatch = coverageBlock.match(/branches\\s*:\\s*(\\d+)/);\n if (branchesMatch && branchesMatch[1])\n requirements.branches = parseInt(branchesMatch[1], 10);\n\n const functionsMatch = coverageBlock.match(/functions\\s*:\\s*(\\d+)/);\n if (functionsMatch && functionsMatch[1])\n requirements.functions = parseInt(functionsMatch[1], 10);\n\n const linesMatch = coverageBlock.match(/lines\\s*:\\s*(\\d+)/);\n if (linesMatch && linesMatch[1])\n requirements.lines = parseInt(linesMatch[1], 10);\n\n if (Object.keys(requirements).length === 0) continue;\n\n // Validate coverage against requirements\n const result = validateCoverage(\n specFile.key,\n specFile.version,\n requirements,\n coverageReport.total\n );\n\n // Report failures as errors\n for (const failure of result.failures) {\n issues.push({\n ruleId: 'coverage-below-threshold',\n severity: 'error',\n message: failure.message,\n category: 'coverage',\n file: specFile.filePath,\n context: {\n specKey: specFile.key,\n specVersion: specFile.version,\n metric: failure.metric,\n required: failure.required,\n actual: failure.actual,\n },\n });\n }\n }\n\n return issues;\n}\n"],"mappings":";;;;;;;;AAgBA,eAAsB,kBACpB,UACA,WACA,SACoB;CACpB,MAAM,EAAE,IAAI,WAAW;CACvB,MAAM,SAAoB,EAAE;CAG5B,MAAM,cAAc,QAAQ,gBACxB,GAAG,QAAQ,cAAc,aACzB;CAEJ,MAAM,gBAAgB;EACpB,GAAG,YAAY;EACf,GAAG,YAAY;EACf,GAAG,YAAY;EAChB;CAED,IAAI;CACJ,IAAI;AAEJ,MAAK,MAAM,QAAQ,cAEjB,KADe,MAAM,GAAG,OAAO,KAAK,EACxB;AACV,oBAAkB,MAAM,GAAG,SAAS,KAAK;AACzC,iBAAe;AACf;;AAIJ,KAAI,CAAC,iBAAiB;AACpB,SAAO,KAAK,qDAAqD;AACjE,SAAO;;CAMT,MAAM,iBADS,aADA,aAAa,gBAAgB,gBAAgB,CACzB,CACL,MAAM,gBAAgB;AAGpD,MAAK,MAAM,YAAY,WAAW;AAChC,MACE,SAAS,aAAa,eACtB,CAAC,SAAS,OACV,CAAC,SAAS,WACV,CAAC,SAAS,YAEV;EAGF,MAAM,gBAAgB,SAAS,YAAY,MACzC,6BACD;AACD,MAAI,CAAC,iBAAiB,CAAC,cAAc,GAAI;EAGzC,MAAM,gBAAgB,cAAc;EACpC,MAAM,eAAuC,EAAE;EAE/C,MAAM,kBAAkB,cAAc,MAAM,yBAAyB;AACrE,MAAI,mBAAmB,gBAAgB,GACrC,cAAa,aAAa,SAAS,gBAAgB,IAAI,GAAG;EAE5D,MAAM,gBAAgB,cAAc,MAAM,uBAAuB;AACjE,MAAI,iBAAiB,cAAc,GACjC,cAAa,WAAW,SAAS,cAAc,IAAI,GAAG;EAExD,MAAM,iBAAiB,cAAc,MAAM,wBAAwB;AACnE,MAAI,kBAAkB,eAAe,GACnC,cAAa,YAAY,SAAS,eAAe,IAAI,GAAG;EAE1D,MAAM,aAAa,cAAc,MAAM,oBAAoB;AAC3D,MAAI,cAAc,WAAW,GAC3B,cAAa,QAAQ,SAAS,WAAW,IAAI,GAAG;AAElD,MAAI,OAAO,KAAK,aAAa,CAAC,WAAW,EAAG;EAG5C,MAAM,SAAS,iBACb,SAAS,KACT,SAAS,SACT,cACA,eAAe,MAChB;AAGD,OAAK,MAAM,WAAW,OAAO,SAC3B,QAAO,KAAK;GACV,QAAQ;GACR,UAAU;GACV,SAAS,QAAQ;GACjB,UAAU;GACV,MAAM,SAAS;GACf,SAAS;IACP,SAAS,SAAS;IAClB,aAAa,SAAS;IACtB,QAAQ,QAAQ;IAChB,UAAU,QAAQ;IAClB,QAAQ,QAAQ;IACjB;GACF,CAAC;;AAIN,QAAO"}
|
|
@@ -10,7 +10,7 @@ async function runHandlerChecks(adapters, specFiles) {
|
|
|
10
10
|
const issues = [];
|
|
11
11
|
const config = await loadWorkspaceConfig(fs);
|
|
12
12
|
for (const specFile of specFiles) {
|
|
13
|
-
if (
|
|
13
|
+
if (specFile.specType !== "operation") continue;
|
|
14
14
|
const result = await validateImplementationFiles(specFile, { fs }, config, {
|
|
15
15
|
checkHandlers: true,
|
|
16
16
|
outputDir: config.outputDir
|
|
@@ -20,14 +20,14 @@ async function runHandlerChecks(adapters, specFiles) {
|
|
|
20
20
|
severity: "warning",
|
|
21
21
|
message: error,
|
|
22
22
|
category: "handlers",
|
|
23
|
-
file: specFile
|
|
23
|
+
file: specFile.filePath
|
|
24
24
|
});
|
|
25
25
|
for (const warning of result.warnings) issues.push({
|
|
26
26
|
ruleId: "handler-warning",
|
|
27
27
|
severity: "warning",
|
|
28
28
|
message: warning,
|
|
29
29
|
category: "handlers",
|
|
30
|
-
file: specFile
|
|
30
|
+
file: specFile.filePath
|
|
31
31
|
});
|
|
32
32
|
}
|
|
33
33
|
return issues;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handlers.mjs","names":[],"sources":["../../../../src/services/ci-check/checks/handlers.ts"],"sourcesContent":["/**\n * Handler implementation checks.\n */\n\nimport type { FsAdapter } from '../../../ports/fs';\nimport type { LoggerAdapter } from '../../../ports/logger';\nimport { loadWorkspaceConfig } from '../../config';\nimport { validateImplementationFiles } from '../../validate/implementation-validator';\nimport type { CIIssue } from '../types';\n\n/**\n * Run handler implementation checks.\n */\nexport async function runHandlerChecks(\n adapters: { fs: FsAdapter; logger: LoggerAdapter },\n specFiles:
|
|
1
|
+
{"version":3,"file":"handlers.mjs","names":[],"sources":["../../../../src/services/ci-check/checks/handlers.ts"],"sourcesContent":["/**\n * Handler implementation checks.\n */\n\nimport type { FsAdapter } from '../../../ports/fs';\nimport type { LoggerAdapter } from '../../../ports/logger';\nimport { loadWorkspaceConfig } from '../../config';\nimport { validateImplementationFiles } from '../../validate/implementation-validator';\nimport type { CIIssue } from '../types';\nimport type { SpecScanResult } from '@contractspec/module.workspace';\n\n/**\n * Run handler implementation checks.\n */\nexport async function runHandlerChecks(\n adapters: { fs: FsAdapter; logger: LoggerAdapter },\n specFiles: SpecScanResult[]\n): Promise<CIIssue[]> {\n const { fs } = adapters;\n const issues: CIIssue[] = [];\n\n const config = await loadWorkspaceConfig(fs);\n\n for (const specFile of specFiles) {\n // Only check operation specs\n if (specFile.specType !== 'operation') continue;\n\n const result = await validateImplementationFiles(specFile, { fs }, config, {\n checkHandlers: true,\n outputDir: config.outputDir,\n });\n\n for (const error of result.errors) {\n issues.push({\n ruleId: 'handler-missing',\n severity: 'warning', // Handler missing is a warning, not error\n message: error,\n category: 'handlers',\n file: specFile.filePath,\n });\n }\n\n for (const warning of result.warnings) {\n issues.push({\n ruleId: 'handler-warning',\n severity: 'warning',\n message: warning,\n category: 'handlers',\n file: specFile.filePath,\n });\n }\n }\n\n return issues;\n}\n"],"mappings":";;;;;;;AAcA,eAAsB,iBACpB,UACA,WACoB;CACpB,MAAM,EAAE,OAAO;CACf,MAAM,SAAoB,EAAE;CAE5B,MAAM,SAAS,MAAM,oBAAoB,GAAG;AAE5C,MAAK,MAAM,YAAY,WAAW;AAEhC,MAAI,SAAS,aAAa,YAAa;EAEvC,MAAM,SAAS,MAAM,4BAA4B,UAAU,EAAE,IAAI,EAAE,QAAQ;GACzE,eAAe;GACf,WAAW,OAAO;GACnB,CAAC;AAEF,OAAK,MAAM,SAAS,OAAO,OACzB,QAAO,KAAK;GACV,QAAQ;GACR,UAAU;GACV,SAAS;GACT,UAAU;GACV,MAAM,SAAS;GAChB,CAAC;AAGJ,OAAK,MAAM,WAAW,OAAO,SAC3B,QAAO,KAAK;GACV,QAAQ;GACR,UAAU;GACV,SAAS;GACT,UAAU;GACV,MAAM,SAAS;GAChB,CAAC;;AAIN,QAAO"}
|
|
@@ -10,7 +10,7 @@ async function runImplementationChecks(adapters, specFiles, options) {
|
|
|
10
10
|
const issues = [];
|
|
11
11
|
const config = await loadWorkspaceConfig(fs);
|
|
12
12
|
const implOptions = options.implementation ?? {};
|
|
13
|
-
const results = await resolveAllImplementations(specFiles.filter((f) => f.
|
|
13
|
+
const results = await resolveAllImplementations(specFiles.filter((f) => f.specType === "operation"), { fs }, config, { computeHashes: implOptions.useCache ?? true });
|
|
14
14
|
for (const result of results) {
|
|
15
15
|
if (implOptions.requireImplemented && result.status === "missing") issues.push({
|
|
16
16
|
ruleId: "impl-missing",
|