@eventcatalog/breaking-changes 0.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.
@@ -0,0 +1,45 @@
1
+ type CompatibilityStrategy = "BACKWARD" | "FORWARD" | "FULL" | "NONE";
2
+ type SchemaChangeType =
3
+ | "FIELD_ADDED_REQUIRED"
4
+ | "FIELD_ADDED_OPTIONAL"
5
+ | "FIELD_REMOVED_REQUIRED"
6
+ | "FIELD_REMOVED_OPTIONAL"
7
+ | "TYPE_CHANGED"
8
+ | "REQUIRED_ADDED"
9
+ | "REQUIRED_REMOVED";
10
+ type SchemaChange = {
11
+ type: SchemaChangeType;
12
+ field: string;
13
+ message: string;
14
+ previousType?: string;
15
+ currentType?: string;
16
+ };
17
+ type BreakingChange = SchemaChange & {
18
+ breaking: true;
19
+ };
20
+
21
+ declare const detectBreakingChanges: (
22
+ before: object,
23
+ after: object,
24
+ strategy: CompatibilityStrategy,
25
+ ) => BreakingChange[];
26
+
27
+ type JsonSchemaObject = {
28
+ type?: string;
29
+ properties?: Record<string, JsonSchemaObject>;
30
+ required?: string[];
31
+ [key: string]: any;
32
+ };
33
+ declare const diffJsonSchemas: (
34
+ before: JsonSchemaObject,
35
+ after: JsonSchemaObject,
36
+ ) => SchemaChange[];
37
+
38
+ export {
39
+ type BreakingChange,
40
+ type CompatibilityStrategy,
41
+ type SchemaChange,
42
+ type SchemaChangeType,
43
+ detectBreakingChanges,
44
+ diffJsonSchemas,
45
+ };
@@ -0,0 +1,45 @@
1
+ type CompatibilityStrategy = "BACKWARD" | "FORWARD" | "FULL" | "NONE";
2
+ type SchemaChangeType =
3
+ | "FIELD_ADDED_REQUIRED"
4
+ | "FIELD_ADDED_OPTIONAL"
5
+ | "FIELD_REMOVED_REQUIRED"
6
+ | "FIELD_REMOVED_OPTIONAL"
7
+ | "TYPE_CHANGED"
8
+ | "REQUIRED_ADDED"
9
+ | "REQUIRED_REMOVED";
10
+ type SchemaChange = {
11
+ type: SchemaChangeType;
12
+ field: string;
13
+ message: string;
14
+ previousType?: string;
15
+ currentType?: string;
16
+ };
17
+ type BreakingChange = SchemaChange & {
18
+ breaking: true;
19
+ };
20
+
21
+ declare const detectBreakingChanges: (
22
+ before: object,
23
+ after: object,
24
+ strategy: CompatibilityStrategy,
25
+ ) => BreakingChange[];
26
+
27
+ type JsonSchemaObject = {
28
+ type?: string;
29
+ properties?: Record<string, JsonSchemaObject>;
30
+ required?: string[];
31
+ [key: string]: any;
32
+ };
33
+ declare const diffJsonSchemas: (
34
+ before: JsonSchemaObject,
35
+ after: JsonSchemaObject,
36
+ ) => SchemaChange[];
37
+
38
+ export {
39
+ type BreakingChange,
40
+ type CompatibilityStrategy,
41
+ type SchemaChange,
42
+ type SchemaChangeType,
43
+ detectBreakingChanges,
44
+ diffJsonSchemas,
45
+ };
package/dist/index.js ADDED
@@ -0,0 +1,145 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if ((from && typeof from === "object") || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, {
15
+ get: () => from[key],
16
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable,
17
+ });
18
+ }
19
+ return to;
20
+ };
21
+ var __toCommonJS = (mod) =>
22
+ __copyProps(__defProp({}, "__esModule", { value: true }), mod);
23
+
24
+ // src/index.ts
25
+ var index_exports = {};
26
+ __export(index_exports, {
27
+ detectBreakingChanges: () => detectBreakingChanges,
28
+ diffJsonSchemas: () => diffJsonSchemas,
29
+ });
30
+ module.exports = __toCommonJS(index_exports);
31
+
32
+ // src/json-schema/differ.ts
33
+ var diffJsonSchemas = (before, after) => {
34
+ const changes = [];
35
+ compareProperties(before, after, "", changes);
36
+ return changes;
37
+ };
38
+ var compareProperties = (before, after, prefix, changes) => {
39
+ const beforeProps = before.properties || {};
40
+ const afterProps = after.properties || {};
41
+ const beforeRequired = new Set(before.required || []);
42
+ const afterRequired = new Set(after.required || []);
43
+ const allFields = /* @__PURE__ */ new Set([
44
+ ...Object.keys(beforeProps),
45
+ ...Object.keys(afterProps),
46
+ ]);
47
+ for (const field of allFields) {
48
+ const fullPath = prefix ? `${prefix}.${field}` : field;
49
+ const inBefore = field in beforeProps;
50
+ const inAfter = field in afterProps;
51
+ if (!inBefore && inAfter) {
52
+ const isRequired = afterRequired.has(field);
53
+ changes.push({
54
+ type: isRequired ? "FIELD_ADDED_REQUIRED" : "FIELD_ADDED_OPTIONAL",
55
+ field: fullPath,
56
+ message: `${isRequired ? "Required" : "Optional"} field '${fullPath}' was added`,
57
+ });
58
+ continue;
59
+ }
60
+ if (inBefore && !inAfter) {
61
+ const wasRequired = beforeRequired.has(field);
62
+ changes.push({
63
+ type: wasRequired ? "FIELD_REMOVED_REQUIRED" : "FIELD_REMOVED_OPTIONAL",
64
+ field: fullPath,
65
+ message: `${wasRequired ? "Required" : "Optional"} field '${fullPath}' was removed`,
66
+ });
67
+ continue;
68
+ }
69
+ const beforeType = beforeProps[field].type;
70
+ const afterType = afterProps[field].type;
71
+ if (beforeType && afterType && beforeType !== afterType) {
72
+ changes.push({
73
+ type: "TYPE_CHANGED",
74
+ field: fullPath,
75
+ message: `Field '${fullPath}' type changed from '${beforeType}' to '${afterType}'`,
76
+ previousType: beforeType,
77
+ currentType: afterType,
78
+ });
79
+ }
80
+ const wasPreviouslyRequired = beforeRequired.has(field);
81
+ const isNowRequired = afterRequired.has(field);
82
+ if (!wasPreviouslyRequired && isNowRequired) {
83
+ changes.push({
84
+ type: "REQUIRED_ADDED",
85
+ field: fullPath,
86
+ message: `Field '${fullPath}' was made required`,
87
+ });
88
+ } else if (wasPreviouslyRequired && !isNowRequired) {
89
+ changes.push({
90
+ type: "REQUIRED_REMOVED",
91
+ field: fullPath,
92
+ message: `Field '${fullPath}' was made optional`,
93
+ });
94
+ }
95
+ if (
96
+ beforeProps[field].type === "object" ||
97
+ afterProps[field].type === "object"
98
+ ) {
99
+ compareProperties(
100
+ beforeProps[field],
101
+ afterProps[field],
102
+ fullPath,
103
+ changes,
104
+ );
105
+ }
106
+ }
107
+ };
108
+
109
+ // src/json-schema/rules.ts
110
+ var BREAKING_RULES = {
111
+ BACKWARD: /* @__PURE__ */ new Set([
112
+ "FIELD_ADDED_REQUIRED",
113
+ "FIELD_REMOVED_REQUIRED",
114
+ "TYPE_CHANGED",
115
+ "REQUIRED_ADDED",
116
+ ]),
117
+ FORWARD: /* @__PURE__ */ new Set([
118
+ "FIELD_ADDED_REQUIRED",
119
+ "FIELD_REMOVED_REQUIRED",
120
+ "TYPE_CHANGED",
121
+ "REQUIRED_ADDED",
122
+ ]),
123
+ FULL: /* @__PURE__ */ new Set([
124
+ "FIELD_ADDED_REQUIRED",
125
+ "FIELD_REMOVED_REQUIRED",
126
+ "TYPE_CHANGED",
127
+ "REQUIRED_ADDED",
128
+ ]),
129
+ NONE: /* @__PURE__ */ new Set(),
130
+ };
131
+ var detectBreakingChanges = (before, after, strategy) => {
132
+ if (strategy === "NONE") return [];
133
+ const changes = diffJsonSchemas(before, after);
134
+ const breakingTypes = BREAKING_RULES[strategy];
135
+ return changes
136
+ .filter((change) => breakingTypes.has(change.type))
137
+ .map((change) => ({ ...change, breaking: true }));
138
+ };
139
+ // Annotate the CommonJS export names for ESM import in node:
140
+ 0 &&
141
+ (module.exports = {
142
+ detectBreakingChanges,
143
+ diffJsonSchemas,
144
+ });
145
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/json-schema/differ.ts","../src/json-schema/rules.ts"],"sourcesContent":["export type { CompatibilityStrategy, SchemaChange, BreakingChange, SchemaChangeType } from './types';\n\nexport { detectBreakingChanges } from './json-schema/rules';\nexport { diffJsonSchemas } from './json-schema/differ';\n","import type { SchemaChange } from '../types';\n\ntype JsonSchemaObject = {\n type?: string;\n properties?: Record<string, JsonSchemaObject>;\n required?: string[];\n [key: string]: any;\n};\n\nexport const diffJsonSchemas = (before: JsonSchemaObject, after: JsonSchemaObject): SchemaChange[] => {\n const changes: SchemaChange[] = [];\n compareProperties(before, after, '', changes);\n return changes;\n};\n\nconst compareProperties = (\n before: JsonSchemaObject,\n after: JsonSchemaObject,\n prefix: string,\n changes: SchemaChange[]\n): void => {\n const beforeProps = before.properties || {};\n const afterProps = after.properties || {};\n const beforeRequired = new Set(before.required || []);\n const afterRequired = new Set(after.required || []);\n\n const allFields = new Set([...Object.keys(beforeProps), ...Object.keys(afterProps)]);\n\n for (const field of allFields) {\n const fullPath = prefix ? `${prefix}.${field}` : field;\n const inBefore = field in beforeProps;\n const inAfter = field in afterProps;\n\n if (!inBefore && inAfter) {\n const isRequired = afterRequired.has(field);\n changes.push({\n type: isRequired ? 'FIELD_ADDED_REQUIRED' : 'FIELD_ADDED_OPTIONAL',\n field: fullPath,\n message: `${isRequired ? 'Required' : 'Optional'} field '${fullPath}' was added`,\n });\n continue;\n }\n\n if (inBefore && !inAfter) {\n const wasRequired = beforeRequired.has(field);\n changes.push({\n type: wasRequired ? 'FIELD_REMOVED_REQUIRED' : 'FIELD_REMOVED_OPTIONAL',\n field: fullPath,\n message: `${wasRequired ? 'Required' : 'Optional'} field '${fullPath}' was removed`,\n });\n continue;\n }\n\n // Field exists in both — check type\n const beforeType = beforeProps[field].type;\n const afterType = afterProps[field].type;\n\n if (beforeType && afterType && beforeType !== afterType) {\n changes.push({\n type: 'TYPE_CHANGED',\n field: fullPath,\n message: `Field '${fullPath}' type changed from '${beforeType}' to '${afterType}'`,\n previousType: beforeType,\n currentType: afterType,\n });\n }\n\n // Check required status change\n const wasPreviouslyRequired = beforeRequired.has(field);\n const isNowRequired = afterRequired.has(field);\n\n if (!wasPreviouslyRequired && isNowRequired) {\n changes.push({\n type: 'REQUIRED_ADDED',\n field: fullPath,\n message: `Field '${fullPath}' was made required`,\n });\n } else if (wasPreviouslyRequired && !isNowRequired) {\n changes.push({\n type: 'REQUIRED_REMOVED',\n field: fullPath,\n message: `Field '${fullPath}' was made optional`,\n });\n }\n\n // Recurse into nested objects\n if (beforeProps[field].type === 'object' || afterProps[field].type === 'object') {\n compareProperties(beforeProps[field], afterProps[field], fullPath, changes);\n }\n }\n};\n","import type { CompatibilityStrategy, SchemaChange, BreakingChange, SchemaChangeType } from '../types';\nimport { diffJsonSchemas } from './differ';\n\nconst BREAKING_RULES: Record<CompatibilityStrategy, Set<SchemaChangeType>> = {\n BACKWARD: new Set([\n 'FIELD_ADDED_REQUIRED',\n 'FIELD_REMOVED_REQUIRED',\n 'TYPE_CHANGED',\n 'REQUIRED_ADDED',\n ]),\n FORWARD: new Set([\n 'FIELD_ADDED_REQUIRED',\n 'FIELD_REMOVED_REQUIRED',\n 'TYPE_CHANGED',\n 'REQUIRED_ADDED',\n ]),\n FULL: new Set([\n 'FIELD_ADDED_REQUIRED',\n 'FIELD_REMOVED_REQUIRED',\n 'TYPE_CHANGED',\n 'REQUIRED_ADDED',\n ]),\n NONE: new Set(),\n};\n\nexport const detectBreakingChanges = (\n before: object,\n after: object,\n strategy: CompatibilityStrategy\n): BreakingChange[] => {\n if (strategy === 'NONE') return [];\n\n const changes = diffJsonSchemas(before, after);\n const breakingTypes = BREAKING_RULES[strategy];\n\n return changes\n .filter((change) => breakingTypes.has(change.type))\n .map((change) => ({ ...change, breaking: true as const }));\n};\n"],"mappings":"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","names":[]}
package/dist/index.mjs ADDED
@@ -0,0 +1,109 @@
1
+ // src/json-schema/differ.ts
2
+ var diffJsonSchemas = (before, after) => {
3
+ const changes = [];
4
+ compareProperties(before, after, "", changes);
5
+ return changes;
6
+ };
7
+ var compareProperties = (before, after, prefix, changes) => {
8
+ const beforeProps = before.properties || {};
9
+ const afterProps = after.properties || {};
10
+ const beforeRequired = new Set(before.required || []);
11
+ const afterRequired = new Set(after.required || []);
12
+ const allFields = /* @__PURE__ */ new Set([
13
+ ...Object.keys(beforeProps),
14
+ ...Object.keys(afterProps),
15
+ ]);
16
+ for (const field of allFields) {
17
+ const fullPath = prefix ? `${prefix}.${field}` : field;
18
+ const inBefore = field in beforeProps;
19
+ const inAfter = field in afterProps;
20
+ if (!inBefore && inAfter) {
21
+ const isRequired = afterRequired.has(field);
22
+ changes.push({
23
+ type: isRequired ? "FIELD_ADDED_REQUIRED" : "FIELD_ADDED_OPTIONAL",
24
+ field: fullPath,
25
+ message: `${isRequired ? "Required" : "Optional"} field '${fullPath}' was added`,
26
+ });
27
+ continue;
28
+ }
29
+ if (inBefore && !inAfter) {
30
+ const wasRequired = beforeRequired.has(field);
31
+ changes.push({
32
+ type: wasRequired ? "FIELD_REMOVED_REQUIRED" : "FIELD_REMOVED_OPTIONAL",
33
+ field: fullPath,
34
+ message: `${wasRequired ? "Required" : "Optional"} field '${fullPath}' was removed`,
35
+ });
36
+ continue;
37
+ }
38
+ const beforeType = beforeProps[field].type;
39
+ const afterType = afterProps[field].type;
40
+ if (beforeType && afterType && beforeType !== afterType) {
41
+ changes.push({
42
+ type: "TYPE_CHANGED",
43
+ field: fullPath,
44
+ message: `Field '${fullPath}' type changed from '${beforeType}' to '${afterType}'`,
45
+ previousType: beforeType,
46
+ currentType: afterType,
47
+ });
48
+ }
49
+ const wasPreviouslyRequired = beforeRequired.has(field);
50
+ const isNowRequired = afterRequired.has(field);
51
+ if (!wasPreviouslyRequired && isNowRequired) {
52
+ changes.push({
53
+ type: "REQUIRED_ADDED",
54
+ field: fullPath,
55
+ message: `Field '${fullPath}' was made required`,
56
+ });
57
+ } else if (wasPreviouslyRequired && !isNowRequired) {
58
+ changes.push({
59
+ type: "REQUIRED_REMOVED",
60
+ field: fullPath,
61
+ message: `Field '${fullPath}' was made optional`,
62
+ });
63
+ }
64
+ if (
65
+ beforeProps[field].type === "object" ||
66
+ afterProps[field].type === "object"
67
+ ) {
68
+ compareProperties(
69
+ beforeProps[field],
70
+ afterProps[field],
71
+ fullPath,
72
+ changes,
73
+ );
74
+ }
75
+ }
76
+ };
77
+
78
+ // src/json-schema/rules.ts
79
+ var BREAKING_RULES = {
80
+ BACKWARD: /* @__PURE__ */ new Set([
81
+ "FIELD_ADDED_REQUIRED",
82
+ "FIELD_REMOVED_REQUIRED",
83
+ "TYPE_CHANGED",
84
+ "REQUIRED_ADDED",
85
+ ]),
86
+ FORWARD: /* @__PURE__ */ new Set([
87
+ "FIELD_ADDED_REQUIRED",
88
+ "FIELD_REMOVED_REQUIRED",
89
+ "TYPE_CHANGED",
90
+ "REQUIRED_ADDED",
91
+ ]),
92
+ FULL: /* @__PURE__ */ new Set([
93
+ "FIELD_ADDED_REQUIRED",
94
+ "FIELD_REMOVED_REQUIRED",
95
+ "TYPE_CHANGED",
96
+ "REQUIRED_ADDED",
97
+ ]),
98
+ NONE: /* @__PURE__ */ new Set(),
99
+ };
100
+ var detectBreakingChanges = (before, after, strategy) => {
101
+ if (strategy === "NONE") return [];
102
+ const changes = diffJsonSchemas(before, after);
103
+ const breakingTypes = BREAKING_RULES[strategy];
104
+ return changes
105
+ .filter((change) => breakingTypes.has(change.type))
106
+ .map((change) => ({ ...change, breaking: true }));
107
+ };
108
+ export { detectBreakingChanges, diffJsonSchemas };
109
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/json-schema/differ.ts","../src/json-schema/rules.ts"],"sourcesContent":["import type { SchemaChange } from '../types';\n\ntype JsonSchemaObject = {\n type?: string;\n properties?: Record<string, JsonSchemaObject>;\n required?: string[];\n [key: string]: any;\n};\n\nexport const diffJsonSchemas = (before: JsonSchemaObject, after: JsonSchemaObject): SchemaChange[] => {\n const changes: SchemaChange[] = [];\n compareProperties(before, after, '', changes);\n return changes;\n};\n\nconst compareProperties = (\n before: JsonSchemaObject,\n after: JsonSchemaObject,\n prefix: string,\n changes: SchemaChange[]\n): void => {\n const beforeProps = before.properties || {};\n const afterProps = after.properties || {};\n const beforeRequired = new Set(before.required || []);\n const afterRequired = new Set(after.required || []);\n\n const allFields = new Set([...Object.keys(beforeProps), ...Object.keys(afterProps)]);\n\n for (const field of allFields) {\n const fullPath = prefix ? `${prefix}.${field}` : field;\n const inBefore = field in beforeProps;\n const inAfter = field in afterProps;\n\n if (!inBefore && inAfter) {\n const isRequired = afterRequired.has(field);\n changes.push({\n type: isRequired ? 'FIELD_ADDED_REQUIRED' : 'FIELD_ADDED_OPTIONAL',\n field: fullPath,\n message: `${isRequired ? 'Required' : 'Optional'} field '${fullPath}' was added`,\n });\n continue;\n }\n\n if (inBefore && !inAfter) {\n const wasRequired = beforeRequired.has(field);\n changes.push({\n type: wasRequired ? 'FIELD_REMOVED_REQUIRED' : 'FIELD_REMOVED_OPTIONAL',\n field: fullPath,\n message: `${wasRequired ? 'Required' : 'Optional'} field '${fullPath}' was removed`,\n });\n continue;\n }\n\n // Field exists in both — check type\n const beforeType = beforeProps[field].type;\n const afterType = afterProps[field].type;\n\n if (beforeType && afterType && beforeType !== afterType) {\n changes.push({\n type: 'TYPE_CHANGED',\n field: fullPath,\n message: `Field '${fullPath}' type changed from '${beforeType}' to '${afterType}'`,\n previousType: beforeType,\n currentType: afterType,\n });\n }\n\n // Check required status change\n const wasPreviouslyRequired = beforeRequired.has(field);\n const isNowRequired = afterRequired.has(field);\n\n if (!wasPreviouslyRequired && isNowRequired) {\n changes.push({\n type: 'REQUIRED_ADDED',\n field: fullPath,\n message: `Field '${fullPath}' was made required`,\n });\n } else if (wasPreviouslyRequired && !isNowRequired) {\n changes.push({\n type: 'REQUIRED_REMOVED',\n field: fullPath,\n message: `Field '${fullPath}' was made optional`,\n });\n }\n\n // Recurse into nested objects\n if (beforeProps[field].type === 'object' || afterProps[field].type === 'object') {\n compareProperties(beforeProps[field], afterProps[field], fullPath, changes);\n }\n }\n};\n","import type { CompatibilityStrategy, SchemaChange, BreakingChange, SchemaChangeType } from '../types';\nimport { diffJsonSchemas } from './differ';\n\nconst BREAKING_RULES: Record<CompatibilityStrategy, Set<SchemaChangeType>> = {\n BACKWARD: new Set([\n 'FIELD_ADDED_REQUIRED',\n 'FIELD_REMOVED_REQUIRED',\n 'TYPE_CHANGED',\n 'REQUIRED_ADDED',\n ]),\n FORWARD: new Set([\n 'FIELD_ADDED_REQUIRED',\n 'FIELD_REMOVED_REQUIRED',\n 'TYPE_CHANGED',\n 'REQUIRED_ADDED',\n ]),\n FULL: new Set([\n 'FIELD_ADDED_REQUIRED',\n 'FIELD_REMOVED_REQUIRED',\n 'TYPE_CHANGED',\n 'REQUIRED_ADDED',\n ]),\n NONE: new Set(),\n};\n\nexport const detectBreakingChanges = (\n before: object,\n after: object,\n strategy: CompatibilityStrategy\n): BreakingChange[] => {\n if (strategy === 'NONE') return [];\n\n const changes = diffJsonSchemas(before, after);\n const breakingTypes = BREAKING_RULES[strategy];\n\n return changes\n .filter((change) => breakingTypes.has(change.type))\n .map((change) => ({ ...change, breaking: true as const }));\n};\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@eventcatalog/breaking-changes",
3
+ "version": "0.1.0",
4
+ "description": "Breaking schema change detection for EventCatalog",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "require": "./dist/index.js",
12
+ "import": "./dist/index.mjs"
13
+ }
14
+ },
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "scripts": {
19
+ "build": "tsup",
20
+ "build:bin": "tsup",
21
+ "test": "vitest --run",
22
+ "test:ci": "vitest --run",
23
+ "format": "prettier --write .",
24
+ "format:diff": "prettier --list-different ."
25
+ },
26
+ "files": [
27
+ "dist"
28
+ ],
29
+ "license": "ISC",
30
+ "devDependencies": {
31
+ "@types/node": "^20.14.10",
32
+ "prettier": "^3.3.3",
33
+ "tsup": "^8.1.0",
34
+ "typescript": "^5.5.3",
35
+ "vitest": "^3.2.4"
36
+ },
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "https://github.com/event-catalog/eventcatalog"
40
+ }
41
+ }