delimit-cli 1.0.0 → 2.1.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/.github/workflows/api-governance.yml +24 -0
- package/README.md +57 -115
- package/adapters/codex-skill.js +87 -0
- package/adapters/cursor-extension.js +190 -0
- package/adapters/gemini-action.js +93 -0
- package/adapters/openai-function.js +112 -0
- package/adapters/xai-plugin.js +151 -0
- package/bin/delimit-cli.js +921 -0
- package/bin/delimit.js +237 -1
- package/delimit.yml +19 -0
- package/hooks/evidence-status.sh +12 -0
- package/hooks/git/commit-msg +4 -0
- package/hooks/git/pre-commit +4 -0
- package/hooks/git/pre-push +4 -0
- package/hooks/install-hooks.sh +583 -0
- package/hooks/message-auth-hook.js +9 -0
- package/hooks/message-governance-hook.js +9 -0
- package/hooks/models/claude-post.js +4 -0
- package/hooks/models/claude-pre.js +4 -0
- package/hooks/models/codex-post.js +4 -0
- package/hooks/models/codex-pre.js +4 -0
- package/hooks/models/cursor-post.js +4 -0
- package/hooks/models/cursor-pre.js +4 -0
- package/hooks/models/gemini-post.js +4 -0
- package/hooks/models/gemini-pre.js +4 -0
- package/hooks/models/openai-post.js +4 -0
- package/hooks/models/openai-pre.js +4 -0
- package/hooks/models/windsurf-post.js +4 -0
- package/hooks/models/windsurf-pre.js +4 -0
- package/hooks/models/xai-post.js +4 -0
- package/hooks/models/xai-pre.js +4 -0
- package/hooks/post-bash-hook.js +13 -0
- package/hooks/post-mcp-hook.js +13 -0
- package/hooks/post-response-hook.js +4 -0
- package/hooks/post-tool-hook.js +126 -0
- package/hooks/post-write-hook.js +13 -0
- package/hooks/pre-bash-hook.js +30 -0
- package/hooks/pre-mcp-hook.js +13 -0
- package/hooks/pre-read-hook.js +13 -0
- package/hooks/pre-search-hook.js +13 -0
- package/hooks/pre-submit-hook.js +4 -0
- package/hooks/pre-task-hook.js +13 -0
- package/hooks/pre-tool-hook.js +121 -0
- package/hooks/pre-web-hook.js +13 -0
- package/hooks/pre-write-hook.js +31 -0
- package/hooks/test-hooks.sh +12 -0
- package/hooks/update-delimit.sh +6 -0
- package/lib/agent.js +509 -0
- package/lib/api-engine.js +156 -0
- package/lib/auth-setup.js +891 -0
- package/lib/decision-engine.js +474 -0
- package/lib/hooks-installer.js +416 -0
- package/lib/platform-adapters.js +353 -0
- package/lib/proxy-handler.js +114 -0
- package/package.json +38 -30
- package/scripts/infect.js +128 -0
- package/test-decision-engine.js +181 -0
- package/test-hook.js +27 -0
- package/dist/commands/validate.d.ts +0 -2
- package/dist/commands/validate.d.ts.map +0 -1
- package/dist/commands/validate.js +0 -106
- package/dist/commands/validate.js.map +0 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -71
- package/dist/index.js.map +0 -1
- package/dist/types/index.d.ts +0 -39
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js +0 -3
- package/dist/types/index.js.map +0 -1
- package/dist/utils/api.d.ts +0 -3
- package/dist/utils/api.d.ts.map +0 -1
- package/dist/utils/api.js +0 -64
- package/dist/utils/api.js.map +0 -1
- package/dist/utils/file.d.ts +0 -7
- package/dist/utils/file.d.ts.map +0 -1
- package/dist/utils/file.js +0 -69
- package/dist/utils/file.js.map +0 -1
- package/dist/utils/logger.d.ts +0 -14
- package/dist/utils/logger.d.ts.map +0 -1
- package/dist/utils/logger.js +0 -28
- package/dist/utils/logger.js.map +0 -1
- package/dist/utils/masker.d.ts +0 -14
- package/dist/utils/masker.d.ts.map +0 -1
- package/dist/utils/masker.js +0 -89
- package/dist/utils/masker.js.map +0 -1
- package/src/commands/validate.ts +0 -150
- package/src/index.ts +0 -80
- package/src/types/index.ts +0 -41
- package/src/utils/api.ts +0 -68
- package/src/utils/file.ts +0 -71
- package/src/utils/logger.ts +0 -27
- package/src/utils/masker.ts +0 -101
- package/test-sensitive.yaml +0 -109
- package/tsconfig.json +0 -23
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,MAAM;gBACL,MAAM;mBACH,MAAM;iBACR,MAAM;gBACP,MAAM;iBACL,MAAM;4BAGK,MAAM,WAAW,MAAM;8BAIrB,MAAM,WAAW,MAAM;eAItC,MAAM;mBAGF,MAAM;gBAGT,MAAM;qBACD,MAAM;CACxB,CAAC"}
|
package/dist/utils/logger.js
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.logger = void 0;
|
|
7
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
-
exports.logger = {
|
|
9
|
-
info: (msg) => console.log(chalk_1.default.blue('ℹ'), msg),
|
|
10
|
-
success: (msg) => console.log(chalk_1.default.green('✔'), chalk_1.default.green(msg)),
|
|
11
|
-
error: (msg) => console.log(chalk_1.default.red('✖'), chalk_1.default.red(msg)),
|
|
12
|
-
warn: (msg) => console.log(chalk_1.default.yellow('⚠'), chalk_1.default.yellow(msg)),
|
|
13
|
-
title: (msg) => console.log('\n' + chalk_1.default.bold.magenta(msg) + '\n'),
|
|
14
|
-
// Additional helpers for validation output
|
|
15
|
-
validationError: (path, message) => {
|
|
16
|
-
console.log(chalk_1.default.red(` ✖ ${path}: ${message}`));
|
|
17
|
-
},
|
|
18
|
-
validationWarning: (path, message) => {
|
|
19
|
-
console.log(chalk_1.default.yellow(` ⚠ ${path}: ${message}`));
|
|
20
|
-
},
|
|
21
|
-
dim: (msg) => console.log(chalk_1.default.dim(msg)),
|
|
22
|
-
// Progress indicators
|
|
23
|
-
loading: (msg) => console.log(chalk_1.default.cyan('⟳'), chalk_1.default.cyan(msg)),
|
|
24
|
-
// Formatting helpers
|
|
25
|
-
bold: (msg) => chalk_1.default.bold(msg),
|
|
26
|
-
underline: (msg) => chalk_1.default.underline(msg),
|
|
27
|
-
};
|
|
28
|
-
//# sourceMappingURL=logger.js.map
|
package/dist/utils/logger.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":";;;;;;AAAA,kDAA0B;AAEb,QAAA,MAAM,GAAG;IACpB,IAAI,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,eAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC;IACxD,OAAO,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,eAAK,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,eAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACzE,KAAK,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,eAAK,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,eAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACnE,IAAI,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,eAAK,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,eAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACxE,KAAK,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,eAAK,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC;IAE1E,2CAA2C;IAC3C,eAAe,EAAE,CAAC,IAAY,EAAE,OAAe,EAAE,EAAE;QACjD,OAAO,CAAC,GAAG,CAAC,eAAK,CAAC,GAAG,CAAC,OAAO,IAAI,KAAK,OAAO,EAAE,CAAC,CAAC,CAAC;IACpD,CAAC;IAED,iBAAiB,EAAE,CAAC,IAAY,EAAE,OAAe,EAAE,EAAE;QACnD,OAAO,CAAC,GAAG,CAAC,eAAK,CAAC,MAAM,CAAC,OAAO,IAAI,KAAK,OAAO,EAAE,CAAC,CAAC,CAAC;IACvD,CAAC;IAED,GAAG,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,eAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAEjD,sBAAsB;IACtB,OAAO,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,eAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,eAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAEvE,qBAAqB;IACrB,IAAI,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,eAAK,CAAC,IAAI,CAAC,GAAG,CAAC;IACtC,SAAS,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,eAAK,CAAC,SAAS,CAAC,GAAG,CAAC;CACjD,CAAC"}
|
package/dist/utils/masker.d.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Privacy Shield Implementation
|
|
3
|
-
* Removes sensitive data from OpenAPI specs before sending to API
|
|
4
|
-
*/
|
|
5
|
-
export declare function maskSensitiveData(data: any): any;
|
|
6
|
-
/**
|
|
7
|
-
* Get statistics about what was masked
|
|
8
|
-
*/
|
|
9
|
-
export declare function getMaskingStats(original: any, masked: any): {
|
|
10
|
-
fieldsRemoved: number;
|
|
11
|
-
bytesReduced: number;
|
|
12
|
-
percentReduced: number;
|
|
13
|
-
};
|
|
14
|
-
//# sourceMappingURL=masker.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"masker.d.ts","sourceRoot":"","sources":["../../src/utils/masker.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,GAAG,GAAG,GAAG,CAyDhD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,GAAG;IAC3D,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;CACxB,CA6BA"}
|
package/dist/utils/masker.js
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Privacy Shield Implementation
|
|
4
|
-
* Removes sensitive data from OpenAPI specs before sending to API
|
|
5
|
-
*/
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.maskSensitiveData = maskSensitiveData;
|
|
8
|
-
exports.getMaskingStats = getMaskingStats;
|
|
9
|
-
function maskSensitiveData(data) {
|
|
10
|
-
const SENSITIVE_KEYS = new Set([
|
|
11
|
-
'description',
|
|
12
|
-
'summary',
|
|
13
|
-
'example',
|
|
14
|
-
'examples',
|
|
15
|
-
'servers',
|
|
16
|
-
'contact', // Also mask contact info for privacy
|
|
17
|
-
'termsOfService',
|
|
18
|
-
'license'
|
|
19
|
-
]);
|
|
20
|
-
// Handle null or undefined
|
|
21
|
-
if (data === null || data === undefined) {
|
|
22
|
-
return data;
|
|
23
|
-
}
|
|
24
|
-
// Handle arrays
|
|
25
|
-
if (Array.isArray(data)) {
|
|
26
|
-
return data.map(item => maskSensitiveData(item));
|
|
27
|
-
}
|
|
28
|
-
// Handle objects
|
|
29
|
-
if (typeof data === 'object') {
|
|
30
|
-
const maskedObj = {};
|
|
31
|
-
for (const [key, value] of Object.entries(data)) {
|
|
32
|
-
// Skip sensitive keys entirely
|
|
33
|
-
if (SENSITIVE_KEYS.has(key)) {
|
|
34
|
-
// For required fields like servers, provide minimal replacement
|
|
35
|
-
if (key === 'servers') {
|
|
36
|
-
maskedObj[key] = [{ url: 'https://api.example.com' }];
|
|
37
|
-
}
|
|
38
|
-
// Skip other sensitive fields
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
|
-
// Special handling for info object - keep structure but mask content
|
|
42
|
-
if (key === 'info' && typeof value === 'object' && value !== null) {
|
|
43
|
-
const infoObj = value;
|
|
44
|
-
maskedObj[key] = {
|
|
45
|
-
title: infoObj.title || 'API',
|
|
46
|
-
version: infoObj.version || '1.0.0',
|
|
47
|
-
// Remove description, contact, license, etc.
|
|
48
|
-
};
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
// Recursively mask nested objects
|
|
52
|
-
maskedObj[key] = maskSensitiveData(value);
|
|
53
|
-
}
|
|
54
|
-
return maskedObj;
|
|
55
|
-
}
|
|
56
|
-
// Return primitives as-is (strings, numbers, booleans)
|
|
57
|
-
return data;
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Get statistics about what was masked
|
|
61
|
-
*/
|
|
62
|
-
function getMaskingStats(original, masked) {
|
|
63
|
-
const originalSize = JSON.stringify(original).length;
|
|
64
|
-
const maskedSize = JSON.stringify(masked).length;
|
|
65
|
-
const bytesReduced = originalSize - maskedSize;
|
|
66
|
-
const percentReduced = Math.round((bytesReduced / originalSize) * 100);
|
|
67
|
-
// Count removed fields (simplified)
|
|
68
|
-
const countFields = (obj) => {
|
|
69
|
-
let count = 0;
|
|
70
|
-
if (obj && typeof obj === 'object') {
|
|
71
|
-
for (const value of Object.values(obj)) {
|
|
72
|
-
count++;
|
|
73
|
-
if (typeof value === 'object') {
|
|
74
|
-
count += countFields(value);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
return count;
|
|
79
|
-
};
|
|
80
|
-
const originalFields = countFields(original);
|
|
81
|
-
const maskedFields = countFields(masked);
|
|
82
|
-
const fieldsRemoved = originalFields - maskedFields;
|
|
83
|
-
return {
|
|
84
|
-
fieldsRemoved,
|
|
85
|
-
bytesReduced,
|
|
86
|
-
percentReduced
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
//# sourceMappingURL=masker.js.map
|
package/dist/utils/masker.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"masker.js","sourceRoot":"","sources":["../../src/utils/masker.ts"],"names":[],"mappings":"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"}
|
package/src/commands/validate.ts
DELETED
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
import { readOpenApiFile, getFileInfo } from '../utils/file';
|
|
2
|
-
import { maskSensitiveData, getMaskingStats } from '../utils/masker';
|
|
3
|
-
import { delimitApi } from '../utils/api';
|
|
4
|
-
import { logger } from '../utils/logger';
|
|
5
|
-
|
|
6
|
-
interface ValidationResponse {
|
|
7
|
-
valid: boolean;
|
|
8
|
-
errors?: Array<{
|
|
9
|
-
path?: string;
|
|
10
|
-
message: string;
|
|
11
|
-
severity?: string;
|
|
12
|
-
}>;
|
|
13
|
-
warnings?: Array<{
|
|
14
|
-
path?: string;
|
|
15
|
-
message: string;
|
|
16
|
-
severity?: string;
|
|
17
|
-
}>;
|
|
18
|
-
summary?: {
|
|
19
|
-
error_count: number;
|
|
20
|
-
warning_count: number;
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export async function validateCommand(filepath: string, options: any = {}) {
|
|
25
|
-
try {
|
|
26
|
-
// Check if JSON output is requested
|
|
27
|
-
const jsonOutput = options.output === 'json';
|
|
28
|
-
|
|
29
|
-
if (!jsonOutput) {
|
|
30
|
-
logger.title('🚀 Delimit API Governance Validator');
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Step 1: Read file
|
|
34
|
-
if (!jsonOutput) {
|
|
35
|
-
logger.info(`Reading file: ${filepath}`);
|
|
36
|
-
const fileInfo = getFileInfo(filepath);
|
|
37
|
-
logger.dim(` File: ${fileInfo.name} (${fileInfo.size})`);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const rawData = readOpenApiFile(filepath);
|
|
41
|
-
|
|
42
|
-
// Step 2: Apply Privacy Shield
|
|
43
|
-
if (!jsonOutput) {
|
|
44
|
-
logger.info('Applying Privacy Shield™ - masking sensitive data...');
|
|
45
|
-
}
|
|
46
|
-
const maskedData = maskSensitiveData(rawData);
|
|
47
|
-
|
|
48
|
-
// Show masking stats if verbose mode
|
|
49
|
-
if (options.verbose && !jsonOutput) {
|
|
50
|
-
const stats = getMaskingStats(rawData, maskedData);
|
|
51
|
-
logger.dim(` Removed ${stats.fieldsRemoved} fields (${stats.percentReduced}% size reduction)`);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Step 3: Validate with API
|
|
55
|
-
if (!jsonOutput) {
|
|
56
|
-
logger.loading('Validating with Delimit governance engine...');
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const response = await delimitApi.post('/validate', {
|
|
60
|
-
spec: maskedData
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
const result: ValidationResponse = response.data;
|
|
64
|
-
|
|
65
|
-
// Step 4: Display results
|
|
66
|
-
if (jsonOutput) {
|
|
67
|
-
// Output JSON for machine parsing
|
|
68
|
-
console.log(JSON.stringify(result));
|
|
69
|
-
process.exit(result.valid ? 0 : 1);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Human-readable output
|
|
73
|
-
console.log(''); // Empty line for spacing
|
|
74
|
-
|
|
75
|
-
if (result.valid) {
|
|
76
|
-
logger.success('✨ Validation PASSED - Your API specification is compliant!');
|
|
77
|
-
|
|
78
|
-
if (result.warnings && result.warnings.length > 0) {
|
|
79
|
-
console.log('');
|
|
80
|
-
logger.warn(`Found ${result.warnings.length} warning(s):`);
|
|
81
|
-
result.warnings.forEach(warning => {
|
|
82
|
-
logger.validationWarning(
|
|
83
|
-
warning.path || 'general',
|
|
84
|
-
warning.message
|
|
85
|
-
);
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Show summary
|
|
90
|
-
if (result.summary) {
|
|
91
|
-
console.log('');
|
|
92
|
-
logger.info('Summary:');
|
|
93
|
-
logger.dim(` • Errors: ${result.summary.error_count}`);
|
|
94
|
-
logger.dim(` • Warnings: ${result.summary.warning_count}`);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
process.exit(0);
|
|
98
|
-
} else {
|
|
99
|
-
logger.error('❌ Validation FAILED - Your API specification has issues');
|
|
100
|
-
|
|
101
|
-
if (result.errors && result.errors.length > 0) {
|
|
102
|
-
console.log('');
|
|
103
|
-
logger.error(`Found ${result.errors.length} error(s):`);
|
|
104
|
-
result.errors.forEach(error => {
|
|
105
|
-
logger.validationError(
|
|
106
|
-
error.path || 'general',
|
|
107
|
-
error.message
|
|
108
|
-
);
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (result.warnings && result.warnings.length > 0) {
|
|
113
|
-
console.log('');
|
|
114
|
-
logger.warn(`Found ${result.warnings.length} warning(s):`);
|
|
115
|
-
result.warnings.forEach(warning => {
|
|
116
|
-
logger.validationWarning(
|
|
117
|
-
warning.path || 'general',
|
|
118
|
-
warning.message
|
|
119
|
-
);
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Show summary
|
|
124
|
-
if (result.summary) {
|
|
125
|
-
console.log('');
|
|
126
|
-
logger.info('Summary:');
|
|
127
|
-
logger.dim(` • Errors: ${result.summary.error_count}`);
|
|
128
|
-
logger.dim(` • Warnings: ${result.summary.warning_count}`);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
console.log('');
|
|
132
|
-
logger.info('💡 Fix the errors above and run validation again.');
|
|
133
|
-
|
|
134
|
-
process.exit(1);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
} catch (error: any) {
|
|
138
|
-
console.log(''); // Empty line for spacing
|
|
139
|
-
logger.error('Validation failed with error:');
|
|
140
|
-
logger.error(error.message);
|
|
141
|
-
|
|
142
|
-
if (error.message.includes('DELIMIT_API_KEY')) {
|
|
143
|
-
console.log('');
|
|
144
|
-
logger.info('📝 Get your free API key at:');
|
|
145
|
-
logger.info(' https://rapidapi.com/delimit/api/openapi-diff-api');
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
process.exit(1);
|
|
149
|
-
}
|
|
150
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { Command } from 'commander';
|
|
4
|
-
import { validateCommand } from './commands/validate';
|
|
5
|
-
import { logger } from './utils/logger';
|
|
6
|
-
|
|
7
|
-
// Create the main program
|
|
8
|
-
const program = new Command();
|
|
9
|
-
|
|
10
|
-
program
|
|
11
|
-
.name('delimit')
|
|
12
|
-
.description('CLI tool for Delimit API governance with built-in Privacy Shield™')
|
|
13
|
-
.version('1.0.0')
|
|
14
|
-
.option('-v, --verbose', 'Show detailed output')
|
|
15
|
-
.option('--api-url <url>', 'Override API URL (for testing)');
|
|
16
|
-
|
|
17
|
-
// Command: delimit validate <filepath>
|
|
18
|
-
program
|
|
19
|
-
.command('validate')
|
|
20
|
-
.description('Validate a local OpenAPI specification against governance rules')
|
|
21
|
-
.argument('<filepath>', 'Path to the OpenAPI specification file (JSON or YAML)')
|
|
22
|
-
.option('--verbose', 'Show detailed validation output')
|
|
23
|
-
.option('--output <format>', 'Output format: text or json', 'text')
|
|
24
|
-
.action(async (filepath, options) => {
|
|
25
|
-
// Merge global and command options
|
|
26
|
-
const globalOptions = program.opts();
|
|
27
|
-
const mergedOptions = { ...globalOptions, ...options };
|
|
28
|
-
|
|
29
|
-
// Set API URL if provided
|
|
30
|
-
if (mergedOptions.apiUrl) {
|
|
31
|
-
process.env.DELIMIT_API_URL = mergedOptions.apiUrl;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
await validateCommand(filepath, mergedOptions);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
// Future commands placeholder
|
|
38
|
-
program
|
|
39
|
-
.command('diff')
|
|
40
|
-
.description('Compare two OpenAPI specifications (coming soon)')
|
|
41
|
-
.argument('<old>', 'Path to the old/previous OpenAPI spec')
|
|
42
|
-
.argument('<new>', 'Path to the new/current OpenAPI spec')
|
|
43
|
-
.action(() => {
|
|
44
|
-
logger.info('The diff command is coming soon in v1.1.0!');
|
|
45
|
-
logger.info('It will detect breaking changes between API versions.');
|
|
46
|
-
process.exit(0);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
program
|
|
50
|
-
.command('analyze')
|
|
51
|
-
.description('Analyze API changes for risks (coming soon)')
|
|
52
|
-
.argument('<old>', 'Path to the old OpenAPI spec')
|
|
53
|
-
.argument('<new>', 'Path to the new OpenAPI spec')
|
|
54
|
-
.action(() => {
|
|
55
|
-
logger.info('The analyze command is coming soon in v1.1.0!');
|
|
56
|
-
logger.info('It will identify risky changes and soft breaks.');
|
|
57
|
-
process.exit(0);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
// Handle unknown commands
|
|
61
|
-
program.on('command:*', () => {
|
|
62
|
-
logger.error(`Invalid command: ${program.args.join(' ')}`);
|
|
63
|
-
logger.info('Run "delimit --help" for a list of available commands.');
|
|
64
|
-
process.exit(1);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
// Catch unhandled rejections
|
|
68
|
-
process.on('unhandledRejection', (reason: any) => {
|
|
69
|
-
logger.error('Unexpected error occurred:');
|
|
70
|
-
logger.error(reason?.message || reason);
|
|
71
|
-
process.exit(1);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
// Parse arguments
|
|
75
|
-
program.parse(process.argv);
|
|
76
|
-
|
|
77
|
-
// Show help if no arguments provided
|
|
78
|
-
if (!process.argv.slice(2).length) {
|
|
79
|
-
program.outputHelp();
|
|
80
|
-
}
|
package/src/types/index.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
export interface OpenApiSpec {
|
|
2
|
-
openapi?: string;
|
|
3
|
-
swagger?: string;
|
|
4
|
-
info?: {
|
|
5
|
-
title?: string;
|
|
6
|
-
version?: string;
|
|
7
|
-
description?: string;
|
|
8
|
-
[key: string]: any;
|
|
9
|
-
};
|
|
10
|
-
paths?: Record<string, any>;
|
|
11
|
-
components?: Record<string, any>;
|
|
12
|
-
servers?: Array<any>;
|
|
13
|
-
[key: string]: any;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface ValidationError {
|
|
17
|
-
path?: string;
|
|
18
|
-
message: string;
|
|
19
|
-
severity?: 'error' | 'warning' | 'info';
|
|
20
|
-
code?: string;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface ValidationResult {
|
|
24
|
-
valid: boolean;
|
|
25
|
-
errors: ValidationError[];
|
|
26
|
-
warnings: ValidationError[];
|
|
27
|
-
summary: {
|
|
28
|
-
error_count: number;
|
|
29
|
-
warning_count: number;
|
|
30
|
-
};
|
|
31
|
-
metadata?: {
|
|
32
|
-
engine_version?: string;
|
|
33
|
-
validation_time?: string;
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface CliOptions {
|
|
38
|
-
verbose?: boolean;
|
|
39
|
-
output?: 'json' | 'text';
|
|
40
|
-
quiet?: boolean;
|
|
41
|
-
}
|
package/src/utils/api.ts
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import axios, { AxiosInstance } from 'axios';
|
|
2
|
-
|
|
3
|
-
// Create axios instance with default config
|
|
4
|
-
export const delimitApi: AxiosInstance = axios.create({
|
|
5
|
-
baseURL: process.env.DELIMIT_API_URL || 'https://api.delimit.ai/v1',
|
|
6
|
-
timeout: 30000, // 30 second timeout
|
|
7
|
-
headers: {
|
|
8
|
-
'Content-Type': 'application/json',
|
|
9
|
-
'User-Agent': 'delimit-cli/1.0.0'
|
|
10
|
-
}
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
// Request interceptor to add API key
|
|
14
|
-
delimitApi.interceptors.request.use(
|
|
15
|
-
(config) => {
|
|
16
|
-
const apiKey = process.env.DELIMIT_API_KEY;
|
|
17
|
-
|
|
18
|
-
if (!apiKey) {
|
|
19
|
-
throw new Error(
|
|
20
|
-
'DELIMIT_API_KEY environment variable is not set.\n' +
|
|
21
|
-
'Please set it using: export DELIMIT_API_KEY="your-api-key"\n' +
|
|
22
|
-
'Get your API key at: https://rapidapi.com/delimit/api/schema-diff'
|
|
23
|
-
);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Use RapidAPI header format
|
|
27
|
-
config.headers['X-RapidAPI-Key'] = apiKey;
|
|
28
|
-
config.headers['X-RapidAPI-Host'] = 'api.delimit.ai';
|
|
29
|
-
|
|
30
|
-
return config;
|
|
31
|
-
},
|
|
32
|
-
(error) => {
|
|
33
|
-
return Promise.reject(error);
|
|
34
|
-
}
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
// Response interceptor for error handling
|
|
38
|
-
delimitApi.interceptors.response.use(
|
|
39
|
-
(response) => response,
|
|
40
|
-
(error) => {
|
|
41
|
-
// Enhance error messages
|
|
42
|
-
if (error.response) {
|
|
43
|
-
// Server responded with error
|
|
44
|
-
const status = error.response.status;
|
|
45
|
-
const data = error.response.data;
|
|
46
|
-
|
|
47
|
-
if (status === 401) {
|
|
48
|
-
error.message = 'Invalid API key. Please check your DELIMIT_API_KEY.';
|
|
49
|
-
} else if (status === 429) {
|
|
50
|
-
error.message = 'Rate limit exceeded. Please try again later.';
|
|
51
|
-
} else if (status === 422) {
|
|
52
|
-
error.message = 'Invalid OpenAPI specification format.';
|
|
53
|
-
if (data?.error?.message) {
|
|
54
|
-
error.message += `\n${data.error.message}`;
|
|
55
|
-
}
|
|
56
|
-
} else if (status >= 500) {
|
|
57
|
-
error.message = 'Delimit API server error. Please try again later.';
|
|
58
|
-
} else if (data?.error?.message) {
|
|
59
|
-
error.message = data.error.message;
|
|
60
|
-
}
|
|
61
|
-
} else if (error.request) {
|
|
62
|
-
// Request was made but no response
|
|
63
|
-
error.message = 'Could not connect to Delimit API. Please check your internet connection.';
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return Promise.reject(error);
|
|
67
|
-
}
|
|
68
|
-
);
|
package/src/utils/file.ts
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import yaml from 'js-yaml';
|
|
4
|
-
|
|
5
|
-
export function readOpenApiFile(filepath: string): any {
|
|
6
|
-
// Resolve to absolute path
|
|
7
|
-
const absolutePath = path.resolve(filepath);
|
|
8
|
-
|
|
9
|
-
if (!fs.existsSync(absolutePath)) {
|
|
10
|
-
throw new Error(`File not found: ${absolutePath}`);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const stats = fs.statSync(absolutePath);
|
|
14
|
-
if (!stats.isFile()) {
|
|
15
|
-
throw new Error(`Path is not a file: ${absolutePath}`);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// Check file size (limit to 10MB for safety)
|
|
19
|
-
const maxSize = 10 * 1024 * 1024; // 10MB
|
|
20
|
-
if (stats.size > maxSize) {
|
|
21
|
-
throw new Error(`File too large (${Math.round(stats.size / 1024 / 1024)}MB). Maximum size is 10MB.`);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const content = fs.readFileSync(absolutePath, 'utf8');
|
|
25
|
-
|
|
26
|
-
// Determine file type by extension
|
|
27
|
-
const ext = path.extname(absolutePath).toLowerCase();
|
|
28
|
-
|
|
29
|
-
try {
|
|
30
|
-
if (ext === '.json') {
|
|
31
|
-
return JSON.parse(content);
|
|
32
|
-
} else if (ext === '.yaml' || ext === '.yml') {
|
|
33
|
-
return yaml.load(content);
|
|
34
|
-
} else {
|
|
35
|
-
// Try to parse as YAML first (it can handle JSON too)
|
|
36
|
-
try {
|
|
37
|
-
return yaml.load(content);
|
|
38
|
-
} catch {
|
|
39
|
-
// Fall back to JSON
|
|
40
|
-
return JSON.parse(content);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
} catch (err: any) {
|
|
44
|
-
throw new Error(
|
|
45
|
-
`Failed to parse file as ${ext === '.json' ? 'JSON' : 'YAML'}.\n` +
|
|
46
|
-
`Please ensure the file is valid OpenAPI specification.\n` +
|
|
47
|
-
`Parser error: ${err.message}`
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function getFileInfo(filepath: string): {
|
|
53
|
-
name: string;
|
|
54
|
-
size: string;
|
|
55
|
-
extension: string;
|
|
56
|
-
} {
|
|
57
|
-
const absolutePath = path.resolve(filepath);
|
|
58
|
-
const stats = fs.statSync(absolutePath);
|
|
59
|
-
|
|
60
|
-
return {
|
|
61
|
-
name: path.basename(absolutePath),
|
|
62
|
-
size: formatFileSize(stats.size),
|
|
63
|
-
extension: path.extname(absolutePath).toLowerCase()
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function formatFileSize(bytes: number): string {
|
|
68
|
-
if (bytes < 1024) return `${bytes} bytes`;
|
|
69
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
70
|
-
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
71
|
-
}
|
package/src/utils/logger.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
|
|
3
|
-
export const logger = {
|
|
4
|
-
info: (msg: string) => console.log(chalk.blue('ℹ'), msg),
|
|
5
|
-
success: (msg: string) => console.log(chalk.green('✔'), chalk.green(msg)),
|
|
6
|
-
error: (msg: string) => console.log(chalk.red('✖'), chalk.red(msg)),
|
|
7
|
-
warn: (msg: string) => console.log(chalk.yellow('⚠'), chalk.yellow(msg)),
|
|
8
|
-
title: (msg: string) => console.log('\n' + chalk.bold.magenta(msg) + '\n'),
|
|
9
|
-
|
|
10
|
-
// Additional helpers for validation output
|
|
11
|
-
validationError: (path: string, message: string) => {
|
|
12
|
-
console.log(chalk.red(` ✖ ${path}: ${message}`));
|
|
13
|
-
},
|
|
14
|
-
|
|
15
|
-
validationWarning: (path: string, message: string) => {
|
|
16
|
-
console.log(chalk.yellow(` ⚠ ${path}: ${message}`));
|
|
17
|
-
},
|
|
18
|
-
|
|
19
|
-
dim: (msg: string) => console.log(chalk.dim(msg)),
|
|
20
|
-
|
|
21
|
-
// Progress indicators
|
|
22
|
-
loading: (msg: string) => console.log(chalk.cyan('⟳'), chalk.cyan(msg)),
|
|
23
|
-
|
|
24
|
-
// Formatting helpers
|
|
25
|
-
bold: (msg: string) => chalk.bold(msg),
|
|
26
|
-
underline: (msg: string) => chalk.underline(msg),
|
|
27
|
-
};
|