@transitrix/cli 1.0.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/README.md +69 -0
- package/dist/cli.js +3184 -0
- package/dist/export-compliance.js +1076 -0
- package/dist/repo-validate.js +207 -0
- package/package.json +48 -0
- package/schemas/bpmn-dsl.schema.json +136 -0
- package/schemas/cervinrc.schema.json +23 -0
- package/schemas/transitrixrc.schema.json +23 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { createRequire as __createRequire__ } from 'node:module'; const require = __createRequire__(import.meta.url);
|
|
2
|
+
|
|
3
|
+
// ../../src/repo-validate.ts
|
|
4
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
5
|
+
import * as path from "node:path";
|
|
6
|
+
import yaml from "js-yaml";
|
|
7
|
+
|
|
8
|
+
// ../diagrams/src/repo-validate/validate-repo.ts
|
|
9
|
+
var PScope = "repo";
|
|
10
|
+
var OWNER_REQUIRED_STATUSES = /* @__PURE__ */ new Set(["Active", "Production"]);
|
|
11
|
+
function isRecord(v) {
|
|
12
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
13
|
+
}
|
|
14
|
+
function docId(doc) {
|
|
15
|
+
if (!doc.data) return null;
|
|
16
|
+
const id = doc.data["id"];
|
|
17
|
+
return typeof id === "string" && id.length > 0 ? id : null;
|
|
18
|
+
}
|
|
19
|
+
function endpointId(value) {
|
|
20
|
+
if (typeof value === "string") return value.length > 0 ? value : null;
|
|
21
|
+
if (isRecord(value)) {
|
|
22
|
+
const id = value["id"];
|
|
23
|
+
if (typeof id === "string" && id.length > 0) return id;
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
function checkSyntax(docs, findings) {
|
|
28
|
+
for (const doc of docs) {
|
|
29
|
+
if (doc.parseError) {
|
|
30
|
+
findings.push({
|
|
31
|
+
scope: PScope,
|
|
32
|
+
id: "",
|
|
33
|
+
message: `YAML syntax error in ${doc.path}: ${doc.parseError}`
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function checkIdUniqueness(input, findings) {
|
|
39
|
+
const filesById = /* @__PURE__ */ new Map();
|
|
40
|
+
for (const doc of [...input.elements, ...input.relations]) {
|
|
41
|
+
const id = docId(doc);
|
|
42
|
+
if (!id) continue;
|
|
43
|
+
const list = filesById.get(id);
|
|
44
|
+
if (list) list.push(doc.path);
|
|
45
|
+
else filesById.set(id, [doc.path]);
|
|
46
|
+
}
|
|
47
|
+
for (const [id, files] of filesById) {
|
|
48
|
+
if (files.length > 1) {
|
|
49
|
+
findings.push({
|
|
50
|
+
scope: PScope,
|
|
51
|
+
id,
|
|
52
|
+
message: `Duplicate id '${id}' defined in ${files.length} files: ${files.join(", ")}`
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function checkAtomicity(input, findings) {
|
|
58
|
+
for (const doc of input.elements) {
|
|
59
|
+
const id = docId(doc);
|
|
60
|
+
if (id && doc.data && "relations" in doc.data) {
|
|
61
|
+
findings.push({
|
|
62
|
+
scope: PScope,
|
|
63
|
+
id,
|
|
64
|
+
message: `Atomicity violation: element '${id}' (${doc.path}) contains a 'relations' section. Move relations to separate files under canon/relations/.`
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function checkReferentialIntegrity(input, findings) {
|
|
70
|
+
const elementIds = /* @__PURE__ */ new Set();
|
|
71
|
+
for (const doc of input.elements) {
|
|
72
|
+
const id = docId(doc);
|
|
73
|
+
if (id) elementIds.add(id);
|
|
74
|
+
}
|
|
75
|
+
for (const doc of input.relations) {
|
|
76
|
+
if (!doc.data) continue;
|
|
77
|
+
const relId = docId(doc) ?? "";
|
|
78
|
+
const fromId = endpointId(doc.data["from"]) ?? endpointId(doc.data["source"]);
|
|
79
|
+
const toId = endpointId(doc.data["to"]) ?? endpointId(doc.data["target"]);
|
|
80
|
+
if (fromId && !elementIds.has(fromId)) {
|
|
81
|
+
findings.push({
|
|
82
|
+
scope: PScope,
|
|
83
|
+
id: relId,
|
|
84
|
+
message: `Referential integrity: relation '${relId || doc.path}' endpoint '${fromId}' (from) does not resolve to a known element.`
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
if (toId && !elementIds.has(toId)) {
|
|
88
|
+
findings.push({
|
|
89
|
+
scope: PScope,
|
|
90
|
+
id: relId,
|
|
91
|
+
message: `Referential integrity: relation '${relId || doc.path}' endpoint '${toId}' (to) does not resolve to a known element.`
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function checkLayerSemantics(_input, _findings) {
|
|
97
|
+
}
|
|
98
|
+
function checkPolicy(input, findings) {
|
|
99
|
+
for (const doc of input.elements) {
|
|
100
|
+
const id = docId(doc);
|
|
101
|
+
if (!id || !doc.data) continue;
|
|
102
|
+
const metadata = doc.data["metadata"];
|
|
103
|
+
if (!isRecord(metadata)) continue;
|
|
104
|
+
const status = metadata["status"];
|
|
105
|
+
const owner = metadata["owner"];
|
|
106
|
+
if (typeof status === "string" && OWNER_REQUIRED_STATUSES.has(status) && !owner) {
|
|
107
|
+
findings.push({
|
|
108
|
+
scope: PScope,
|
|
109
|
+
id,
|
|
110
|
+
message: `Policy: element '${id}' has status '${status}' but no owner assigned.`
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function validateRepoModel(input) {
|
|
116
|
+
const findings = [];
|
|
117
|
+
checkSyntax([...input.elements, ...input.relations], findings);
|
|
118
|
+
if (findings.length > 0) return findings;
|
|
119
|
+
checkIdUniqueness(input, findings);
|
|
120
|
+
checkAtomicity(input, findings);
|
|
121
|
+
checkReferentialIntegrity(input, findings);
|
|
122
|
+
checkLayerSemantics(input, findings);
|
|
123
|
+
checkPolicy(input, findings);
|
|
124
|
+
return findings;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ../../src/repo-validate.ts
|
|
128
|
+
var SKIP_SEGMENTS = /* @__PURE__ */ new Set(["node_modules", ".templates", ".validators"]);
|
|
129
|
+
function segments(rel) {
|
|
130
|
+
return rel.split(/[\\/]/);
|
|
131
|
+
}
|
|
132
|
+
function isYaml(rel) {
|
|
133
|
+
return /\.ya?ml$/i.test(rel);
|
|
134
|
+
}
|
|
135
|
+
function shouldSkip(rel) {
|
|
136
|
+
return segments(rel).some((s) => SKIP_SEGMENTS.has(s));
|
|
137
|
+
}
|
|
138
|
+
function readDoc(root, rel) {
|
|
139
|
+
try {
|
|
140
|
+
const parsed = yaml.load(readFileSync(path.join(root, rel), "utf-8"));
|
|
141
|
+
const data = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
142
|
+
return { path: rel.replace(/\\/g, "/"), data };
|
|
143
|
+
} catch (e) {
|
|
144
|
+
const err = e;
|
|
145
|
+
return { path: rel.replace(/\\/g, "/"), data: null, parseError: err.message };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function loadRepoModel(root) {
|
|
149
|
+
const elements = [];
|
|
150
|
+
const relations = [];
|
|
151
|
+
let entries = [];
|
|
152
|
+
try {
|
|
153
|
+
entries = readdirSync(path.join(root, "canon"), { recursive: true });
|
|
154
|
+
} catch {
|
|
155
|
+
return { elements, relations };
|
|
156
|
+
}
|
|
157
|
+
for (const rel of entries) {
|
|
158
|
+
if (typeof rel !== "string" || !isYaml(rel) || shouldSkip(rel)) continue;
|
|
159
|
+
const segs = segments(rel);
|
|
160
|
+
const zone = segs[0];
|
|
161
|
+
const fullRel = path.join("canon", rel);
|
|
162
|
+
if (zone === "elements") {
|
|
163
|
+
elements.push(readDoc(root, fullRel));
|
|
164
|
+
} else if (zone === "relations") {
|
|
165
|
+
relations.push(readDoc(root, fullRel));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return { elements, relations };
|
|
169
|
+
}
|
|
170
|
+
function runRepoValidate(root) {
|
|
171
|
+
return validateRepoModel(loadRepoModel(root));
|
|
172
|
+
}
|
|
173
|
+
function reportRepoFindings(root, findings, useJson) {
|
|
174
|
+
if (useJson) {
|
|
175
|
+
console.log(
|
|
176
|
+
JSON.stringify(
|
|
177
|
+
{ scope: "repo", root, valid: findings.length === 0, findings },
|
|
178
|
+
null,
|
|
179
|
+
2
|
|
180
|
+
)
|
|
181
|
+
);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (findings.length === 0) {
|
|
185
|
+
console.log(`\u2713 ${root} \u2014 repo-scope validation passed`);
|
|
186
|
+
console.log();
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
console.log();
|
|
190
|
+
console.log(`\u2717 ${root}`);
|
|
191
|
+
console.log();
|
|
192
|
+
console.log("Repo-scope validation:");
|
|
193
|
+
for (const f of findings) {
|
|
194
|
+
const where = f.id ? f.id : "(file)";
|
|
195
|
+
console.log(` \x1B[31m\u2717 ${where}\x1B[0m ${f.message}`);
|
|
196
|
+
}
|
|
197
|
+
console.log();
|
|
198
|
+
console.log(
|
|
199
|
+
`Repo-scope validation: \x1B[31m${findings.length} finding${findings.length === 1 ? "" : "s"}\x1B[0m`
|
|
200
|
+
);
|
|
201
|
+
console.log();
|
|
202
|
+
}
|
|
203
|
+
export {
|
|
204
|
+
loadRepoModel,
|
|
205
|
+
reportRepoFindings,
|
|
206
|
+
runRepoValidate
|
|
207
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@transitrix/cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Transitrix CLI — compile, validate, and report on Transitrix diagrams (BPMN, Goals, FGCA, …) from any shell or CI pipeline.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://github.com/transitrix/transitrix-studio/tree/main/packages/cli#readme",
|
|
8
|
+
"bugs": {
|
|
9
|
+
"url": "https://github.com/transitrix/transitrix-studio/issues"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/transitrix/transitrix-studio.git",
|
|
14
|
+
"directory": "packages/cli"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"bpmn",
|
|
18
|
+
"transitrix",
|
|
19
|
+
"diagrams",
|
|
20
|
+
"cli",
|
|
21
|
+
"enterprise-architecture"
|
|
22
|
+
],
|
|
23
|
+
"bin": {
|
|
24
|
+
"transitrix": "./dist/cli.js"
|
|
25
|
+
},
|
|
26
|
+
"main": "./dist/cli.js",
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"schemas",
|
|
30
|
+
"README.md",
|
|
31
|
+
"LICENSE"
|
|
32
|
+
],
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=20"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "node ../../scripts/build-cli-package.mjs",
|
|
38
|
+
"prepack": "node ../../scripts/build-cli-package.mjs"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"ajv": "^8.17.1",
|
|
42
|
+
"ajv-formats": "^3.0.1",
|
|
43
|
+
"bpmn-moddle": "^10.0.0",
|
|
44
|
+
"elkjs": "^0.9.3",
|
|
45
|
+
"js-yaml": "^4.1.0",
|
|
46
|
+
"xmlbuilder2": "^3.1.1"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://transitrix.com/schema/bpmn-dsl-v1.json",
|
|
4
|
+
"title": "Transitrix BPMN YAML DSL (BPMN process)",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": ["process"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"notation": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"const": "bpmn",
|
|
12
|
+
"description": "Canonical notation header per notations/CONTRACT.md. Required by the methodology; optional in this schema for backward compatibility with files predating the canonical header."
|
|
13
|
+
},
|
|
14
|
+
"spec_version": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"description": "Notation spec version this document conforms to. Reserved per CONTRACT.md; accepted but not enforced."
|
|
17
|
+
},
|
|
18
|
+
"process": {
|
|
19
|
+
"type": "object",
|
|
20
|
+
"additionalProperties": false,
|
|
21
|
+
"required": ["id", "name", "pools", "flows"],
|
|
22
|
+
"properties": {
|
|
23
|
+
"id": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"pattern": "^[A-Za-z][A-Za-z0-9_-]*$"
|
|
26
|
+
},
|
|
27
|
+
"name": { "type": "string", "minLength": 1 },
|
|
28
|
+
"pools": {
|
|
29
|
+
"type": "array",
|
|
30
|
+
"minItems": 1,
|
|
31
|
+
"maxItems": 1,
|
|
32
|
+
"items": { "$ref": "#/definitions/pool" }
|
|
33
|
+
},
|
|
34
|
+
"flows": {
|
|
35
|
+
"type": "array",
|
|
36
|
+
"minItems": 1,
|
|
37
|
+
"items": { "$ref": "#/definitions/flow" }
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"definitions": {
|
|
43
|
+
"pool": {
|
|
44
|
+
"type": "object",
|
|
45
|
+
"additionalProperties": false,
|
|
46
|
+
"required": ["id", "name", "lanes"],
|
|
47
|
+
"properties": {
|
|
48
|
+
"id": {
|
|
49
|
+
"type": "string",
|
|
50
|
+
"pattern": "^[A-Za-z][A-Za-z0-9_-]*$"
|
|
51
|
+
},
|
|
52
|
+
"name": { "type": "string", "minLength": 1 },
|
|
53
|
+
"lanes": {
|
|
54
|
+
"type": "array",
|
|
55
|
+
"minItems": 1,
|
|
56
|
+
"items": { "$ref": "#/definitions/lane" }
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"lane": {
|
|
61
|
+
"type": "object",
|
|
62
|
+
"additionalProperties": false,
|
|
63
|
+
"required": ["id", "name", "elements"],
|
|
64
|
+
"properties": {
|
|
65
|
+
"id": {
|
|
66
|
+
"type": "string",
|
|
67
|
+
"pattern": "^[A-Za-z][A-Za-z0-9_-]*$"
|
|
68
|
+
},
|
|
69
|
+
"name": { "type": "string", "minLength": 1 },
|
|
70
|
+
"elements": {
|
|
71
|
+
"type": "array",
|
|
72
|
+
"minItems": 1,
|
|
73
|
+
"items": { "$ref": "#/definitions/element" }
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"element": {
|
|
78
|
+
"type": "object",
|
|
79
|
+
"additionalProperties": false,
|
|
80
|
+
"required": ["id", "type"],
|
|
81
|
+
"properties": {
|
|
82
|
+
"id": {
|
|
83
|
+
"type": "string",
|
|
84
|
+
"pattern": "^[A-Za-z][A-Za-z0-9_-]*$"
|
|
85
|
+
},
|
|
86
|
+
"type": {
|
|
87
|
+
"type": "string",
|
|
88
|
+
"enum": [
|
|
89
|
+
"startEvent",
|
|
90
|
+
"endEvent",
|
|
91
|
+
"task",
|
|
92
|
+
"userTask",
|
|
93
|
+
"serviceTask",
|
|
94
|
+
"exclusiveGateway",
|
|
95
|
+
"parallelGateway"
|
|
96
|
+
]
|
|
97
|
+
},
|
|
98
|
+
"name": { "type": "string" }
|
|
99
|
+
},
|
|
100
|
+
"allOf": [
|
|
101
|
+
{
|
|
102
|
+
"if": {
|
|
103
|
+
"properties": { "type": { "enum": ["startEvent", "endEvent"] } }
|
|
104
|
+
},
|
|
105
|
+
"then": { "properties": { "name": {} } },
|
|
106
|
+
"else": {
|
|
107
|
+
"required": ["name"],
|
|
108
|
+
"properties": { "name": { "type": "string", "minLength": 1 } }
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
]
|
|
112
|
+
},
|
|
113
|
+
"flow": {
|
|
114
|
+
"type": "object",
|
|
115
|
+
"additionalProperties": false,
|
|
116
|
+
"required": ["from", "to"],
|
|
117
|
+
"properties": {
|
|
118
|
+
"id": {
|
|
119
|
+
"type": "string",
|
|
120
|
+
"pattern": "^[A-Za-z][A-Za-z0-9_-]*$"
|
|
121
|
+
},
|
|
122
|
+
"from": {
|
|
123
|
+
"type": "string",
|
|
124
|
+
"pattern": "^[A-Za-z][A-Za-z0-9_-]*$"
|
|
125
|
+
},
|
|
126
|
+
"to": {
|
|
127
|
+
"type": "string",
|
|
128
|
+
"pattern": "^[A-Za-z][A-Za-z0-9_-]*$"
|
|
129
|
+
},
|
|
130
|
+
"condition": { "type": "string", "minLength": 1 },
|
|
131
|
+
"default": { "type": "boolean" },
|
|
132
|
+
"name": { "type": "string", "minLength": 1 }
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"title": "Transitrix Studio Config Schema",
|
|
4
|
+
"description": "Schema for .cervinrc configuration file",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"rules": {
|
|
8
|
+
"type": "object",
|
|
9
|
+
"title": "Rule overrides",
|
|
10
|
+
"description": "Override validation rule severity levels. Errors cannot be downgraded.",
|
|
11
|
+
"patternProperties": {
|
|
12
|
+
"^[A-Z]+-[0-9-]+$": {
|
|
13
|
+
"type": "string",
|
|
14
|
+
"enum": ["off", "warn"],
|
|
15
|
+
"description": "Rule override value: 'off' to disable, 'warn' to report as warning"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"additionalProperties": false
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"additionalProperties": false,
|
|
22
|
+
"required": []
|
|
23
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"title": "Transitrix Studio Config Schema",
|
|
4
|
+
"description": "Schema for .transitrixrc configuration file (legacy .cervinrc is read as a fallback through 1.x).",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"rules": {
|
|
8
|
+
"type": "object",
|
|
9
|
+
"title": "Rule overrides",
|
|
10
|
+
"description": "Override validation rule severity levels. Errors cannot be downgraded.",
|
|
11
|
+
"patternProperties": {
|
|
12
|
+
"^[A-Z]+-[0-9-]+$": {
|
|
13
|
+
"type": "string",
|
|
14
|
+
"enum": ["off", "warn"],
|
|
15
|
+
"description": "Rule override value: 'off' to disable, 'warn' to report as warning"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"additionalProperties": false
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"additionalProperties": false,
|
|
22
|
+
"required": []
|
|
23
|
+
}
|