@contractual/cli 0.1.0-dev.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/LICENSE +21 -0
- package/bin/cli.js +2 -0
- package/dist/commands/breaking.command.d.ts +19 -0
- package/dist/commands/breaking.command.d.ts.map +1 -0
- package/dist/commands/breaking.command.js +138 -0
- package/dist/commands/changeset.command.d.ts +11 -0
- package/dist/commands/changeset.command.d.ts.map +1 -0
- package/dist/commands/changeset.command.js +65 -0
- package/dist/commands/contract.command.d.ts +34 -0
- package/dist/commands/contract.command.d.ts.map +1 -0
- package/dist/commands/contract.command.js +259 -0
- package/dist/commands/diff.command.d.ts +21 -0
- package/dist/commands/diff.command.d.ts.map +1 -0
- package/dist/commands/diff.command.js +58 -0
- package/dist/commands/index.d.ts +7 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +6 -0
- package/dist/commands/init.command.d.ts +21 -0
- package/dist/commands/init.command.d.ts.map +1 -0
- package/dist/commands/init.command.js +294 -0
- package/dist/commands/lint.command.d.ts +16 -0
- package/dist/commands/lint.command.d.ts.map +1 -0
- package/dist/commands/lint.command.js +174 -0
- package/dist/commands/pre.command.d.ts +14 -0
- package/dist/commands/pre.command.d.ts.map +1 -0
- package/dist/commands/pre.command.js +141 -0
- package/dist/commands/status.command.d.ts +5 -0
- package/dist/commands/status.command.d.ts.map +1 -0
- package/dist/commands/status.command.js +120 -0
- package/dist/commands/version.command.d.ts +16 -0
- package/dist/commands/version.command.d.ts.map +1 -0
- package/dist/commands/version.command.js +247 -0
- package/dist/commands.d.ts +2 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +84 -0
- package/dist/config/index.d.ts +3 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +2 -0
- package/dist/config/loader.d.ts +28 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +123 -0
- package/dist/config/schema.json +121 -0
- package/dist/config/validator.d.ts +28 -0
- package/dist/config/validator.d.ts.map +1 -0
- package/dist/config/validator.js +34 -0
- package/dist/core/diff.d.ts +26 -0
- package/dist/core/diff.d.ts.map +1 -0
- package/dist/core/diff.js +89 -0
- package/dist/formatters/diff.d.ts +31 -0
- package/dist/formatters/diff.d.ts.map +1 -0
- package/dist/formatters/diff.js +139 -0
- package/dist/governance/index.d.ts +11 -0
- package/dist/governance/index.d.ts.map +1 -0
- package/dist/governance/index.js +14 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/utils/exec.d.ts +29 -0
- package/dist/utils/exec.d.ts.map +1 -0
- package/dist/utils/exec.js +66 -0
- package/dist/utils/files.d.ts +36 -0
- package/dist/utils/files.d.ts.map +1 -0
- package/dist/utils/files.js +137 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/output.d.ts +25 -0
- package/dist/utils/output.d.ts.map +1 -0
- package/dist/utils/output.js +73 -0
- package/dist/utils/prompts.d.ts +90 -0
- package/dist/utils/prompts.d.ts.map +1 -0
- package/dist/utils/prompts.js +119 -0
- package/package.json +81 -0
- package/src/config/schema.json +121 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://contractual.dev/schemas/config.json",
|
|
4
|
+
"title": "Contractual Configuration",
|
|
5
|
+
"description": "Configuration file for Contractual schema contract lifecycle orchestrator",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["contracts"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"contracts": {
|
|
10
|
+
"type": "array",
|
|
11
|
+
"description": "List of contract definitions",
|
|
12
|
+
"minItems": 1,
|
|
13
|
+
"items": {
|
|
14
|
+
"type": "object",
|
|
15
|
+
"required": ["name", "type", "path"],
|
|
16
|
+
"properties": {
|
|
17
|
+
"name": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"description": "Unique identifier for the contract",
|
|
20
|
+
"pattern": "^[a-z][a-z0-9-]*$"
|
|
21
|
+
},
|
|
22
|
+
"type": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"description": "Type of schema",
|
|
25
|
+
"enum": ["openapi", "json-schema", "asyncapi", "odcs"]
|
|
26
|
+
},
|
|
27
|
+
"path": {
|
|
28
|
+
"type": "string",
|
|
29
|
+
"description": "Path to spec file (can be glob pattern)"
|
|
30
|
+
},
|
|
31
|
+
"lint": {
|
|
32
|
+
"oneOf": [
|
|
33
|
+
{ "type": "string", "description": "Linter tool name or custom command" },
|
|
34
|
+
{ "type": "boolean", "const": false, "description": "Disable linting" }
|
|
35
|
+
]
|
|
36
|
+
},
|
|
37
|
+
"breaking": {
|
|
38
|
+
"oneOf": [
|
|
39
|
+
{ "type": "string", "description": "Differ tool name or custom command" },
|
|
40
|
+
{ "type": "boolean", "const": false, "description": "Disable breaking change detection" }
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
"generate": {
|
|
44
|
+
"type": "array",
|
|
45
|
+
"description": "Output generation commands",
|
|
46
|
+
"items": { "type": "string" }
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"additionalProperties": false
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"versioning": {
|
|
53
|
+
"type": "object",
|
|
54
|
+
"description": "Versioning configuration",
|
|
55
|
+
"properties": {
|
|
56
|
+
"mode": {
|
|
57
|
+
"type": "string",
|
|
58
|
+
"enum": ["independent", "fixed"],
|
|
59
|
+
"description": "Versioning mode: independent (each contract separate) or fixed (all share same version)",
|
|
60
|
+
"default": "independent"
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"additionalProperties": false
|
|
64
|
+
},
|
|
65
|
+
"changeset": {
|
|
66
|
+
"type": "object",
|
|
67
|
+
"description": "Changeset behavior configuration",
|
|
68
|
+
"properties": {
|
|
69
|
+
"autoDetect": {
|
|
70
|
+
"type": "boolean",
|
|
71
|
+
"description": "Auto-detect change classifications",
|
|
72
|
+
"default": true
|
|
73
|
+
},
|
|
74
|
+
"requireOnPR": {
|
|
75
|
+
"type": "boolean",
|
|
76
|
+
"description": "Require changeset for spec changes in PRs",
|
|
77
|
+
"default": true
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
"additionalProperties": false
|
|
81
|
+
},
|
|
82
|
+
"ai": {
|
|
83
|
+
"type": "object",
|
|
84
|
+
"description": "AI/LLM integration configuration",
|
|
85
|
+
"properties": {
|
|
86
|
+
"provider": {
|
|
87
|
+
"type": "string",
|
|
88
|
+
"enum": ["anthropic"],
|
|
89
|
+
"default": "anthropic"
|
|
90
|
+
},
|
|
91
|
+
"model": {
|
|
92
|
+
"type": "string",
|
|
93
|
+
"description": "Model to use"
|
|
94
|
+
},
|
|
95
|
+
"features": {
|
|
96
|
+
"type": "object",
|
|
97
|
+
"properties": {
|
|
98
|
+
"explain": {
|
|
99
|
+
"type": "boolean",
|
|
100
|
+
"description": "PR change explanations",
|
|
101
|
+
"default": true
|
|
102
|
+
},
|
|
103
|
+
"changelog": {
|
|
104
|
+
"type": "boolean",
|
|
105
|
+
"description": "AI-enriched changelog entries",
|
|
106
|
+
"default": true
|
|
107
|
+
},
|
|
108
|
+
"enhance": {
|
|
109
|
+
"type": "boolean",
|
|
110
|
+
"description": "Spec metadata enhancement",
|
|
111
|
+
"default": false
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
"additionalProperties": false
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
"additionalProperties": false
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
"additionalProperties": false
|
|
121
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ContractualConfig } from '@contractual/types';
|
|
2
|
+
/**
|
|
3
|
+
* Validation error with path and message
|
|
4
|
+
*/
|
|
5
|
+
export interface ValidationError {
|
|
6
|
+
path: string;
|
|
7
|
+
message: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Result of config validation
|
|
11
|
+
*/
|
|
12
|
+
export interface ValidationResult {
|
|
13
|
+
valid: boolean;
|
|
14
|
+
errors: ValidationError[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Validates a parsed config object against the JSON schema
|
|
18
|
+
*/
|
|
19
|
+
export declare function validateConfig(config: unknown): ValidationResult;
|
|
20
|
+
/**
|
|
21
|
+
* Type guard to check if config is valid ContractualConfig
|
|
22
|
+
*/
|
|
23
|
+
export declare function isValidConfig(config: unknown): config is ContractualConfig;
|
|
24
|
+
/**
|
|
25
|
+
* Format validation errors for display
|
|
26
|
+
*/
|
|
27
|
+
export declare function formatValidationErrors(errors: ValidationError[]): string;
|
|
28
|
+
//# sourceMappingURL=validator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../src/config/validator.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAG5D;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,eAAe,EAAE,CAAC;CAC3B;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,OAAO,GAAG,gBAAgB,CAiBhE;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,IAAI,iBAAiB,CAE1E;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM,CAExE"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import Ajv from 'ajv';
|
|
2
|
+
import addFormats from 'ajv-formats';
|
|
3
|
+
const AjvConstructor = Ajv.default ?? Ajv;
|
|
4
|
+
const addFormatsFunc = addFormats.default ?? addFormats;
|
|
5
|
+
import configSchema from './schema.json' with { type: 'json' };
|
|
6
|
+
/**
|
|
7
|
+
* Validates a parsed config object against the JSON schema
|
|
8
|
+
*/
|
|
9
|
+
export function validateConfig(config) {
|
|
10
|
+
const ajv = new AjvConstructor({ allErrors: true, strict: false });
|
|
11
|
+
addFormatsFunc(ajv);
|
|
12
|
+
const validate = ajv.compile(configSchema);
|
|
13
|
+
const valid = validate(config);
|
|
14
|
+
if (valid) {
|
|
15
|
+
return { valid: true, errors: [] };
|
|
16
|
+
}
|
|
17
|
+
const errors = (validate.errors ?? []).map((err) => ({
|
|
18
|
+
path: err.instancePath || '/',
|
|
19
|
+
message: err.message ?? 'Unknown validation error',
|
|
20
|
+
}));
|
|
21
|
+
return { valid: false, errors };
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Type guard to check if config is valid ContractualConfig
|
|
25
|
+
*/
|
|
26
|
+
export function isValidConfig(config) {
|
|
27
|
+
return validateConfig(config).valid;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Format validation errors for display
|
|
31
|
+
*/
|
|
32
|
+
export function formatValidationErrors(errors) {
|
|
33
|
+
return errors.map((err) => ` ${err.path}: ${err.message}`).join('\n');
|
|
34
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core diffing logic
|
|
3
|
+
*
|
|
4
|
+
* Shared module for running diffs across contracts.
|
|
5
|
+
* Used by `diff`, `breaking`, and `changeset` commands.
|
|
6
|
+
*/
|
|
7
|
+
import type { ResolvedConfig, DiffResult } from '@contractual/types';
|
|
8
|
+
export interface DiffOptions {
|
|
9
|
+
/** Filter to specific contract names */
|
|
10
|
+
contracts?: string[];
|
|
11
|
+
/** Include contracts with no changes in results */
|
|
12
|
+
includeEmpty?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface DiffContractsResult {
|
|
15
|
+
results: DiffResult[];
|
|
16
|
+
contractualDir: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Core diffing function. Runs the appropriate differ for each contract,
|
|
20
|
+
* comparing current spec against last versioned snapshot.
|
|
21
|
+
*
|
|
22
|
+
* Returns classified changes for all contracts.
|
|
23
|
+
* This is the primitive that `diff`, `breaking`, and `changeset` all use.
|
|
24
|
+
*/
|
|
25
|
+
export declare function diffContracts(config: ResolvedConfig, options?: DiffOptions): Promise<DiffContractsResult>;
|
|
26
|
+
//# sourceMappingURL=diff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../src/core/diff.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAoB,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAIvF,MAAM,WAAW,WAAW;IAC1B,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,mDAAmD;IACnD,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;GAMG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,cAAc,EACtB,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC,mBAAmB,CAAC,CA+B9B"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core diffing logic
|
|
3
|
+
*
|
|
4
|
+
* Shared module for running diffs across contracts.
|
|
5
|
+
* Used by `diff`, `breaking`, and `changeset` commands.
|
|
6
|
+
*/
|
|
7
|
+
import { getDiffer } from '../governance/index.js';
|
|
8
|
+
import { findContractualDir, getSnapshotPath } from '../utils/files.js';
|
|
9
|
+
/**
|
|
10
|
+
* Core diffing function. Runs the appropriate differ for each contract,
|
|
11
|
+
* comparing current spec against last versioned snapshot.
|
|
12
|
+
*
|
|
13
|
+
* Returns classified changes for all contracts.
|
|
14
|
+
* This is the primitive that `diff`, `breaking`, and `changeset` all use.
|
|
15
|
+
*/
|
|
16
|
+
export async function diffContracts(config, options = {}) {
|
|
17
|
+
const contractualDir = findContractualDir(config.configDir);
|
|
18
|
+
if (!contractualDir) {
|
|
19
|
+
throw new Error('No .contractual directory found. Run `contractual init` first.');
|
|
20
|
+
}
|
|
21
|
+
// Filter contracts if specific ones requested
|
|
22
|
+
let contracts = config.contracts;
|
|
23
|
+
if (options.contracts && options.contracts.length > 0) {
|
|
24
|
+
contracts = config.contracts.filter((c) => options.contracts.includes(c.name));
|
|
25
|
+
// Check for missing contracts
|
|
26
|
+
for (const name of options.contracts) {
|
|
27
|
+
if (!config.contracts.some((c) => c.name === name)) {
|
|
28
|
+
throw new Error(`Contract "${name}" not found in configuration.`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const results = [];
|
|
33
|
+
for (const contract of contracts) {
|
|
34
|
+
const result = await diffSingleContract(contract, contractualDir);
|
|
35
|
+
// Include result if it has changes, or if includeEmpty is true
|
|
36
|
+
if (result.changes.length > 0 || options.includeEmpty) {
|
|
37
|
+
results.push(result);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return { results, contractualDir };
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Diff a single contract against its snapshot
|
|
44
|
+
*/
|
|
45
|
+
async function diffSingleContract(contract, contractualDir) {
|
|
46
|
+
// Get snapshot path
|
|
47
|
+
const snapshotPath = getSnapshotPath(contract.name, contractualDir);
|
|
48
|
+
// No snapshot = first version, no changes
|
|
49
|
+
if (!snapshotPath) {
|
|
50
|
+
return createEmptyResult(contract.name, 'first-version');
|
|
51
|
+
}
|
|
52
|
+
// Check if breaking detection is disabled
|
|
53
|
+
if (contract.breaking === false) {
|
|
54
|
+
return createEmptyResult(contract.name, 'disabled');
|
|
55
|
+
}
|
|
56
|
+
// Get the differ from the registry
|
|
57
|
+
const differ = getDiffer(contract.type, contract.breaking);
|
|
58
|
+
if (differ === null) {
|
|
59
|
+
// Disabled via config override
|
|
60
|
+
return createEmptyResult(contract.name, 'disabled');
|
|
61
|
+
}
|
|
62
|
+
if (!differ) {
|
|
63
|
+
// No differ registered for this type
|
|
64
|
+
return createEmptyResult(contract.name, 'no-differ');
|
|
65
|
+
}
|
|
66
|
+
// Run the differ: snapshot (old) vs current spec (new)
|
|
67
|
+
const diffResult = await differ(snapshotPath, contract.absolutePath);
|
|
68
|
+
// Override contract name to match config
|
|
69
|
+
return {
|
|
70
|
+
...diffResult,
|
|
71
|
+
contract: contract.name,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Create an empty diff result
|
|
76
|
+
*/
|
|
77
|
+
function createEmptyResult(contractName, _reason) {
|
|
78
|
+
return {
|
|
79
|
+
contract: contractName,
|
|
80
|
+
changes: [],
|
|
81
|
+
summary: {
|
|
82
|
+
breaking: 0,
|
|
83
|
+
nonBreaking: 0,
|
|
84
|
+
patch: 0,
|
|
85
|
+
unknown: 0,
|
|
86
|
+
},
|
|
87
|
+
suggestedBump: 'none',
|
|
88
|
+
};
|
|
89
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diff output formatters
|
|
3
|
+
*
|
|
4
|
+
* Format diff results for text and JSON output.
|
|
5
|
+
*/
|
|
6
|
+
import type { DiffResult } from '@contractual/types';
|
|
7
|
+
export interface FormatOptions {
|
|
8
|
+
/** Show JSON Pointer paths for each change */
|
|
9
|
+
verbose?: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Format diff results as human-readable text
|
|
13
|
+
*/
|
|
14
|
+
export declare function formatDiffText(results: DiffResult[], options?: FormatOptions): void;
|
|
15
|
+
/**
|
|
16
|
+
* Format diff results as JSON
|
|
17
|
+
*
|
|
18
|
+
* Output format:
|
|
19
|
+
* {
|
|
20
|
+
* "contracts": {
|
|
21
|
+
* "order": { "changes": [...], "summary": {...}, "suggestedBump": "minor" },
|
|
22
|
+
* "petstore": { "changes": [...], "summary": {...}, "suggestedBump": "none" }
|
|
23
|
+
* }
|
|
24
|
+
* }
|
|
25
|
+
*/
|
|
26
|
+
export declare function formatDiffJson(results: DiffResult[]): string;
|
|
27
|
+
/**
|
|
28
|
+
* Filter results by severity level
|
|
29
|
+
*/
|
|
30
|
+
export declare function filterBySeverity(results: DiffResult[], severity: string): DiffResult[];
|
|
31
|
+
//# sourceMappingURL=diff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../src/formatters/diff.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAuC,MAAM,oBAAoB,CAAC;AAE1F,MAAM,WAAW,aAAa;IAC5B,8CAA8C;IAC9C,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,UAAU,EAAE,EAAE,OAAO,GAAE,aAAkB,GAAG,IAAI,CASvF;AAsED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,MAAM,CAS5D;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,UAAU,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU,EAAE,CActF"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diff output formatters
|
|
3
|
+
*
|
|
4
|
+
* Format diff results for text and JSON output.
|
|
5
|
+
*/
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
/**
|
|
8
|
+
* Format diff results as human-readable text
|
|
9
|
+
*/
|
|
10
|
+
export function formatDiffText(results, options = {}) {
|
|
11
|
+
if (results.length === 0) {
|
|
12
|
+
console.log(chalk.dim('No contracts to diff.'));
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
for (const result of results) {
|
|
16
|
+
formatContractResult(result, options);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Format a single contract's diff result
|
|
21
|
+
*/
|
|
22
|
+
function formatContractResult(result, options) {
|
|
23
|
+
if (result.changes.length === 0) {
|
|
24
|
+
console.log(`${chalk.cyan(result.contract)}: ${chalk.dim('no changes')}`);
|
|
25
|
+
console.log();
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
// Build summary parts
|
|
29
|
+
const parts = [];
|
|
30
|
+
if (result.summary.breaking > 0) {
|
|
31
|
+
parts.push(`${result.summary.breaking} breaking`);
|
|
32
|
+
}
|
|
33
|
+
if (result.summary.nonBreaking > 0) {
|
|
34
|
+
parts.push(`${result.summary.nonBreaking} non-breaking`);
|
|
35
|
+
}
|
|
36
|
+
if (result.summary.patch > 0) {
|
|
37
|
+
parts.push(`${result.summary.patch} patch`);
|
|
38
|
+
}
|
|
39
|
+
if (result.summary.unknown > 0) {
|
|
40
|
+
parts.push(`${result.summary.unknown} unknown`);
|
|
41
|
+
}
|
|
42
|
+
// Color for suggested bump
|
|
43
|
+
const bumpColor = result.suggestedBump === 'major'
|
|
44
|
+
? chalk.red
|
|
45
|
+
: result.suggestedBump === 'minor'
|
|
46
|
+
? chalk.yellow
|
|
47
|
+
: chalk.green;
|
|
48
|
+
// Header line
|
|
49
|
+
console.log(`${chalk.cyan(result.contract)}: ${result.changes.length} change(s) (${parts.join(', ')}) — suggested bump: ${bumpColor(result.suggestedBump)}`);
|
|
50
|
+
console.log();
|
|
51
|
+
// Each change
|
|
52
|
+
for (const change of result.changes) {
|
|
53
|
+
const label = formatSeverityLabel(change.severity);
|
|
54
|
+
console.log(` ${label} ${change.message}`);
|
|
55
|
+
if (options.verbose && change.path) {
|
|
56
|
+
console.log(`${' '.repeat(14)}path: ${chalk.dim(change.path)}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
console.log();
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Format severity as a colored, padded label
|
|
63
|
+
*/
|
|
64
|
+
function formatSeverityLabel(severity) {
|
|
65
|
+
switch (severity) {
|
|
66
|
+
case 'breaking':
|
|
67
|
+
return chalk.red.bold('BREAKING'.padEnd(12));
|
|
68
|
+
case 'non-breaking':
|
|
69
|
+
return chalk.yellow('non-breaking');
|
|
70
|
+
case 'patch':
|
|
71
|
+
return chalk.green('patch'.padEnd(12));
|
|
72
|
+
case 'unknown':
|
|
73
|
+
return chalk.gray('unknown'.padEnd(12));
|
|
74
|
+
default:
|
|
75
|
+
return chalk.gray(String(severity).padEnd(12));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Format diff results as JSON
|
|
80
|
+
*
|
|
81
|
+
* Output format:
|
|
82
|
+
* {
|
|
83
|
+
* "contracts": {
|
|
84
|
+
* "order": { "changes": [...], "summary": {...}, "suggestedBump": "minor" },
|
|
85
|
+
* "petstore": { "changes": [...], "summary": {...}, "suggestedBump": "none" }
|
|
86
|
+
* }
|
|
87
|
+
* }
|
|
88
|
+
*/
|
|
89
|
+
export function formatDiffJson(results) {
|
|
90
|
+
const contracts = {};
|
|
91
|
+
for (const result of results) {
|
|
92
|
+
const { contract, ...rest } = result;
|
|
93
|
+
contracts[contract] = rest;
|
|
94
|
+
}
|
|
95
|
+
return JSON.stringify({ contracts }, null, 2);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Filter results by severity level
|
|
99
|
+
*/
|
|
100
|
+
export function filterBySeverity(results, severity) {
|
|
101
|
+
if (severity === 'all') {
|
|
102
|
+
return results;
|
|
103
|
+
}
|
|
104
|
+
return results.map((r) => {
|
|
105
|
+
const filteredChanges = r.changes.filter((c) => c.severity === severity);
|
|
106
|
+
return {
|
|
107
|
+
...r,
|
|
108
|
+
changes: filteredChanges,
|
|
109
|
+
summary: recalculateSummary(filteredChanges),
|
|
110
|
+
suggestedBump: calculateSuggestedBump(filteredChanges),
|
|
111
|
+
};
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Recalculate summary from filtered changes
|
|
116
|
+
*/
|
|
117
|
+
function recalculateSummary(changes) {
|
|
118
|
+
return {
|
|
119
|
+
breaking: changes.filter((c) => c.severity === 'breaking').length,
|
|
120
|
+
nonBreaking: changes.filter((c) => c.severity === 'non-breaking').length,
|
|
121
|
+
patch: changes.filter((c) => c.severity === 'patch').length,
|
|
122
|
+
unknown: changes.filter((c) => c.severity === 'unknown').length,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Calculate suggested bump from changes
|
|
127
|
+
*/
|
|
128
|
+
function calculateSuggestedBump(changes) {
|
|
129
|
+
if (changes.some((c) => c.severity === 'breaking')) {
|
|
130
|
+
return 'major';
|
|
131
|
+
}
|
|
132
|
+
if (changes.some((c) => c.severity === 'non-breaking')) {
|
|
133
|
+
return 'minor';
|
|
134
|
+
}
|
|
135
|
+
if (changes.some((c) => c.severity === 'patch')) {
|
|
136
|
+
return 'patch';
|
|
137
|
+
}
|
|
138
|
+
return 'none';
|
|
139
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-export governance engines from @contractual/governance
|
|
3
|
+
*
|
|
4
|
+
* This module re-exports the governance registry functions and auto-registers
|
|
5
|
+
* all built-in engines on import.
|
|
6
|
+
*/
|
|
7
|
+
import '@contractual/governance';
|
|
8
|
+
export { registerLinter, registerDiffer, getLinter, getDiffer, hasLinter, hasDiffer, getRegisteredLinterTypes, getRegisteredDifferTypes, } from '@contractual/governance';
|
|
9
|
+
export { lintOpenAPI, lintJsonSchema, diffOpenAPI, diffJsonSchema } from '@contractual/governance';
|
|
10
|
+
export { executeCustomCommand, parseCustomLintOutput, parseCustomDiffOutput, } from '@contractual/governance';
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/governance/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,yBAAyB,CAAC;AAGjC,OAAO,EACL,cAAc,EACd,cAAc,EACd,SAAS,EACT,SAAS,EACT,SAAS,EACT,SAAS,EACT,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAGnG,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,yBAAyB,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-export governance engines from @contractual/governance
|
|
3
|
+
*
|
|
4
|
+
* This module re-exports the governance registry functions and auto-registers
|
|
5
|
+
* all built-in engines on import.
|
|
6
|
+
*/
|
|
7
|
+
// Import to trigger auto-registration of all engines
|
|
8
|
+
import '@contractual/governance';
|
|
9
|
+
// Re-export registry functions
|
|
10
|
+
export { registerLinter, registerDiffer, getLinter, getDiffer, hasLinter, hasDiffer, getRegisteredLinterTypes, getRegisteredDifferTypes, } from '@contractual/governance';
|
|
11
|
+
// Re-export individual engines for direct use
|
|
12
|
+
export { lintOpenAPI, lintJsonSchema, diffOpenAPI, diffJsonSchema } from '@contractual/governance';
|
|
13
|
+
// Re-export runner utilities for custom commands
|
|
14
|
+
export { executeCustomCommand, parseCustomLintOutput, parseCustomDiffOutput, } from '@contractual/governance';
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { loadConfig, ConfigError } from './config/index.js';
|
|
2
|
+
export { registerLinter, registerDiffer, getLinter, getDiffer } from './governance/index.js';
|
|
3
|
+
export { diffContracts } from './core/diff.js';
|
|
4
|
+
export type { DiffOptions, DiffContractsResult } from './core/diff.js';
|
|
5
|
+
export { findContractualDir, getSnapshotPath, copyToSnapshots, CONTRACTUAL_DIR, } from './utils/files.js';
|
|
6
|
+
export { VersionManager, PreReleaseManager, createChangeset, readChangesets, aggregateBumps, generateChangesetName, extractContractChanges, appendChangelog, incrementVersionWithPreRelease, VERSIONS_FILE, SNAPSHOTS_DIR, CHANGESETS_DIR, PRE_RELEASE_FILE, } from '@contractual/changesets';
|
|
7
|
+
export type { BumpOperationResult } from '@contractual/changesets';
|
|
8
|
+
export type * from '@contractual/types';
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAG7F,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,YAAY,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAGvE,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,eAAe,EACf,eAAe,GAChB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,cAAc,EACd,qBAAqB,EACrB,sBAAsB,EACtB,eAAe,EACf,8BAA8B,EAC9B,aAAa,EACb,aAAa,EACb,cAAc,EACd,gBAAgB,GACjB,MAAM,yBAAyB,CAAC;AACjC,YAAY,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAGnE,mBAAmB,oBAAoB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Public API for programmatic use
|
|
2
|
+
export { loadConfig, ConfigError } from './config/index.js';
|
|
3
|
+
export { registerLinter, registerDiffer, getLinter, getDiffer } from './governance/index.js';
|
|
4
|
+
// Core diffing
|
|
5
|
+
export { diffContracts } from './core/diff.js';
|
|
6
|
+
// File utilities
|
|
7
|
+
export { findContractualDir, getSnapshotPath, copyToSnapshots, CONTRACTUAL_DIR, } from './utils/files.js';
|
|
8
|
+
// Re-export from @contractual/changesets
|
|
9
|
+
export { VersionManager, PreReleaseManager, createChangeset, readChangesets, aggregateBumps, generateChangesetName, extractContractChanges, appendChangelog, incrementVersionWithPreRelease, VERSIONS_FILE, SNAPSHOTS_DIR, CHANGESETS_DIR, PRE_RELEASE_FILE, } from '@contractual/changesets';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type ExecSyncOptions } from 'node:child_process';
|
|
2
|
+
/**
|
|
3
|
+
* Result of executing a command
|
|
4
|
+
*/
|
|
5
|
+
export interface ExecResult {
|
|
6
|
+
/** Standard output */
|
|
7
|
+
stdout: string;
|
|
8
|
+
/** Standard error */
|
|
9
|
+
stderr: string;
|
|
10
|
+
/** Exit code */
|
|
11
|
+
exitCode: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Substitute placeholders in a command string
|
|
15
|
+
*/
|
|
16
|
+
export declare function substitutePlaceholders(command: string, substitutions: Record<string, string>): string;
|
|
17
|
+
/**
|
|
18
|
+
* Check if a string contains placeholders
|
|
19
|
+
*/
|
|
20
|
+
export declare function hasPlaceholders(command: string): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Execute a command and return the result
|
|
23
|
+
*/
|
|
24
|
+
export declare function execCommand(command: string, substitutions?: Record<string, string>, options?: ExecSyncOptions): ExecResult;
|
|
25
|
+
/**
|
|
26
|
+
* Execute a command and throw if it fails
|
|
27
|
+
*/
|
|
28
|
+
export declare function execCommandOrThrow(command: string, substitutions?: Record<string, string>, options?: ExecSyncOptions): string;
|
|
29
|
+
//# sourceMappingURL=exec.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exec.d.ts","sourceRoot":"","sources":["../../src/utils/exec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAEpE;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,sBAAsB;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,qBAAqB;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAsBD;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACpC,MAAM,CAMR;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAExD;AAED;;GAEG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,MAAM,EACf,aAAa,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,EAC1C,OAAO,GAAE,eAAoB,GAC5B,UAAU,CA2BZ;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,aAAa,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,EAC1C,OAAO,GAAE,eAAoB,GAC5B,MAAM,CAMR"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
/**
|
|
3
|
+
* Type guard for execSync errors
|
|
4
|
+
*/
|
|
5
|
+
function isExecSyncError(err) {
|
|
6
|
+
return (typeof err === 'object' &&
|
|
7
|
+
err !== null &&
|
|
8
|
+
('status' in err || 'stdout' in err || 'stderr' in err));
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Substitute placeholders in a command string
|
|
12
|
+
*/
|
|
13
|
+
export function substitutePlaceholders(command, substitutions) {
|
|
14
|
+
let result = command;
|
|
15
|
+
for (const [key, value] of Object.entries(substitutions)) {
|
|
16
|
+
result = result.replace(new RegExp(`\\{${key}\\}`, 'g'), value);
|
|
17
|
+
}
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Check if a string contains placeholders
|
|
22
|
+
*/
|
|
23
|
+
export function hasPlaceholders(command) {
|
|
24
|
+
return /\{(spec|old|new)\}/.test(command);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Execute a command and return the result
|
|
28
|
+
*/
|
|
29
|
+
export function execCommand(command, substitutions = {}, options = {}) {
|
|
30
|
+
const resolvedCommand = substitutePlaceholders(command, substitutions);
|
|
31
|
+
try {
|
|
32
|
+
const stdout = execSync(resolvedCommand, {
|
|
33
|
+
encoding: 'utf-8',
|
|
34
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
35
|
+
...options,
|
|
36
|
+
});
|
|
37
|
+
return { stdout, stderr: '', exitCode: 0 };
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
if (isExecSyncError(err)) {
|
|
41
|
+
const stdout = typeof err.stdout === 'string' ? err.stdout : (err.stdout?.toString() ?? '');
|
|
42
|
+
const stderr = typeof err.stderr === 'string' ? err.stderr : (err.stderr?.toString() ?? '');
|
|
43
|
+
return {
|
|
44
|
+
stdout,
|
|
45
|
+
stderr,
|
|
46
|
+
exitCode: err.status ?? 1,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
// Unknown error type - return generic failure
|
|
50
|
+
return {
|
|
51
|
+
stdout: '',
|
|
52
|
+
stderr: err instanceof Error ? err.message : 'Unknown error',
|
|
53
|
+
exitCode: 1,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Execute a command and throw if it fails
|
|
59
|
+
*/
|
|
60
|
+
export function execCommandOrThrow(command, substitutions = {}, options = {}) {
|
|
61
|
+
const result = execCommand(command, substitutions, options);
|
|
62
|
+
if (result.exitCode !== 0) {
|
|
63
|
+
throw new Error(`Command failed with exit code ${result.exitCode}: ${result.stderr}`);
|
|
64
|
+
}
|
|
65
|
+
return result.stdout;
|
|
66
|
+
}
|