@lssm/module.contractspec-workspace 0.0.0-canary-20251217083314 → 1.41.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/ai/code-generation.js +13 -50
- package/dist/ai/spec-creation.js +18 -50
- package/dist/analysis/deps/graph.js +2 -84
- package/dist/analysis/deps/parse-imports.js +1 -30
- package/dist/analysis/diff/semantic.js +1 -96
- package/dist/analysis/feature-scan.js +1 -151
- package/dist/analysis/spec-scan.js +1 -344
- package/dist/analysis/validate/spec-structure.js +1 -122
- package/dist/index.js +1 -25
- package/dist/templates/app-config.js +28 -100
- package/dist/templates/data-view.js +27 -41
- package/dist/templates/event.js +14 -28
- package/dist/templates/experiment.js +51 -76
- package/dist/templates/handler.js +17 -49
- package/dist/templates/integration-utils.js +26 -97
- package/dist/templates/integration.js +23 -46
- package/dist/templates/knowledge.js +19 -59
- package/dist/templates/migration.js +26 -49
- package/dist/templates/operation.js +28 -40
- package/dist/templates/presentation.js +20 -45
- package/dist/templates/telemetry.js +53 -73
- package/dist/templates/utils.js +1 -38
- package/dist/templates/workflow-runner.js +6 -12
- package/dist/templates/workflow.js +24 -50
- package/dist/types/generation-types.js +1 -20
- package/package.json +6 -7
- package/dist/ai/code-generation.d.ts +0 -27
- package/dist/ai/spec-creation.d.ts +0 -26
- package/dist/analysis/deps/graph.d.ts +0 -33
- package/dist/analysis/deps/parse-imports.d.ts +0 -16
- package/dist/analysis/diff/semantic.d.ts +0 -10
- package/dist/analysis/feature-scan.d.ts +0 -14
- package/dist/analysis/spec-scan.d.ts +0 -33
- package/dist/analysis/validate/spec-structure.d.ts +0 -10
- package/dist/index.d.ts +0 -26
- package/dist/templates/app-config.d.ts +0 -6
- package/dist/templates/data-view.d.ts +0 -6
- package/dist/templates/event.d.ts +0 -10
- package/dist/templates/experiment.d.ts +0 -6
- package/dist/templates/handler.d.ts +0 -19
- package/dist/templates/integration.d.ts +0 -6
- package/dist/templates/knowledge.d.ts +0 -6
- package/dist/templates/migration.d.ts +0 -6
- package/dist/templates/operation.d.ts +0 -10
- package/dist/templates/presentation.d.ts +0 -10
- package/dist/templates/telemetry.d.ts +0 -6
- package/dist/templates/utils.d.ts +0 -26
- package/dist/templates/workflow-runner.d.ts +0 -15
- package/dist/templates/workflow.d.ts +0 -10
- package/dist/types/analysis-types.d.ts +0 -125
- package/dist/types/generation-types.d.ts +0 -83
- package/dist/types/spec-types.d.ts +0 -344
|
@@ -1,18 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* AI prompts for code generation.
|
|
4
|
-
* Extracted from cli-contracts/src/ai/prompts/code-generation.ts
|
|
5
|
-
*/
|
|
6
|
-
/**
|
|
7
|
-
* Build prompt for generating handler implementation.
|
|
8
|
-
*/
|
|
9
|
-
function buildHandlerPrompt(specCode) {
|
|
10
|
-
return `You are a senior TypeScript developer implementing a handler for a contract specification.
|
|
1
|
+
function e(e){return`You are a senior TypeScript developer implementing a handler for a contract specification.
|
|
11
2
|
|
|
12
3
|
Here is the contract spec:
|
|
13
4
|
|
|
14
5
|
\`\`\`typescript
|
|
15
|
-
${
|
|
6
|
+
${e}
|
|
16
7
|
\`\`\`
|
|
17
8
|
|
|
18
9
|
Generate a complete handler implementation that:
|
|
@@ -26,18 +17,12 @@ Generate a complete handler implementation that:
|
|
|
26
17
|
|
|
27
18
|
The handler should be production-ready with proper error handling, logging points, and clear structure.
|
|
28
19
|
|
|
29
|
-
Return only the TypeScript code for the handler function
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Build prompt for generating React component from presentation spec.
|
|
33
|
-
*/
|
|
34
|
-
function buildComponentPrompt(specCode) {
|
|
35
|
-
return `You are a senior React developer creating a component for a presentation specification.
|
|
20
|
+
Return only the TypeScript code for the handler function.`}function t(e){return`You are a senior React developer creating a component for a presentation specification.
|
|
36
21
|
|
|
37
22
|
Here is the presentation spec:
|
|
38
23
|
|
|
39
24
|
\`\`\`typescript
|
|
40
|
-
${
|
|
25
|
+
${e}
|
|
41
26
|
\`\`\`
|
|
42
27
|
|
|
43
28
|
Generate a complete React component that:
|
|
@@ -51,18 +36,12 @@ Generate a complete React component that:
|
|
|
51
36
|
|
|
52
37
|
The component should follow Atomic Design principles and be reusable.
|
|
53
38
|
|
|
54
|
-
Return only the TypeScript/TSX code for the component
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Build prompt for generating form component.
|
|
58
|
-
*/
|
|
59
|
-
function buildFormPrompt(specCode) {
|
|
60
|
-
return `You are a senior React developer creating a form component from a form specification.
|
|
39
|
+
Return only the TypeScript/TSX code for the component.`}function n(e){return`You are a senior React developer creating a form component from a form specification.
|
|
61
40
|
|
|
62
41
|
Here is the form spec:
|
|
63
42
|
|
|
64
43
|
\`\`\`typescript
|
|
65
|
-
${
|
|
44
|
+
${e}
|
|
66
45
|
\`\`\`
|
|
67
46
|
|
|
68
47
|
Generate a complete form component using react-hook-form that:
|
|
@@ -77,31 +56,25 @@ Generate a complete form component using react-hook-form that:
|
|
|
77
56
|
|
|
78
57
|
The form should provide excellent UX with real-time validation and helpful feedback.
|
|
79
58
|
|
|
80
|
-
Return only the TypeScript/TSX code for the form component
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Build prompt for generating tests.
|
|
84
|
-
*/
|
|
85
|
-
function buildTestPrompt(specCode, implementationCode, testType) {
|
|
86
|
-
return `You are a senior developer writing comprehensive tests.
|
|
59
|
+
Return only the TypeScript/TSX code for the form component.`}function r(e,t,n){return`You are a senior developer writing comprehensive tests.
|
|
87
60
|
|
|
88
61
|
Spec:
|
|
89
62
|
\`\`\`typescript
|
|
90
|
-
${
|
|
63
|
+
${e}
|
|
91
64
|
\`\`\`
|
|
92
65
|
|
|
93
66
|
Implementation:
|
|
94
67
|
\`\`\`typescript
|
|
95
|
-
${
|
|
68
|
+
${t}
|
|
96
69
|
\`\`\`
|
|
97
70
|
|
|
98
71
|
Generate complete test suite using Vitest that:
|
|
99
|
-
${
|
|
72
|
+
${n===`handler`?`
|
|
100
73
|
- Test all acceptance scenarios from the spec
|
|
101
74
|
- Test error cases defined in spec.io.errors
|
|
102
75
|
- Verify events are emitted correctly
|
|
103
76
|
- Test input validation
|
|
104
|
-
- Test happy path and edge cases
|
|
77
|
+
- Test happy path and edge cases`:`
|
|
105
78
|
- Test rendering with various props
|
|
106
79
|
- Test user interactions
|
|
107
80
|
- Test accessibility (a11y)
|
|
@@ -110,13 +83,7 @@ ${testType === "handler" ? `
|
|
|
110
83
|
|
|
111
84
|
Use clear test descriptions and follow AAA pattern (Arrange, Act, Assert).
|
|
112
85
|
|
|
113
|
-
Return only the TypeScript test code
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* System prompt for code generation.
|
|
117
|
-
*/
|
|
118
|
-
function getCodeGenSystemPrompt() {
|
|
119
|
-
return `You are an expert TypeScript developer with deep knowledge of:
|
|
86
|
+
Return only the TypeScript test code.`}function i(){return`You are an expert TypeScript developer with deep knowledge of:
|
|
120
87
|
- Type-safe API design
|
|
121
88
|
- React and modern hooks
|
|
122
89
|
- Test-driven development
|
|
@@ -130,8 +97,4 @@ Generate production-ready code that is:
|
|
|
130
97
|
- Defensive and error-safe
|
|
131
98
|
- Easy to maintain and extend
|
|
132
99
|
|
|
133
|
-
Always prioritize code quality, safety, and user experience
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
//#endregion
|
|
137
|
-
export { buildComponentPrompt, buildFormPrompt, buildHandlerPrompt, buildTestPrompt, getCodeGenSystemPrompt };
|
|
100
|
+
Always prioritize code quality, safety, and user experience.`}export{t as buildComponentPrompt,n as buildFormPrompt,e as buildHandlerPrompt,r as buildTestPrompt,i as getCodeGenSystemPrompt};
|
package/dist/ai/spec-creation.js
CHANGED
|
@@ -1,13 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* Build prompt for creating operation spec from description.
|
|
4
|
-
*/
|
|
5
|
-
function buildOperationSpecPrompt(description, kind) {
|
|
6
|
-
return `You are a senior software architect creating a contract specification for an operation.
|
|
1
|
+
function e(e,t){return`You are a senior software architect creating a contract specification for an operation.
|
|
7
2
|
|
|
8
|
-
The operation is a ${
|
|
3
|
+
The operation is a ${t} (${t===`command`?`changes state, has side effects`:`read-only, idempotent`}).
|
|
9
4
|
|
|
10
|
-
User description: ${
|
|
5
|
+
User description: ${e}
|
|
11
6
|
|
|
12
7
|
Create a complete contract specification following these guidelines:
|
|
13
8
|
|
|
@@ -21,15 +16,9 @@ Create a complete contract specification following these guidelines:
|
|
|
21
16
|
8. **Feature Flags**: Any flags that gate this operation
|
|
22
17
|
9. **Side Effects**: What events might be emitted, analytics to track
|
|
23
18
|
|
|
24
|
-
Respond with a structured spec
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Build prompt for creating event spec from description.
|
|
28
|
-
*/
|
|
29
|
-
function buildEventSpecPrompt(description) {
|
|
30
|
-
return `You are a senior software architect creating an event specification.
|
|
19
|
+
Respond with a structured spec.`}function t(e){return`You are a senior software architect creating an event specification.
|
|
31
20
|
|
|
32
|
-
User description: ${
|
|
21
|
+
User description: ${e}
|
|
33
22
|
|
|
34
23
|
Create a complete event specification following these guidelines:
|
|
35
24
|
|
|
@@ -41,21 +30,11 @@ Create a complete event specification following these guidelines:
|
|
|
41
30
|
|
|
42
31
|
Events represent things that have already happened and should use past tense.
|
|
43
32
|
|
|
44
|
-
Respond with a structured spec
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Build prompt for creating presentation spec from description.
|
|
48
|
-
*/
|
|
49
|
-
function buildPresentationSpecPrompt(description, kind) {
|
|
50
|
-
return `You are a senior software architect creating a presentation specification.
|
|
33
|
+
Respond with a structured spec.`}function n(e,t){return`You are a senior software architect creating a presentation specification.
|
|
51
34
|
|
|
52
|
-
This is a ${
|
|
53
|
-
web_component: "a React component with props schema",
|
|
54
|
-
markdown: "markdown/MDX documentation or guide",
|
|
55
|
-
data: "structured data export (JSON/XML)"
|
|
56
|
-
}[kind]}.
|
|
35
|
+
This is a ${t} presentation - ${{web_component:`a React component with props schema`,markdown:`markdown/MDX documentation or guide`,data:`structured data export (JSON/XML)`}[t]}.
|
|
57
36
|
|
|
58
|
-
User description: ${
|
|
37
|
+
User description: ${e}
|
|
59
38
|
|
|
60
39
|
Create a complete presentation specification following these guidelines:
|
|
61
40
|
|
|
@@ -63,15 +42,13 @@ Create a complete presentation specification following these guidelines:
|
|
|
63
42
|
2. **Version**: Start at 1
|
|
64
43
|
3. **Description**: What this presentation shows/provides
|
|
65
44
|
4. **Kind-specific details**:
|
|
66
|
-
${
|
|
45
|
+
${t===`web_component`?`- Component key (symbolic, resolved by host app)
|
|
46
|
+
- Props structure
|
|
47
|
+
- Analytics events to track`:t===`markdown`?`- Content or resource URI
|
|
48
|
+
- Target audience`:`- MIME type (e.g., application/json)
|
|
49
|
+
- Data structure description`}
|
|
67
50
|
|
|
68
|
-
Respond with a structured spec
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Build system prompt for all spec generation.
|
|
72
|
-
*/
|
|
73
|
-
function getSystemPrompt() {
|
|
74
|
-
return `You are an expert software architect specializing in API design and contract-driven development.
|
|
51
|
+
Respond with a structured spec.`}function r(){return`You are an expert software architect specializing in API design and contract-driven development.
|
|
75
52
|
|
|
76
53
|
You create clear, well-documented specifications that serve as the single source of truth for operations, events, and presentations.
|
|
77
54
|
|
|
@@ -81,21 +58,12 @@ Your specs are:
|
|
|
81
58
|
- Business-oriented (capturing the "why" not just "what")
|
|
82
59
|
- Designed for both humans and AI agents to understand
|
|
83
60
|
|
|
84
|
-
Always use proper dot notation for names and ensure all metadata is meaningful and accurate
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* Create example-based prompt for better results.
|
|
88
|
-
*/
|
|
89
|
-
function addExampleContext(basePrompt, examples) {
|
|
90
|
-
if (examples.length === 0) return basePrompt;
|
|
91
|
-
return `${basePrompt}
|
|
61
|
+
Always use proper dot notation for names and ensure all metadata is meaningful and accurate.`}function i(e,t){return t.length===0?e:`${e}
|
|
92
62
|
|
|
93
63
|
Here are some good examples for reference:
|
|
94
64
|
|
|
95
|
-
${
|
|
65
|
+
${t.join(`
|
|
96
66
|
|
|
97
|
-
|
|
98
|
-
}
|
|
67
|
+
`)}
|
|
99
68
|
|
|
100
|
-
|
|
101
|
-
export { addExampleContext, buildEventSpecPrompt, buildOperationSpecPrompt, buildPresentationSpecPrompt, getSystemPrompt };
|
|
69
|
+
Follow this structure and quality level.`}export{i as addExampleContext,t as buildEventSpecPrompt,e as buildOperationSpecPrompt,n as buildPresentationSpecPrompt,r as getSystemPrompt};
|
|
@@ -1,84 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* Build reverse edges (dependents) for all nodes in the graph.
|
|
4
|
-
*/
|
|
5
|
-
function buildReverseEdges(graph) {
|
|
6
|
-
for (const node of graph.values()) node.dependents = [];
|
|
7
|
-
for (const [name, node] of graph) for (const dep of node.dependencies) {
|
|
8
|
-
const depNode = graph.get(dep);
|
|
9
|
-
if (depNode) depNode.dependents.push(name);
|
|
10
|
-
}
|
|
11
|
-
for (const node of graph.values()) node.dependents.sort((a, b) => a.localeCompare(b));
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Detect circular dependencies in the graph.
|
|
15
|
-
*/
|
|
16
|
-
function detectCycles(graph) {
|
|
17
|
-
const visited = /* @__PURE__ */ new Set();
|
|
18
|
-
const stack = /* @__PURE__ */ new Set();
|
|
19
|
-
const cycles = [];
|
|
20
|
-
function dfs(name, path) {
|
|
21
|
-
if (stack.has(name)) {
|
|
22
|
-
const start = path.indexOf(name);
|
|
23
|
-
if (start >= 0) cycles.push([...path.slice(start), name]);
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
if (visited.has(name)) return;
|
|
27
|
-
visited.add(name);
|
|
28
|
-
stack.add(name);
|
|
29
|
-
const node = graph.get(name);
|
|
30
|
-
if (node) for (const dep of node.dependencies) dfs(dep, [...path, name]);
|
|
31
|
-
stack.delete(name);
|
|
32
|
-
}
|
|
33
|
-
for (const name of graph.keys()) if (!visited.has(name)) dfs(name, []);
|
|
34
|
-
return cycles;
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Find missing dependencies (referenced but not defined).
|
|
38
|
-
*/
|
|
39
|
-
function findMissingDependencies(graph) {
|
|
40
|
-
const missing = [];
|
|
41
|
-
for (const [name, node] of graph) {
|
|
42
|
-
const absent = node.dependencies.filter((dep) => !graph.has(dep));
|
|
43
|
-
if (absent.length > 0) missing.push({
|
|
44
|
-
contract: name,
|
|
45
|
-
missing: absent
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
return missing;
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Generate DOT format output for visualization.
|
|
52
|
-
*/
|
|
53
|
-
function toDot(graph) {
|
|
54
|
-
const lines = [];
|
|
55
|
-
lines.push("digraph ContractDependencies {");
|
|
56
|
-
lines.push(" rankdir=LR;");
|
|
57
|
-
lines.push(" node [shape=box];");
|
|
58
|
-
for (const [name, node] of graph) {
|
|
59
|
-
for (const dep of node.dependencies) lines.push(` "${name}" -> "${dep}";`);
|
|
60
|
-
if (node.dependencies.length === 0) lines.push(` "${name}";`);
|
|
61
|
-
}
|
|
62
|
-
lines.push("}");
|
|
63
|
-
return lines.join("\n");
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Create an empty contract graph.
|
|
67
|
-
*/
|
|
68
|
-
function createContractGraph() {
|
|
69
|
-
return /* @__PURE__ */ new Map();
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Add a node to the contract graph.
|
|
73
|
-
*/
|
|
74
|
-
function addContractNode(graph, name, file, dependencies) {
|
|
75
|
-
graph.set(name, {
|
|
76
|
-
name,
|
|
77
|
-
file,
|
|
78
|
-
dependencies,
|
|
79
|
-
dependents: []
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
//#endregion
|
|
84
|
-
export { addContractNode, buildReverseEdges, createContractGraph, detectCycles, findMissingDependencies, toDot };
|
|
1
|
+
function e(e){for(let t of e.values())t.dependents=[];for(let[t,n]of e)for(let r of n.dependencies){let n=e.get(r);n&&n.dependents.push(t)}for(let t of e.values())t.dependents.sort((e,t)=>e.localeCompare(t))}function t(e){let t=new Set,n=new Set,r=[];function i(a,o){if(n.has(a)){let e=o.indexOf(a);e>=0&&r.push([...o.slice(e),a]);return}if(t.has(a))return;t.add(a),n.add(a);let s=e.get(a);if(s)for(let e of s.dependencies)i(e,[...o,a]);n.delete(a)}for(let n of e.keys())t.has(n)||i(n,[]);return r}function n(e){let t=[];for(let[n,r]of e){let i=r.dependencies.filter(t=>!e.has(t));i.length>0&&t.push({contract:n,missing:i})}return t}function r(e){let t=[];t.push(`digraph ContractDependencies {`),t.push(` rankdir=LR;`),t.push(` node [shape=box];`);for(let[n,r]of e){for(let e of r.dependencies)t.push(` "${n}" -> "${e}";`);r.dependencies.length===0&&t.push(` "${n}";`)}return t.push(`}`),t.join(`
|
|
2
|
+
`)}function i(){return new Map}function a(e,t,n,r){e.set(t,{name:t,file:n,dependencies:r,dependents:[]})}export{a as addContractNode,e as buildReverseEdges,i as createContractGraph,t as detectCycles,n as findMissingDependencies,r as toDot};
|
|
@@ -1,30 +1 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* Import parsing utilities for dependency analysis.
|
|
4
|
-
* Extracted from cli-contracts/src/commands/deps/parse-imports.ts
|
|
5
|
-
*/
|
|
6
|
-
/**
|
|
7
|
-
* Parse spec imports from source code.
|
|
8
|
-
* Returns the names of imported specs based on file naming conventions.
|
|
9
|
-
*
|
|
10
|
-
* @param sourceCode - The source code to parse
|
|
11
|
-
* @param fromFilePath - The path of the file being parsed (for relative resolution)
|
|
12
|
-
* @returns Array of imported spec names
|
|
13
|
-
*/
|
|
14
|
-
function parseImportedSpecNames(sourceCode, _fromFilePath) {
|
|
15
|
-
const imports = [];
|
|
16
|
-
const importRegex = /import\s+.*?\s+from\s+['"]([^'"]+\.(?:contracts|event|presentation|workflow|data-view|migration|telemetry|experiment|app-config|integration|knowledge)(?:\.[jt]s)?)['"]/g;
|
|
17
|
-
let match;
|
|
18
|
-
while ((match = importRegex.exec(sourceCode)) !== null) {
|
|
19
|
-
const importPath = match[1];
|
|
20
|
-
if (!importPath) continue;
|
|
21
|
-
if (!importPath.startsWith(".") && !importPath.startsWith("/")) continue;
|
|
22
|
-
const pathParts = importPath.split("/");
|
|
23
|
-
const name = (pathParts[pathParts.length - 1] ?? "").replace(/\.(ts|js)$/, "").replace(/\.(contracts|event|presentation|workflow|data-view|migration|telemetry|experiment|app-config|integration|knowledge)$/, "");
|
|
24
|
-
if (name.length > 0) imports.push(name);
|
|
25
|
-
}
|
|
26
|
-
return Array.from(new Set(imports)).sort((a, b) => a.localeCompare(b));
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
//#endregion
|
|
30
|
-
export { parseImportedSpecNames };
|
|
1
|
+
function e(e,t){let n=[],r=/import\s+.*?\s+from\s+['"]([^'"]+\.(?:contracts|event|presentation|workflow|data-view|migration|telemetry|experiment|app-config|integration|knowledge)(?:\.[jt]s)?)['"]/g,i;for(;(i=r.exec(e))!==null;){let e=i[1];if(!e||!e.startsWith(`.`)&&!e.startsWith(`/`))continue;let t=e.split(`/`),r=(t[t.length-1]??``).replace(/\.(ts|js)$/,``).replace(/\.(contracts|event|presentation|workflow|data-view|migration|telemetry|experiment|app-config|integration|knowledge)$/,``);r.length>0&&n.push(r)}return Array.from(new Set(n)).sort((e,t)=>e.localeCompare(t))}export{e as parseImportedSpecNames};
|
|
@@ -1,96 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
//#region src/analysis/diff/semantic.ts
|
|
4
|
-
/**
|
|
5
|
-
* Compute semantic differences between two spec sources.
|
|
6
|
-
*/
|
|
7
|
-
function computeSemanticDiff(aCode, aPath, bCode, bPath, options = {}) {
|
|
8
|
-
const a = scanSpecSource(aCode, aPath);
|
|
9
|
-
const b = scanSpecSource(bCode, bPath);
|
|
10
|
-
const diffs = [];
|
|
11
|
-
compareScalar(diffs, "specType", a.specType, b.specType, {
|
|
12
|
-
breaking: true,
|
|
13
|
-
label: "Spec type"
|
|
14
|
-
});
|
|
15
|
-
compareScalar(diffs, "name", a.name, b.name, {
|
|
16
|
-
breaking: true,
|
|
17
|
-
label: "Name"
|
|
18
|
-
});
|
|
19
|
-
compareScalar(diffs, "version", a.version, b.version, {
|
|
20
|
-
breaking: true,
|
|
21
|
-
label: "Version"
|
|
22
|
-
});
|
|
23
|
-
compareScalar(diffs, "kind", a.kind, b.kind, {
|
|
24
|
-
breaking: true,
|
|
25
|
-
label: "Kind"
|
|
26
|
-
});
|
|
27
|
-
compareScalar(diffs, "stability", a.stability, b.stability, {
|
|
28
|
-
breaking: isStabilityDowngrade(a, b),
|
|
29
|
-
label: "Stability"
|
|
30
|
-
});
|
|
31
|
-
compareArray(diffs, "owners", a.owners ?? [], b.owners ?? [], { label: "Owners" });
|
|
32
|
-
compareArray(diffs, "tags", a.tags ?? [], b.tags ?? [], { label: "Tags" });
|
|
33
|
-
compareStructuralHints(diffs, a, b);
|
|
34
|
-
return options.breakingOnly ? diffs.filter((d) => d.type === "breaking") : diffs;
|
|
35
|
-
}
|
|
36
|
-
function compareScalar(diffs, path, a, b, config) {
|
|
37
|
-
if (a === b) return;
|
|
38
|
-
diffs.push({
|
|
39
|
-
type: config.breaking ? "breaking" : "changed",
|
|
40
|
-
path,
|
|
41
|
-
oldValue: a,
|
|
42
|
-
newValue: b,
|
|
43
|
-
description: `${config.label} changed`
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
function compareArray(diffs, path, a, b, config) {
|
|
47
|
-
const aSorted = [...a].sort();
|
|
48
|
-
const bSorted = [...b].sort();
|
|
49
|
-
if (JSON.stringify(aSorted) === JSON.stringify(bSorted)) return;
|
|
50
|
-
diffs.push({
|
|
51
|
-
type: "changed",
|
|
52
|
-
path,
|
|
53
|
-
oldValue: aSorted,
|
|
54
|
-
newValue: bSorted,
|
|
55
|
-
description: `${config.label} changed`
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
function isStabilityDowngrade(a, b) {
|
|
59
|
-
const order = {
|
|
60
|
-
experimental: 0,
|
|
61
|
-
beta: 1,
|
|
62
|
-
stable: 2,
|
|
63
|
-
deprecated: 3
|
|
64
|
-
};
|
|
65
|
-
const aValue = a.stability ? order[a.stability] ?? 0 : 0;
|
|
66
|
-
return (b.stability ? order[b.stability] ?? 0 : 0) > aValue;
|
|
67
|
-
}
|
|
68
|
-
function compareStructuralHints(diffs, a, b) {
|
|
69
|
-
compareScalar(diffs, "hasMeta", a.hasMeta, b.hasMeta, {
|
|
70
|
-
breaking: a.specType === "operation" || b.specType === "operation",
|
|
71
|
-
label: "meta section presence"
|
|
72
|
-
});
|
|
73
|
-
compareScalar(diffs, "hasIo", a.hasIo, b.hasIo, {
|
|
74
|
-
breaking: a.specType === "operation" || b.specType === "operation",
|
|
75
|
-
label: "io section presence"
|
|
76
|
-
});
|
|
77
|
-
compareScalar(diffs, "hasPolicy", a.hasPolicy, b.hasPolicy, {
|
|
78
|
-
breaking: a.specType === "operation" || b.specType === "operation",
|
|
79
|
-
label: "policy section presence"
|
|
80
|
-
});
|
|
81
|
-
compareScalar(diffs, "hasPayload", a.hasPayload, b.hasPayload, {
|
|
82
|
-
breaking: a.specType === "event" || b.specType === "event",
|
|
83
|
-
label: "payload section presence"
|
|
84
|
-
});
|
|
85
|
-
compareScalar(diffs, "hasContent", a.hasContent, b.hasContent, {
|
|
86
|
-
breaking: a.specType === "presentation" || b.specType === "presentation",
|
|
87
|
-
label: "content section presence"
|
|
88
|
-
});
|
|
89
|
-
compareScalar(diffs, "hasDefinition", a.hasDefinition, b.hasDefinition, {
|
|
90
|
-
breaking: a.specType === "workflow" || b.specType === "workflow",
|
|
91
|
-
label: "definition section presence"
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
//#endregion
|
|
96
|
-
export { computeSemanticDiff };
|
|
1
|
+
import{scanSpecSource as e}from"../spec-scan.js";function t(t,o,s,c,l={}){let u=e(t,o),d=e(s,c),f=[];return n(f,`specType`,u.specType,d.specType,{breaking:!0,label:`Spec type`}),n(f,`name`,u.name,d.name,{breaking:!0,label:`Name`}),n(f,`version`,u.version,d.version,{breaking:!0,label:`Version`}),n(f,`kind`,u.kind,d.kind,{breaking:!0,label:`Kind`}),n(f,`stability`,u.stability,d.stability,{breaking:i(u,d),label:`Stability`}),r(f,`owners`,u.owners??[],d.owners??[],{label:`Owners`}),r(f,`tags`,u.tags??[],d.tags??[],{label:`Tags`}),a(f,u,d),l.breakingOnly?f.filter(e=>e.type===`breaking`):f}function n(e,t,n,r,i){n!==r&&e.push({type:i.breaking?`breaking`:`changed`,path:t,oldValue:n,newValue:r,description:`${i.label} changed`})}function r(e,t,n,r,i){let a=[...n].sort(),o=[...r].sort();JSON.stringify(a)!==JSON.stringify(o)&&e.push({type:`changed`,path:t,oldValue:a,newValue:o,description:`${i.label} changed`})}function i(e,t){let n={experimental:0,beta:1,stable:2,deprecated:3},r=e.stability?n[e.stability]??0:0;return(t.stability?n[t.stability]??0:0)>r}function a(e,t,r){n(e,`hasMeta`,t.hasMeta,r.hasMeta,{breaking:t.specType===`operation`||r.specType===`operation`,label:`meta section presence`}),n(e,`hasIo`,t.hasIo,r.hasIo,{breaking:t.specType===`operation`||r.specType===`operation`,label:`io section presence`}),n(e,`hasPolicy`,t.hasPolicy,r.hasPolicy,{breaking:t.specType===`operation`||r.specType===`operation`,label:`policy section presence`}),n(e,`hasPayload`,t.hasPayload,r.hasPayload,{breaking:t.specType===`event`||r.specType===`event`,label:`payload section presence`}),n(e,`hasContent`,t.hasContent,r.hasContent,{breaking:t.specType===`presentation`||r.specType===`presentation`,label:`content section presence`}),n(e,`hasDefinition`,t.hasDefinition,r.hasDefinition,{breaking:t.specType===`workflow`||r.specType===`workflow`,label:`definition section presence`})}export{t as computeSemanticDiff};
|
|
@@ -1,151 +1 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* Check if a file is a feature file based on naming conventions.
|
|
4
|
-
*/
|
|
5
|
-
function isFeatureFile(filePath) {
|
|
6
|
-
return filePath.includes(".feature.");
|
|
7
|
-
}
|
|
8
|
-
/**
|
|
9
|
-
* Scan a feature source file to extract metadata.
|
|
10
|
-
*/
|
|
11
|
-
function scanFeatureSource(code, filePath) {
|
|
12
|
-
const key = matchStringField(code, "key") ?? extractKeyFromFilePath(filePath);
|
|
13
|
-
const title = matchStringField(code, "title") ?? void 0;
|
|
14
|
-
const description = matchStringField(code, "description") ?? void 0;
|
|
15
|
-
const domain = matchStringField(code, "domain") ?? void 0;
|
|
16
|
-
const stabilityRaw = matchStringField(code, "stability");
|
|
17
|
-
return {
|
|
18
|
-
filePath,
|
|
19
|
-
key,
|
|
20
|
-
title,
|
|
21
|
-
description,
|
|
22
|
-
domain,
|
|
23
|
-
stability: isStability(stabilityRaw) ? stabilityRaw : void 0,
|
|
24
|
-
owners: matchStringArrayField(code, "owners"),
|
|
25
|
-
tags: matchStringArrayField(code, "tags"),
|
|
26
|
-
operations: extractRefsFromArray(code, "operations"),
|
|
27
|
-
events: extractRefsFromArray(code, "events"),
|
|
28
|
-
presentations: extractRefsFromArray(code, "presentations"),
|
|
29
|
-
experiments: extractRefsFromArray(code, "experiments"),
|
|
30
|
-
capabilities: extractCapabilities(code),
|
|
31
|
-
opToPresentationLinks: extractOpToPresentationLinks(code)
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Extract refs from a named array (e.g., operations, events, presentations).
|
|
36
|
-
*/
|
|
37
|
-
function extractRefsFromArray(code, fieldName) {
|
|
38
|
-
const refs = [];
|
|
39
|
-
const arrayPattern = new RegExp(`${escapeRegex(fieldName)}\\s*:\\s*\\[([\\s\\S]*?)\\]`, "m");
|
|
40
|
-
const arrayMatch = code.match(arrayPattern);
|
|
41
|
-
if (!arrayMatch?.[1]) return refs;
|
|
42
|
-
const refPattern = /\{\s*name:\s*['"]([^'"]+)['"]\s*,\s*version:\s*(\d+)/g;
|
|
43
|
-
let match;
|
|
44
|
-
while ((match = refPattern.exec(arrayMatch[1])) !== null) if (match[1] && match[2]) refs.push({
|
|
45
|
-
name: match[1],
|
|
46
|
-
version: Number(match[2])
|
|
47
|
-
});
|
|
48
|
-
return refs;
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Extract capability bindings (provides and requires).
|
|
52
|
-
*/
|
|
53
|
-
function extractCapabilities(code) {
|
|
54
|
-
const provides = [];
|
|
55
|
-
const requires = [];
|
|
56
|
-
const capabilitiesMatch = code.match(/capabilities\s*:\s*\{([\s\S]*?)\}/);
|
|
57
|
-
if (!capabilitiesMatch?.[1]) return {
|
|
58
|
-
provides,
|
|
59
|
-
requires
|
|
60
|
-
};
|
|
61
|
-
const capabilitiesContent = capabilitiesMatch[1];
|
|
62
|
-
const providesMatch = capabilitiesContent.match(/provides\s*:\s*\[([\s\S]*?)\]/);
|
|
63
|
-
if (providesMatch?.[1]) {
|
|
64
|
-
const refPattern = /\{\s*key:\s*['"]([^'"]+)['"]\s*,\s*version:\s*(\d+)/g;
|
|
65
|
-
let match;
|
|
66
|
-
while ((match = refPattern.exec(providesMatch[1])) !== null) if (match[1] && match[2]) provides.push({
|
|
67
|
-
name: match[1],
|
|
68
|
-
version: Number(match[2])
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
const requiresMatch = capabilitiesContent.match(/requires\s*:\s*\[([\s\S]*?)\]/);
|
|
72
|
-
if (requiresMatch?.[1]) {
|
|
73
|
-
const refPatternWithVersion = /\{\s*key:\s*['"]([^'"]+)['"]\s*,\s*version:\s*(\d+)/g;
|
|
74
|
-
const refPatternKeyOnly = /\{\s*key:\s*['"]([^'"]+)['"]\s*\}/g;
|
|
75
|
-
let match = null;
|
|
76
|
-
while ((match = refPatternWithVersion.exec(requiresMatch[1])) !== null) if (match[1] && match[2]) requires.push({
|
|
77
|
-
name: match[1],
|
|
78
|
-
version: Number(match[2])
|
|
79
|
-
});
|
|
80
|
-
while ((match = refPatternKeyOnly.exec(requiresMatch[1])) !== null) if (match && match[1]) {
|
|
81
|
-
if (!requires.some((r) => r.name === match[1])) requires.push({
|
|
82
|
-
name: match[1],
|
|
83
|
-
version: 1
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
return {
|
|
88
|
-
provides,
|
|
89
|
-
requires
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Extract opToPresentation links.
|
|
94
|
-
*/
|
|
95
|
-
function extractOpToPresentationLinks(code) {
|
|
96
|
-
const links = [];
|
|
97
|
-
const arrayMatch = code.match(/opToPresentation\s*:\s*\[([\s\S]*?)\]/);
|
|
98
|
-
if (!arrayMatch?.[1]) return links;
|
|
99
|
-
const linkPattern = /\{\s*op:\s*\{\s*name:\s*['"]([^'"]+)['"]\s*,\s*version:\s*(\d+)\s*\}\s*,\s*pres:\s*\{\s*name:\s*['"]([^'"]+)['"]\s*,\s*version:\s*(\d+)\s*\}/g;
|
|
100
|
-
let match;
|
|
101
|
-
while ((match = linkPattern.exec(arrayMatch[1])) !== null) if (match[1] && match[2] && match[3] && match[4]) links.push({
|
|
102
|
-
op: {
|
|
103
|
-
name: match[1],
|
|
104
|
-
version: Number(match[2])
|
|
105
|
-
},
|
|
106
|
-
pres: {
|
|
107
|
-
name: match[3],
|
|
108
|
-
version: Number(match[4])
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
|
-
return links;
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Extract key from file path as fallback.
|
|
115
|
-
*/
|
|
116
|
-
function extractKeyFromFilePath(filePath) {
|
|
117
|
-
return (filePath.split("/").pop() ?? filePath).replace(/\.feature\.[jt]s$/, "").replace(/[^a-zA-Z0-9-]/g, "-");
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* Match a string field in source code.
|
|
121
|
-
*/
|
|
122
|
-
function matchStringField(code, field) {
|
|
123
|
-
const regex = /* @__PURE__ */ new RegExp(`${escapeRegex(field)}\\s*:\\s*['"]([^'"]+)['"]`);
|
|
124
|
-
return code.match(regex)?.[1] ?? null;
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* Match a string array field in source code.
|
|
128
|
-
*/
|
|
129
|
-
function matchStringArrayField(code, field) {
|
|
130
|
-
const regex = /* @__PURE__ */ new RegExp(`${escapeRegex(field)}\\s*:\\s*\\[([\\s\\S]*?)\\]`);
|
|
131
|
-
const match = code.match(regex);
|
|
132
|
-
if (!match?.[1]) return void 0;
|
|
133
|
-
const inner = match[1];
|
|
134
|
-
const items = Array.from(inner.matchAll(/['"]([^'"]+)['"]/g)).map((m) => m[1]).filter((value) => typeof value === "string" && value.length > 0);
|
|
135
|
-
return items.length > 0 ? items : void 0;
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* Check if a value is a valid stability.
|
|
139
|
-
*/
|
|
140
|
-
function isStability(value) {
|
|
141
|
-
return value === "experimental" || value === "beta" || value === "stable" || value === "deprecated";
|
|
142
|
-
}
|
|
143
|
-
/**
|
|
144
|
-
* Escape regex special characters.
|
|
145
|
-
*/
|
|
146
|
-
function escapeRegex(value) {
|
|
147
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
//#endregion
|
|
151
|
-
export { isFeatureFile, scanFeatureSource };
|
|
1
|
+
function e(e){return e.includes(`.feature.`)}function t(e,t){let l=o(e,`key`)??a(t),u=o(e,`title`)??void 0,d=o(e,`description`)??void 0,f=o(e,`domain`)??void 0,p=o(e,`stability`);return{filePath:t,key:l,title:u,description:d,domain:f,stability:c(p)?p:void 0,owners:s(e,`owners`),tags:s(e,`tags`),operations:n(e,`operations`),events:n(e,`events`),presentations:n(e,`presentations`),experiments:n(e,`experiments`),capabilities:r(e),opToPresentationLinks:i(e)}}function n(e,t){let n=[],r=RegExp(`${l(t)}\\s*:\\s*\\[([\\s\\S]*?)\\]`,`m`),i=e.match(r);if(!i?.[1])return n;let a=/\{\s*name:\s*['"]([^'"]+)['"]\s*,\s*version:\s*(\d+)/g,o;for(;(o=a.exec(i[1]))!==null;)o[1]&&o[2]&&n.push({name:o[1],version:Number(o[2])});return n}function r(e){let t=[],n=[],r=e.match(/capabilities\s*:\s*\{([\s\S]*?)\}/);if(!r?.[1])return{provides:t,requires:n};let i=r[1],a=i.match(/provides\s*:\s*\[([\s\S]*?)\]/);if(a?.[1]){let e=/\{\s*key:\s*['"]([^'"]+)['"]\s*,\s*version:\s*(\d+)/g,n;for(;(n=e.exec(a[1]))!==null;)n[1]&&n[2]&&t.push({name:n[1],version:Number(n[2])})}let o=i.match(/requires\s*:\s*\[([\s\S]*?)\]/);if(o?.[1]){let e=/\{\s*key:\s*['"]([^'"]+)['"]\s*,\s*version:\s*(\d+)/g,t=/\{\s*key:\s*['"]([^'"]+)['"]\s*\}/g,r=null;for(;(r=e.exec(o[1]))!==null;)r[1]&&r[2]&&n.push({name:r[1],version:Number(r[2])});for(;(r=t.exec(o[1]))!==null;)r&&r[1]&&(n.some(e=>e.name===r[1])||n.push({name:r[1],version:1}))}return{provides:t,requires:n}}function i(e){let t=[],n=e.match(/opToPresentation\s*:\s*\[([\s\S]*?)\]/);if(!n?.[1])return t;let r=/\{\s*op:\s*\{\s*name:\s*['"]([^'"]+)['"]\s*,\s*version:\s*(\d+)\s*\}\s*,\s*pres:\s*\{\s*name:\s*['"]([^'"]+)['"]\s*,\s*version:\s*(\d+)\s*\}/g,i;for(;(i=r.exec(n[1]))!==null;)i[1]&&i[2]&&i[3]&&i[4]&&t.push({op:{name:i[1],version:Number(i[2])},pres:{name:i[3],version:Number(i[4])}});return t}function a(e){return(e.split(`/`).pop()??e).replace(/\.feature\.[jt]s$/,``).replace(/[^a-zA-Z0-9-]/g,`-`)}function o(e,t){let n=RegExp(`${l(t)}\\s*:\\s*['"]([^'"]+)['"]`);return e.match(n)?.[1]??null}function s(e,t){let n=RegExp(`${l(t)}\\s*:\\s*\\[([\\s\\S]*?)\\]`),r=e.match(n);if(!r?.[1])return;let i=r[1],a=Array.from(i.matchAll(/['"]([^'"]+)['"]/g)).map(e=>e[1]).filter(e=>typeof e==`string`&&e.length>0);return a.length>0?a:void 0}function c(e){return e===`experimental`||e===`beta`||e===`stable`||e===`deprecated`}function l(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}export{e as isFeatureFile,t as scanFeatureSource};
|