proschi 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.
package/dist/cli.cjs ADDED
@@ -0,0 +1,957 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/cli.ts
22
+ var cli_exports = {};
23
+ __export(cli_exports, {
24
+ collectFiles: () => collectFiles,
25
+ format: () => format,
26
+ run: () => run
27
+ });
28
+ module.exports = __toCommonJS(cli_exports);
29
+ var import_node_fs = require("node:fs");
30
+ var import_node_path = require("node:path");
31
+
32
+ // ../frontend/src/catalog/componentCatalog.ts
33
+ var componentCatalog = [
34
+ // Generic Shapes
35
+ { type: "shape", techStack: "Rectangle", category: "Generic Shapes", searchTerms: "rectangle box square shape" },
36
+ { type: "shape", techStack: "Circle", category: "Generic Shapes", searchTerms: "circle round shape" },
37
+ { type: "shape", techStack: "Diamond", category: "Generic Shapes", searchTerms: "diamond rhombus shape decision" },
38
+ { type: "shape", techStack: "Cylinder", category: "Generic Shapes", searchTerms: "cylinder shape storage" },
39
+ { type: "shape", techStack: "Cloud", category: "Generic Shapes", searchTerms: "cloud shape" },
40
+ { type: "shape", techStack: "Actor", category: "Generic Shapes", searchTerms: "actor user person human shape" },
41
+ { type: "shape", techStack: "Note", category: "Generic Shapes", searchTerms: "note document comment annotation" },
42
+ // Services
43
+ { type: "service", techStack: "REST API", category: "Services", searchTerms: "rest api http service endpoint" },
44
+ { type: "service", techStack: "GraphQL", category: "Services", searchTerms: "graphql api service query" },
45
+ { type: "service", techStack: "gRPC", category: "Services", searchTerms: "grpc rpc service" },
46
+ { type: "service", techStack: "WebSocket", category: "Services", searchTerms: "websocket ws realtime service" },
47
+ { type: "service", techStack: "SOAP API", category: "Services", searchTerms: "soap api xml service" },
48
+ // AWS Serverless
49
+ { type: "serverless", techStack: "AWS Lambda", category: "AWS Services", searchTerms: "aws lambda serverless function" },
50
+ { type: "serverless", techStack: "AWS API Gateway", category: "AWS Services", searchTerms: "aws api gateway rest" },
51
+ // AWS Compute
52
+ { type: "compute", techStack: "AWS EC2", category: "AWS Services", searchTerms: "aws ec2 compute instance vm" },
53
+ { type: "container", techStack: "AWS ECS", category: "AWS Services", searchTerms: "aws ecs container docker" },
54
+ { type: "container", techStack: "AWS EKS", category: "AWS Services", searchTerms: "aws eks kubernetes k8s" },
55
+ { type: "container", techStack: "AWS Fargate", category: "AWS Services", searchTerms: "aws fargate serverless container" },
56
+ // AWS Storage
57
+ { type: "storage", techStack: "AWS S3", category: "AWS Services", searchTerms: "aws s3 storage bucket object" },
58
+ { type: "storage", techStack: "AWS EBS", category: "AWS Services", searchTerms: "aws ebs storage volume disk" },
59
+ { type: "storage", techStack: "AWS EFS", category: "AWS Services", searchTerms: "aws efs storage file system" },
60
+ // AWS Database
61
+ { type: "database", techStack: "AWS RDS", category: "AWS Services", searchTerms: "aws rds database relational sql" },
62
+ { type: "database", techStack: "AWS DynamoDB", category: "AWS Services", searchTerms: "aws dynamodb database nosql" },
63
+ { type: "cache", techStack: "AWS ElastiCache", category: "AWS Services", searchTerms: "aws elasticache redis memcached cache" },
64
+ { type: "database", techStack: "AWS Aurora", category: "AWS Services", searchTerms: "aws aurora database mysql postgresql" },
65
+ // AWS Messaging
66
+ { type: "queue", techStack: "AWS SQS", category: "AWS Services", searchTerms: "aws sqs queue message" },
67
+ { type: "queue", techStack: "AWS SNS", category: "AWS Services", searchTerms: "aws sns notification topic pubsub" },
68
+ { type: "queue", techStack: "AWS EventBridge", category: "AWS Services", searchTerms: "aws eventbridge event bus" },
69
+ { type: "queue", techStack: "AWS Kinesis", category: "AWS Services", searchTerms: "aws kinesis stream data" },
70
+ // AWS CDN/Network
71
+ { type: "cdn", techStack: "AWS CloudFront", category: "AWS Services", searchTerms: "aws cloudfront cdn edge" },
72
+ { type: "cdn", techStack: "AWS Route53", category: "AWS Services", searchTerms: "aws route53 dns domain" },
73
+ { type: "cdn", techStack: "AWS Load Balancer", category: "AWS Services", searchTerms: "aws alb elb load balancer" },
74
+ // GCP Serverless
75
+ { type: "serverless", techStack: "GCP Cloud Functions", category: "GCP Services", searchTerms: "gcp google cloud functions serverless" },
76
+ { type: "serverless", techStack: "GCP Cloud Run", category: "GCP Services", searchTerms: "gcp google cloud run serverless container" },
77
+ { type: "serverless", techStack: "GCP App Engine", category: "GCP Services", searchTerms: "gcp google app engine paas" },
78
+ // GCP Compute
79
+ { type: "compute", techStack: "GCP Compute Engine", category: "GCP Services", searchTerms: "gcp google compute engine vm instance" },
80
+ { type: "container", techStack: "GCP GKE", category: "GCP Services", searchTerms: "gcp google gke kubernetes k8s" },
81
+ // GCP Storage
82
+ { type: "storage", techStack: "GCP Cloud Storage", category: "GCP Services", searchTerms: "gcp google cloud storage bucket object" },
83
+ { type: "storage", techStack: "GCP Persistent Disk", category: "GCP Services", searchTerms: "gcp google persistent disk storage" },
84
+ // GCP Database
85
+ { type: "database", techStack: "GCP Cloud SQL", category: "GCP Services", searchTerms: "gcp google cloud sql database" },
86
+ { type: "database", techStack: "GCP Firestore", category: "GCP Services", searchTerms: "gcp google firestore database nosql" },
87
+ { type: "database", techStack: "GCP Bigtable", category: "GCP Services", searchTerms: "gcp google bigtable database nosql" },
88
+ { type: "database", techStack: "GCP Spanner", category: "GCP Services", searchTerms: "gcp google spanner database sql" },
89
+ { type: "database", techStack: "GCP BigQuery", category: "GCP Services", searchTerms: "gcp google bigquery data warehouse analytics" },
90
+ // GCP Messaging
91
+ { type: "queue", techStack: "GCP Pub/Sub", category: "GCP Services", searchTerms: "gcp google pubsub pub/sub message queue" },
92
+ // GCP CDN/Network
93
+ { type: "cdn", techStack: "GCP Cloud CDN", category: "GCP Services", searchTerms: "gcp google cloud cdn edge" },
94
+ { type: "cdn", techStack: "GCP Cloud DNS", category: "GCP Services", searchTerms: "gcp google cloud dns domain" },
95
+ { type: "cdn", techStack: "GCP Load Balancing", category: "GCP Services", searchTerms: "gcp google load balancing" },
96
+ // Azure Serverless
97
+ { type: "serverless", techStack: "Azure Functions", category: "Azure Services", searchTerms: "azure functions serverless" },
98
+ { type: "serverless", techStack: "Azure Logic Apps", category: "Azure Services", searchTerms: "azure logic apps workflow" },
99
+ { type: "serverless", techStack: "Azure API Management", category: "Azure Services", searchTerms: "azure api management gateway" },
100
+ // Azure Compute
101
+ { type: "compute", techStack: "Azure VM", category: "Azure Services", searchTerms: "azure vm virtual machine compute" },
102
+ { type: "container", techStack: "Azure AKS", category: "Azure Services", searchTerms: "azure aks kubernetes k8s" },
103
+ { type: "container", techStack: "Azure Container Instances", category: "Azure Services", searchTerms: "azure container instances aci" },
104
+ // Azure Storage
105
+ { type: "storage", techStack: "Azure Blob Storage", category: "Azure Services", searchTerms: "azure blob storage object" },
106
+ { type: "storage", techStack: "Azure Files", category: "Azure Services", searchTerms: "azure files storage file system" },
107
+ { type: "storage", techStack: "Azure Disk Storage", category: "Azure Services", searchTerms: "azure disk storage volume" },
108
+ // Azure Database
109
+ { type: "database", techStack: "Azure SQL", category: "Azure Services", searchTerms: "azure sql database" },
110
+ { type: "database", techStack: "Azure Cosmos DB", category: "Azure Services", searchTerms: "azure cosmos db database nosql" },
111
+ { type: "database", techStack: "Azure Database for PostgreSQL", category: "Azure Services", searchTerms: "azure postgresql database" },
112
+ { type: "database", techStack: "Azure Database for MySQL", category: "Azure Services", searchTerms: "azure mysql database" },
113
+ { type: "cache", techStack: "Azure Cache for Redis", category: "Azure Services", searchTerms: "azure redis cache" },
114
+ // Azure Messaging
115
+ { type: "queue", techStack: "Azure Service Bus", category: "Azure Services", searchTerms: "azure service bus queue message" },
116
+ { type: "queue", techStack: "Azure Event Hubs", category: "Azure Services", searchTerms: "azure event hubs stream" },
117
+ { type: "queue", techStack: "Azure Event Grid", category: "Azure Services", searchTerms: "azure event grid" },
118
+ { type: "queue", techStack: "Azure Queue Storage", category: "Azure Services", searchTerms: "azure queue storage message" },
119
+ // Azure CDN/Network
120
+ { type: "cdn", techStack: "Azure CDN", category: "Azure Services", searchTerms: "azure cdn edge" },
121
+ { type: "cdn", techStack: "Azure DNS", category: "Azure Services", searchTerms: "azure dns domain" },
122
+ { type: "cdn", techStack: "Azure Front Door", category: "Azure Services", searchTerms: "azure front door cdn" },
123
+ // Traditional Databases
124
+ { type: "database", techStack: "PostgreSQL", category: "Databases", searchTerms: "postgresql postgres sql database" },
125
+ { type: "database", techStack: "MySQL", category: "Databases", searchTerms: "mysql sql database" },
126
+ { type: "database", techStack: "MongoDB", category: "Databases", searchTerms: "mongodb mongo nosql database" },
127
+ { type: "database", techStack: "Redis", category: "Databases", searchTerms: "redis cache database key-value" },
128
+ { type: "database", techStack: "DynamoDB", category: "Databases", searchTerms: "dynamodb nosql database aws" },
129
+ { type: "database", techStack: "Cassandra", category: "Databases", searchTerms: "cassandra nosql database" },
130
+ { type: "database", techStack: "CouchDB", category: "Databases", searchTerms: "couchdb nosql database" },
131
+ { type: "database", techStack: "Elasticsearch", category: "Databases", searchTerms: "elasticsearch search database" },
132
+ { type: "database", techStack: "Neo4j", category: "Databases", searchTerms: "neo4j graph database" },
133
+ { type: "database", techStack: "InfluxDB", category: "Databases", searchTerms: "influxdb timeseries database" },
134
+ { type: "database", techStack: "TimescaleDB", category: "Databases", searchTerms: "timescaledb timeseries postgresql database" },
135
+ { type: "database", techStack: "MariaDB", category: "Databases", searchTerms: "mariadb mysql sql database" },
136
+ { type: "database", techStack: "SQLite", category: "Databases", searchTerms: "sqlite sql database" },
137
+ { type: "database", techStack: "Oracle", category: "Databases", searchTerms: "oracle sql database" },
138
+ { type: "database", techStack: "SQL Server", category: "Databases", searchTerms: "sql server mssql database microsoft" },
139
+ // Cache & In-Memory
140
+ { type: "cache", techStack: "Memcached", category: "Cache & In-Memory", searchTerms: "memcached cache memory" },
141
+ { type: "cache", techStack: "Hazelcast", category: "Cache & In-Memory", searchTerms: "hazelcast cache memory" },
142
+ { type: "cache", techStack: "Aerospike", category: "Cache & In-Memory", searchTerms: "aerospike cache database" },
143
+ // Message Queues
144
+ { type: "queue", techStack: "Kafka", category: "Message Queues", searchTerms: "kafka message queue stream" },
145
+ { type: "queue", techStack: "RabbitMQ", category: "Message Queues", searchTerms: "rabbitmq message queue amqp" },
146
+ { type: "queue", techStack: "SQS", category: "Message Queues", searchTerms: "sqs queue aws message" },
147
+ { type: "queue", techStack: "Redis Queue", category: "Message Queues", searchTerms: "redis queue message" },
148
+ { type: "queue", techStack: "NATS", category: "Message Queues", searchTerms: "nats message queue" },
149
+ { type: "queue", techStack: "Apache Pulsar", category: "Message Queues", searchTerms: "pulsar message queue stream" },
150
+ { type: "queue", techStack: "ActiveMQ", category: "Message Queues", searchTerms: "activemq message queue" },
151
+ { type: "queue", techStack: "ZeroMQ", category: "Message Queues", searchTerms: "zeromq zmq message queue" },
152
+ // External Systems
153
+ { type: "external", techStack: "Third Party API", category: "External Systems", searchTerms: "third party api external" },
154
+ { type: "external", techStack: "Payment Gateway", category: "External Systems", searchTerms: "payment gateway stripe paypal" },
155
+ { type: "external", techStack: "Auth Service", category: "External Systems", searchTerms: "auth authentication authorization" },
156
+ { type: "external", techStack: "Email Service", category: "External Systems", searchTerms: "email service sendgrid mailgun" },
157
+ { type: "external", techStack: "SMS Service", category: "External Systems", searchTerms: "sms service twilio" },
158
+ { type: "external", techStack: "Analytics Service", category: "External Systems", searchTerms: "analytics service google analytics" },
159
+ // Text & Annotations
160
+ { type: "text", techStack: "Text Note", category: "Annotations", searchTerms: "text note annotation comment label" },
161
+ { type: "text", techStack: "Sticky Note", category: "Annotations", searchTerms: "sticky note annotation comment" },
162
+ { type: "text", techStack: "Comment", category: "Annotations", searchTerms: "comment annotation note text" },
163
+ // Grouping
164
+ { type: "group", techStack: "Logical Group", category: "Grouping", searchTerms: "group container boundary logical" },
165
+ { type: "group", techStack: "Network Boundary", category: "Grouping", searchTerms: "group network boundary vpc subnet" },
166
+ { type: "group", techStack: "Security Zone", category: "Grouping", searchTerms: "group security zone boundary" },
167
+ { type: "group", techStack: "Service Group", category: "Grouping", searchTerms: "group service container boundary" }
168
+ ];
169
+
170
+ // ../frontend/src/dsl/lexer.ts
171
+ var IDENT_START = /[A-Za-z_]/;
172
+ var IDENT_PART = /[A-Za-z0-9_]/;
173
+ var TEAM_PART = /[A-Za-z0-9_-]/;
174
+ var DIGIT = /[0-9]/;
175
+ function tokenizeLine(text, line) {
176
+ const tokens = [];
177
+ const diagnostics = [];
178
+ let i = 0;
179
+ const error = (message, col, length) => diagnostics.push({ severity: "error", message, line, col, length });
180
+ while (i < text.length) {
181
+ const ch = text[i];
182
+ if (ch === " " || ch === " " || ch === "\r") {
183
+ i++;
184
+ continue;
185
+ }
186
+ if (ch === "#") break;
187
+ const start = i;
188
+ if (ch === ":") {
189
+ const raw = stripTrailingComment(text.slice(i + 1));
190
+ tokens.push({ kind: "label", value: raw.trim(), col: start + 1, length: text.length - start });
191
+ break;
192
+ }
193
+ if (ch === '"') {
194
+ const { value, end, closed } = readString(text, i);
195
+ if (!closed) error("Unterminated string", start + 1, text.length - start);
196
+ tokens.push({ kind: "string", value, col: start + 1, length: end - start });
197
+ i = end;
198
+ continue;
199
+ }
200
+ if (ch === "[") {
201
+ const close = text.indexOf("]", i + 1);
202
+ if (close === -1) {
203
+ error("Missing ] after tech stack", start + 1, text.length - start);
204
+ tokens.push({ kind: "tech", value: text.slice(i + 1).trim(), col: start + 1, length: text.length - start });
205
+ break;
206
+ }
207
+ tokens.push({ kind: "tech", value: text.slice(i + 1, close).trim(), col: start + 1, length: close + 1 - start });
208
+ i = close + 1;
209
+ continue;
210
+ }
211
+ if (ch === "-") {
212
+ const arrow = ["->>", "-->", "->"].find((a) => text.startsWith(a, i));
213
+ const failed = text.startsWith("-x", i) && !IDENT_PART.test(text[i + 2] ?? "");
214
+ if (arrow || failed) {
215
+ const value = arrow ?? "-x";
216
+ tokens.push({ kind: "arrow", value, col: start + 1, length: value.length });
217
+ i += value.length;
218
+ continue;
219
+ }
220
+ if (DIGIT.test(text[i + 1] ?? "")) {
221
+ i++;
222
+ while (i < text.length && DIGIT.test(text[i])) i++;
223
+ tokens.push({ kind: "number", value: text.slice(start, i), col: start + 1, length: i - start });
224
+ continue;
225
+ }
226
+ }
227
+ if (DIGIT.test(ch)) {
228
+ while (i < text.length && DIGIT.test(text[i])) i++;
229
+ tokens.push({ kind: "number", value: text.slice(start, i), col: start + 1, length: i - start });
230
+ continue;
231
+ }
232
+ if (ch === "@") {
233
+ i++;
234
+ while (i < text.length && TEAM_PART.test(text[i])) i++;
235
+ if (i === start + 1) error("Expected a team name after @", start + 1, 1);
236
+ tokens.push({ kind: "team", value: text.slice(start + 1, i), col: start + 1, length: i - start });
237
+ continue;
238
+ }
239
+ if (IDENT_START.test(ch)) {
240
+ while (i < text.length && IDENT_PART.test(text[i])) i++;
241
+ tokens.push({ kind: "ident", value: text.slice(start, i), col: start + 1, length: i - start });
242
+ continue;
243
+ }
244
+ if (ch === "{" || ch === "}" || ch === ",") {
245
+ const kind = ch === "{" ? "lbrace" : ch === "}" ? "rbrace" : "comma";
246
+ tokens.push({ kind, value: ch, col: start + 1, length: 1 });
247
+ i++;
248
+ continue;
249
+ }
250
+ error(`Unexpected character '${ch}'`, start + 1, 1);
251
+ i++;
252
+ }
253
+ return { tokens, diagnostics };
254
+ }
255
+ function readString(text, start) {
256
+ let value = "";
257
+ let i = start + 1;
258
+ while (i < text.length) {
259
+ const ch = text[i];
260
+ if (ch === "\\" && i + 1 < text.length) {
261
+ value += text[i + 1];
262
+ i += 2;
263
+ continue;
264
+ }
265
+ if (ch === '"') return { value, end: i + 1, closed: true };
266
+ value += ch;
267
+ i++;
268
+ }
269
+ return { value, end: text.length, closed: false };
270
+ }
271
+ function stripTrailingComment(text) {
272
+ let depth = 0;
273
+ let quote = null;
274
+ for (let i = 0; i < text.length; i++) {
275
+ const ch = text[i];
276
+ if (quote) {
277
+ if (ch === "\\") i++;
278
+ else if (ch === quote) quote = null;
279
+ continue;
280
+ }
281
+ if (ch === '"') quote = ch;
282
+ else if (ch === "{" || ch === "[") depth++;
283
+ else if (ch === "}" || ch === "]") depth = Math.max(0, depth - 1);
284
+ else if (ch === "#" && depth === 0 && (i === 0 || /\s/.test(text[i - 1]))) return text.slice(0, i);
285
+ }
286
+ return text;
287
+ }
288
+ function bracketDepth(text) {
289
+ let depth = 0;
290
+ let inString = false;
291
+ for (let i = 0; i < text.length; i++) {
292
+ const ch = text[i];
293
+ if (inString) {
294
+ if (ch === "\\") i++;
295
+ else if (ch === '"') inString = false;
296
+ continue;
297
+ }
298
+ if (ch === '"') inString = true;
299
+ else if (ch === "{" || ch === "[") depth++;
300
+ else if (ch === "}" || ch === "]") depth--;
301
+ }
302
+ return depth;
303
+ }
304
+
305
+ // ../frontend/src/dsl/parser.ts
306
+ var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
307
+ var DEFAULT_TECH = "Rectangle";
308
+ var DEFAULT_GROUP_TECH = "Logical Group";
309
+ var MAX_SCENARIOS = 32;
310
+ var techByName = new Map(
311
+ componentCatalog.map((c) => [c.techStack.toLowerCase(), { type: c.type, techStack: c.techStack }])
312
+ );
313
+ function parse(source) {
314
+ return new Parser(source).run();
315
+ }
316
+ var Parser = class {
317
+ lines;
318
+ diagnostics = [];
319
+ nodes = /* @__PURE__ */ new Map();
320
+ edges = [];
321
+ useCases = [];
322
+ references = [];
323
+ stack = [];
324
+ title;
325
+ constructor(source) {
326
+ this.lines = source.split("\n");
327
+ }
328
+ run() {
329
+ for (let i = 0; i < this.lines.length; i++) {
330
+ i = this.parseLine(i);
331
+ }
332
+ for (const frame of this.stack) {
333
+ const what = frame.kind === "group" ? `group '${frame.id}'` : frame.kind === "usecase" ? `usecase '${frame.useCase.name}'` : frame.kind === "alt" ? `alt '${frame.name}'` : "par block";
334
+ this.error(`Missing } to close ${what}`, frame.loc);
335
+ }
336
+ this.createImplicitNodes();
337
+ const diagram = {
338
+ title: this.title,
339
+ nodes: [...this.nodes.values()],
340
+ edges: this.edges,
341
+ useCases: this.useCases.map(({ useCase, body }) => this.buildUseCase(useCase, body))
342
+ };
343
+ this.diagnostics.sort((a, b) => a.line - b.line || a.col - b.col);
344
+ return { diagram, diagnostics: this.diagnostics };
345
+ }
346
+ /** Parses the line at `index` and returns the index of the last line it consumed. */
347
+ parseLine(index) {
348
+ const line = index + 1;
349
+ const { tokens, diagnostics } = tokenizeLine(this.lines[index], line);
350
+ this.diagnostics.push(...diagnostics);
351
+ if (tokens.length === 0 || diagnostics.some((d) => d.severity === "error")) return index;
352
+ const [first, second] = tokens;
353
+ const loc = (t) => ({ line, col: t.col, length: t.length });
354
+ if (first.kind === "rbrace") {
355
+ if (this.stack.length === 0) this.error("Unmatched }", loc(first));
356
+ else this.closeFrame();
357
+ if (second?.kind === "ident" && second.value === "alt") this.parseAlt(tokens.slice(1), line);
358
+ else if (second) this.error("Unexpected input after }", loc(second));
359
+ return index;
360
+ }
361
+ if (first.kind === "ident" && second?.kind === "arrow") {
362
+ return this.parseConnection(tokens, index);
363
+ }
364
+ if (first.kind !== "ident") {
365
+ this.error("Expected a node, connection, group or usecase", loc(first));
366
+ return index;
367
+ }
368
+ switch (first.value) {
369
+ case "title":
370
+ this.parseTitle(tokens, line);
371
+ break;
372
+ case "group":
373
+ this.parseGroup(tokens, line);
374
+ break;
375
+ case "usecase":
376
+ this.parseUseCase(tokens, line);
377
+ break;
378
+ case "par":
379
+ this.parsePar(tokens, line);
380
+ break;
381
+ case "alt":
382
+ this.parseAlt(tokens, line);
383
+ break;
384
+ default:
385
+ this.parseNode(tokens, line);
386
+ }
387
+ return index;
388
+ }
389
+ parseTitle(tokens, line) {
390
+ const value = tokens[1];
391
+ if (!value || value.kind !== "string" && value.kind !== "ident") {
392
+ this.error('Expected a title, e.g. title "My System"', this.locOf(tokens[0], line));
393
+ return;
394
+ }
395
+ this.title = value.value;
396
+ this.expectEnd(tokens, 2, line);
397
+ }
398
+ parseGroup(tokens, line) {
399
+ const keyword = tokens[0];
400
+ const top = this.top();
401
+ if (top && top.kind !== "group") {
402
+ this.error("Groups cannot be declared inside a usecase", this.locOf(keyword, line));
403
+ return;
404
+ }
405
+ const idToken = tokens[1];
406
+ if (idToken?.kind !== "ident") {
407
+ this.error('Expected a group id, e.g. group vpc "AWS VPC" {', this.locOf(keyword, line));
408
+ return;
409
+ }
410
+ let i = 2;
411
+ let name = idToken.value;
412
+ let techStack = DEFAULT_GROUP_TECH;
413
+ if (tokens[i]?.kind === "string") name = tokens[i++].value;
414
+ if (tokens[i]?.kind === "tech") {
415
+ const tech = tokens[i++];
416
+ const resolved = this.resolveTech(tech, line);
417
+ if (resolved.type === "group") techStack = resolved.techStack;
418
+ else this.warning(`'${tech.value}' is not a group style; using ${DEFAULT_GROUP_TECH}`, this.locOf(tech, line));
419
+ }
420
+ let position;
421
+ if (tokens[i]?.kind === "ident" && tokens[i].value === "pos") {
422
+ const [x, comma, y] = [tokens[i + 1], tokens[i + 2], tokens[i + 3]];
423
+ if (x?.kind !== "number" || comma?.kind !== "comma" || y?.kind !== "number") {
424
+ this.error("Expected pos x,y", this.locOf(tokens[i], line));
425
+ return;
426
+ }
427
+ position = { x: Number(x.value), y: Number(y.value) };
428
+ i += 4;
429
+ }
430
+ if (tokens[i]?.kind !== "lbrace") {
431
+ this.error("Expected { after group header", this.locOf(tokens[i] ?? tokens[i - 1], line));
432
+ return;
433
+ }
434
+ this.expectEnd(tokens, i + 1, line);
435
+ const loc = this.locOf(idToken, line);
436
+ this.addNode({ id: idToken.value, kind: "group", name, type: "group", techStack, parent: this.currentGroup(), position, loc });
437
+ this.stack.push({ kind: "group", id: idToken.value, loc });
438
+ }
439
+ parseUseCase(tokens, line) {
440
+ const keyword = tokens[0];
441
+ if (this.stack.length > 0) {
442
+ this.error("A usecase must be declared at the top level", this.locOf(keyword, line));
443
+ return;
444
+ }
445
+ const nameToken = tokens[1];
446
+ if (nameToken?.kind !== "string" && nameToken?.kind !== "ident") {
447
+ this.error('Expected a usecase name, e.g. usecase "Place order" {', this.locOf(keyword, line));
448
+ return;
449
+ }
450
+ let i = 2;
451
+ let description;
452
+ if (tokens[i]?.kind === "string") description = tokens[i++].value;
453
+ if (tokens[i]?.kind !== "lbrace") {
454
+ this.error("Expected { after usecase name", this.locOf(tokens[i] ?? tokens[i - 1], line));
455
+ return;
456
+ }
457
+ this.expectEnd(tokens, i + 1, line);
458
+ const loc = this.locOf(nameToken, line);
459
+ const useCase = { id: this.useCaseId(nameToken.value), name: nameToken.value, description, steps: [], scenarios: [], loc };
460
+ const frame = { kind: "usecase", useCase, body: { items: [] }, parCount: 0, loc };
461
+ this.useCases.push({ useCase, body: frame.body });
462
+ this.stack.push(frame);
463
+ }
464
+ parseAlt(tokens, line) {
465
+ const keyword = tokens[0];
466
+ const top = this.top();
467
+ if (top?.kind !== "usecase" && top?.kind !== "alt") {
468
+ this.error(top?.kind === "par" ? "alt blocks cannot be inside a par block" : "alt is only allowed inside a usecase", this.locOf(keyword, line));
469
+ return;
470
+ }
471
+ const nameToken = tokens[1];
472
+ if (nameToken?.kind !== "string" && nameToken?.kind !== "ident") {
473
+ this.error('Expected a scenario name, e.g. alt "Not found" {', this.locOf(keyword, line));
474
+ return;
475
+ }
476
+ if (tokens[2]?.kind !== "lbrace") {
477
+ this.error("Expected { after alt name", this.locOf(tokens[2] ?? nameToken, line));
478
+ return;
479
+ }
480
+ this.expectEnd(tokens, 3, line);
481
+ const owner = top.body;
482
+ let set = owner.openAlt;
483
+ if (!set) {
484
+ set = { kind: "alt", branches: [] };
485
+ owner.items.push(set);
486
+ }
487
+ const loc = this.locOf(nameToken, line);
488
+ if (set.branches.some((b) => b.name === nameToken.value)) this.warning(`Duplicate alt name '${nameToken.value}'`, loc);
489
+ const body = { items: [] };
490
+ set.branches.push({ name: nameToken.value, items: body.items, loc });
491
+ owner.openAlt = void 0;
492
+ this.stack.push({ kind: "alt", name: nameToken.value, body, owner, set, loc });
493
+ }
494
+ closeFrame() {
495
+ const frame = this.stack.pop();
496
+ if (frame?.kind === "alt") frame.owner.openAlt = frame.set;
497
+ }
498
+ parsePar(tokens, line) {
499
+ const keyword = tokens[0];
500
+ const top = this.top();
501
+ if (top?.kind !== "usecase" && top?.kind !== "alt") {
502
+ this.error(top?.kind === "par" ? "par blocks cannot be nested" : "par is only allowed inside a usecase", this.locOf(keyword, line));
503
+ return;
504
+ }
505
+ if (tokens[1]?.kind !== "lbrace") {
506
+ this.error("Expected { after par", this.locOf(tokens[1] ?? keyword, line));
507
+ return;
508
+ }
509
+ this.expectEnd(tokens, 2, line);
510
+ top.body.openAlt = void 0;
511
+ const useCase = this.currentUseCase();
512
+ useCase.parCount++;
513
+ this.stack.push({ kind: "par", group: useCase.parCount, loc: this.locOf(keyword, line) });
514
+ }
515
+ parseNode(tokens, line) {
516
+ const idToken = tokens[0];
517
+ const loc = this.locOf(idToken, line);
518
+ if (this.inUseCase()) {
519
+ this.error("Declare nodes outside usecase blocks; steps look like a -> b : label", loc);
520
+ return;
521
+ }
522
+ const node = { id: idToken.value, kind: "component", name: idToken.value, type: "shape", techStack: DEFAULT_TECH, parent: this.currentGroup(), loc };
523
+ let strings = 0;
524
+ for (let i = 1; i < tokens.length; i++) {
525
+ const t = tokens[i];
526
+ if (t.kind === "string" && strings < 2) {
527
+ if (strings++ === 0) node.name = t.value;
528
+ else node.description = t.value;
529
+ } else if (t.kind === "tech") {
530
+ const resolved = this.resolveTech(t, line);
531
+ node.type = resolved.type;
532
+ node.techStack = resolved.techStack;
533
+ node.kind = resolved.type === "text" ? "text" : resolved.type === "group" ? "group" : "component";
534
+ } else if (t.kind === "team") {
535
+ node.ownerTeam = t.value;
536
+ } else if (t.kind === "ident" && t.value === "pos") {
537
+ const [x, comma, y] = [tokens[i + 1], tokens[i + 2], tokens[i + 3]];
538
+ if (x?.kind !== "number" || comma?.kind !== "comma" || y?.kind !== "number") {
539
+ this.error("Expected pos x,y", this.locOf(t, line));
540
+ return;
541
+ }
542
+ node.position = { x: Number(x.value), y: Number(y.value) };
543
+ i += 3;
544
+ } else if (t.kind === "label" || t.kind === "arrow") {
545
+ this.error(`Expected '${idToken.value} -> target' for a connection`, this.locOf(t, line));
546
+ return;
547
+ } else {
548
+ this.error(`Unexpected ${describe(t)} in node declaration`, this.locOf(t, line));
549
+ return;
550
+ }
551
+ }
552
+ this.addNode(node);
553
+ }
554
+ parseConnection(tokens, index) {
555
+ const line = index + 1;
556
+ const [fromToken, arrowToken, toToken, labelToken] = tokens;
557
+ if (toToken?.kind !== "ident") {
558
+ this.error("Expected a target id after the arrow", this.locOf(toToken ?? arrowToken, line));
559
+ return index;
560
+ }
561
+ if (tokens.length > 4 || labelToken && labelToken.kind !== "label") {
562
+ this.error("Expected : before the connection label", this.locOf(labelToken, line));
563
+ return index;
564
+ }
565
+ let label = labelToken?.value ?? "";
566
+ let lastLine = index;
567
+ if (bracketDepth(label) > 0) {
568
+ while (bracketDepth(label) > 0 && lastLine + 1 < this.lines.length) {
569
+ lastLine++;
570
+ label += "\n" + this.lines[lastLine];
571
+ }
572
+ if (bracketDepth(label) > 0) this.error("Unclosed { or [ in payload", this.locOf(labelToken, line));
573
+ label = label.trimEnd();
574
+ }
575
+ const loc = { line, col: fromToken.col, length: (labelToken ?? toToken).col + (labelToken ?? toToken).length - fromToken.col };
576
+ const from = fromToken.value;
577
+ const to = toToken.value;
578
+ const arrow = arrowToken.value;
579
+ this.references.push({ id: from, loc: this.locOf(fromToken, line) }, { id: to, loc: this.locOf(toToken, line) });
580
+ const top = this.top();
581
+ if (top?.kind === "usecase" || top?.kind === "alt" || top?.kind === "par") {
582
+ const body = this.currentBody();
583
+ body.openAlt = void 0;
584
+ body.items.push({ kind: "step", step: { from, to, arrow, label, parallelGroup: top.kind === "par" ? top.group : void 0, loc } });
585
+ } else if (arrow === "-x") {
586
+ this.error("'-x' marks a failed call and is only allowed in use case steps", this.locOf(arrowToken, line));
587
+ } else {
588
+ const base = `${from}->${to}`;
589
+ const count = this.edges.filter((e) => e.id === base || e.id.startsWith(`${base}#`)).length;
590
+ this.edges.push({ id: count ? `${base}#${count + 1}` : base, source: from, target: to, label: unquote(label) || void 0, loc });
591
+ }
592
+ return lastLine;
593
+ }
594
+ buildUseCase(useCase, body) {
595
+ let paths = expand(body.items);
596
+ if (paths.length > MAX_SCENARIOS) {
597
+ this.warning(`Too many scenarios (${paths.length}); only the first ${MAX_SCENARIOS} are kept`, useCase.loc);
598
+ paths = paths.slice(0, MAX_SCENARIOS);
599
+ }
600
+ const taken = /* @__PURE__ */ new Set();
601
+ const scenarios = paths.map((path) => {
602
+ const base = path.names.length ? slug(path.names.join(" ")) || "scenario" : "main";
603
+ let id = base;
604
+ for (let n = 2; taken.has(id); n++) id = `${base}-${n}`;
605
+ taken.add(id);
606
+ const steps2 = this.buildSteps(useCase.id, id, path.steps);
607
+ const entry2 = steps2[0];
608
+ const failedEntry = !!entry2 && (entry2.failed || (entry2.statusCode ?? 0) >= 400);
609
+ return {
610
+ id,
611
+ name: path.names.length ? path.names.join(" \u203A ") : useCase.name,
612
+ outcome: failedEntry ? "error" : "success",
613
+ steps: steps2,
614
+ loc: path.locs.at(-1) ?? useCase.loc
615
+ };
616
+ });
617
+ const steps = scenarios[0]?.steps ?? [];
618
+ const entry = steps[0];
619
+ return {
620
+ ...useCase,
621
+ entryServiceId: entry?.fromServiceId,
622
+ endpoint: entry?.httpMethod ? `${entry.httpMethod} ${entry.endpoint}` : void 0,
623
+ steps,
624
+ scenarios
625
+ };
626
+ }
627
+ buildSteps(useCaseId, scenarioId, raw) {
628
+ const steps = [];
629
+ const answered = /* @__PURE__ */ new Set();
630
+ const prefix = scenarioId === "main" ? useCaseId : `${useCaseId}-${scenarioId}`;
631
+ for (const step of raw) {
632
+ if (step.arrow === "-->") {
633
+ const request = [...steps].reverse().find((s) => s.fromServiceId === step.to && s.toServiceId === step.from && !answered.has(s) && !s.failed);
634
+ if (!request) {
635
+ this.warning(`Response has no matching request from '${step.to}' to '${step.from}'`, step.loc);
636
+ continue;
637
+ }
638
+ answered.add(request);
639
+ const status = step.label.match(/^(\d{3})\b\s*([\s\S]*)$/);
640
+ if (status) request.statusCode = Number(status[1]);
641
+ const payload = splitPayload(status ? status[2] : step.label);
642
+ const body = payload.body ?? payload.text;
643
+ if (body) {
644
+ request.responseBody = body;
645
+ request.responseFormat = payload.body ? payload.format : "FREE_TEXT";
646
+ }
647
+ if (request.executionType === "ASYNC_FIRE_AND_FORGET") request.executionType = "ASYNC_REQUEST_RESPONSE";
648
+ continue;
649
+ }
650
+ const label = parseStepLabel(step.label);
651
+ const target = this.nodes.get(step.to);
652
+ const isAsync = step.arrow === "->>";
653
+ steps.push({
654
+ id: `${prefix}-${steps.length + 1}`,
655
+ stepOrder: steps.length,
656
+ stepName: label.name || `${step.from} \u2192 ${step.to}`,
657
+ fromServiceId: step.from,
658
+ toServiceId: step.to,
659
+ protocol: inferProtocol(isAsync, label.method, target),
660
+ httpMethod: label.method ?? "",
661
+ endpoint: label.endpoint ?? "",
662
+ requestFormat: label.format ?? "JSON",
663
+ requestBody: label.body,
664
+ responseFormat: "JSON",
665
+ description: label.description,
666
+ executionType: isAsync ? "ASYNC_FIRE_AND_FORGET" : "SYNC_REQUEST_RESPONSE",
667
+ parallelGroup: step.parallelGroup,
668
+ isParallel: step.parallelGroup !== void 0,
669
+ isConditional: false,
670
+ ...step.arrow === "-x" ? { failed: true } : {}
671
+ });
672
+ }
673
+ return steps;
674
+ }
675
+ createImplicitNodes() {
676
+ for (const ref of this.references) {
677
+ if (this.nodes.has(ref.id)) continue;
678
+ this.nodes.set(ref.id, { id: ref.id, kind: "component", name: ref.id, type: "shape", techStack: DEFAULT_TECH, implicit: true, loc: ref.loc });
679
+ }
680
+ }
681
+ addNode(node) {
682
+ const existing = this.nodes.get(node.id);
683
+ if (existing) {
684
+ this.error(`Duplicate id '${node.id}' (first declared on line ${existing.loc.line})`, node.loc);
685
+ return;
686
+ }
687
+ this.nodes.set(node.id, node);
688
+ }
689
+ resolveTech(token, line) {
690
+ const resolved = techByName.get(token.value.toLowerCase());
691
+ if (resolved) return resolved;
692
+ this.warning(`Unknown tech stack '${token.value}'; drawing a ${DEFAULT_TECH}`, this.locOf(token, line));
693
+ return { type: "shape", techStack: DEFAULT_TECH };
694
+ }
695
+ useCaseId(name) {
696
+ const base = slug(name) || "usecase";
697
+ const taken = new Set(this.useCases.map((u) => u.useCase.id));
698
+ let id = base;
699
+ for (let n = 2; taken.has(id); n++) id = `${base}-${n}`;
700
+ return id;
701
+ }
702
+ top() {
703
+ return this.stack[this.stack.length - 1];
704
+ }
705
+ currentGroup() {
706
+ const top = this.top();
707
+ return top?.kind === "group" ? top.id : void 0;
708
+ }
709
+ currentUseCase() {
710
+ for (let i = this.stack.length - 1; i >= 0; i--) {
711
+ const frame = this.stack[i];
712
+ if (frame.kind === "usecase") return frame;
713
+ }
714
+ return void 0;
715
+ }
716
+ currentBody() {
717
+ for (let i = this.stack.length - 1; i >= 0; i--) {
718
+ const frame = this.stack[i];
719
+ if (frame.kind === "usecase" || frame.kind === "alt") return frame.body;
720
+ }
721
+ return void 0;
722
+ }
723
+ inUseCase() {
724
+ return this.currentUseCase() !== void 0;
725
+ }
726
+ expectEnd(tokens, from, line) {
727
+ if (tokens.length > from) this.error(`Unexpected ${describe(tokens[from])}`, this.locOf(tokens[from], line));
728
+ }
729
+ locOf(token, line) {
730
+ return token ? { line, col: token.col, length: token.length } : { line, col: 1, length: 1 };
731
+ }
732
+ error(message, loc) {
733
+ this.diagnostics.push({ severity: "error", message, ...loc });
734
+ }
735
+ warning(message, loc) {
736
+ if (this.diagnostics.some((d) => d.message === message && d.line === loc.line && d.col === loc.col)) return;
737
+ this.diagnostics.push({ severity: "warning", message, ...loc });
738
+ }
739
+ };
740
+ function expand(items) {
741
+ let paths = [{ names: [], locs: [], steps: [] }];
742
+ for (const item of items) {
743
+ if (item.kind === "step") {
744
+ for (const path of paths) path.steps.push(item.step);
745
+ continue;
746
+ }
747
+ const next = [];
748
+ for (const path of paths) {
749
+ for (const branch of item.branches) {
750
+ for (const sub of expand(branch.items)) {
751
+ next.push({
752
+ names: [...path.names, branch.name, ...sub.names],
753
+ locs: [...path.locs, branch.loc, ...sub.locs],
754
+ steps: [...path.steps, ...sub.steps]
755
+ });
756
+ }
757
+ }
758
+ if (next.length > MAX_SCENARIOS * 4) break;
759
+ }
760
+ paths = next;
761
+ }
762
+ return paths;
763
+ }
764
+ function slug(text) {
765
+ return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
766
+ }
767
+ function splitPayload(input) {
768
+ const text = input.trim();
769
+ const formats = { json: "JSON", xml: "XML", text: "FREE_TEXT" };
770
+ const leading = text.match(/^(json|xml|text)\s+([\s\S]+)$/i);
771
+ if (leading) return { text: "", format: formats[leading[1].toLowerCase()], body: leading[2].trim() };
772
+ const keyed = text.match(/^([\s\S]*?)\s+(json|xml)\s+([{[<][\s\S]*)$/i);
773
+ if (keyed) return { text: keyed[1].trim(), format: formats[keyed[2].toLowerCase()], body: keyed[3].trim() };
774
+ const bare = text.search(/(^|\s)[{[<]/);
775
+ if (bare !== -1) {
776
+ const body = text.slice(bare).trim();
777
+ return { text: text.slice(0, bare).trim(), format: body.startsWith("<") ? "XML" : "JSON", body };
778
+ }
779
+ return { text: unquote(text), format: "JSON" };
780
+ }
781
+ function parseStepLabel(input) {
782
+ const http = input.trim().match(/^([A-Z]+)\s+(\S+)\s*([\s\S]*)$/);
783
+ if (http && HTTP_METHODS.includes(http[1])) {
784
+ const payload2 = splitPayload(http[3]);
785
+ return {
786
+ name: `${http[1]} ${http[2]}`,
787
+ method: http[1],
788
+ endpoint: http[2],
789
+ description: payload2.text || void 0,
790
+ format: payload2.body ? payload2.format : void 0,
791
+ body: payload2.body
792
+ };
793
+ }
794
+ const payload = splitPayload(input);
795
+ return { name: payload.text, format: payload.body ? payload.format : void 0, body: payload.body };
796
+ }
797
+ function inferProtocol(isAsync, method, target) {
798
+ if (isAsync || target?.type === "queue") return "MESSAGING";
799
+ if (!method) return "OTHER";
800
+ switch (target?.techStack) {
801
+ case "GraphQL":
802
+ return "GRAPHQL";
803
+ case "gRPC":
804
+ return "GRPC";
805
+ case "SOAP API":
806
+ return "SOAP";
807
+ default:
808
+ return "REST";
809
+ }
810
+ }
811
+ function unquote(text) {
812
+ const t = text.trim();
813
+ return t.length >= 2 && t.startsWith('"') && t.endsWith('"') ? t.slice(1, -1) : t;
814
+ }
815
+ function describe(token) {
816
+ switch (token.kind) {
817
+ case "string":
818
+ return `string "${token.value}"`;
819
+ case "tech":
820
+ return `[${token.value}]`;
821
+ case "team":
822
+ return `@${token.value}`;
823
+ case "label":
824
+ return "':'";
825
+ case "lbrace":
826
+ case "rbrace":
827
+ case "comma":
828
+ case "arrow":
829
+ return `'${token.value}'`;
830
+ default:
831
+ return `'${token.value}'`;
832
+ }
833
+ }
834
+
835
+ // src/cli.ts
836
+ var VERSION = true ? "0.1.0" : "dev";
837
+ var USAGE = `Usage:
838
+ proschi check [--strict] [--format text|github|json] <file|dir>...
839
+ proschi parse <file>
840
+ proschi --version
841
+
842
+ check Reports errors and warnings. Directories are searched for *.proschi files.
843
+ Exits with 1 if any file has an error (or a warning, with --strict).
844
+ parse Prints {"diagram", "diagnostics"} as JSON; the shape is described by
845
+ schema/proschi-diagram.schema.json.`;
846
+ function collectFiles(paths) {
847
+ const out = [];
848
+ const walk = (dir) => {
849
+ for (const entry of (0, import_node_fs.readdirSync)(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
850
+ if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
851
+ const full = (0, import_node_path.join)(dir, entry.name);
852
+ if (entry.isDirectory()) walk(full);
853
+ else if (entry.name.endsWith(".proschi")) out.push(full);
854
+ }
855
+ };
856
+ for (const p of paths) {
857
+ if ((0, import_node_fs.statSync)(p).isDirectory()) walk(p);
858
+ else out.push(p);
859
+ }
860
+ return out;
861
+ }
862
+ function displayPath(file) {
863
+ const rel = (0, import_node_path.relative)(process.cwd(), file);
864
+ return rel && !rel.startsWith("..") && !(0, import_node_path.isAbsolute)(rel) ? rel : file;
865
+ }
866
+ function format(results, style) {
867
+ if (style === "json") return JSON.stringify(results, null, 2);
868
+ const lines = [];
869
+ for (const { file, diagnostics } of results) {
870
+ for (const d of diagnostics) {
871
+ if (style === "github") {
872
+ const level = d.severity === "error" ? "error" : "warning";
873
+ lines.push(`::${level} file=${file},line=${d.line},col=${d.col},title=proschi::${d.message.replace(/%/g, "%25").replace(/\r?\n/g, "%0A")}`);
874
+ } else {
875
+ lines.push(`${file}:${d.line}:${d.col}: ${d.severity}: ${d.message}`);
876
+ }
877
+ }
878
+ }
879
+ if (style === "text") {
880
+ const errors = results.reduce((n, r) => n + r.diagnostics.filter((d) => d.severity === "error").length, 0);
881
+ const warnings = results.reduce((n, r) => n + r.diagnostics.filter((d) => d.severity === "warning").length, 0);
882
+ const files = results.length === 1 ? "1 file" : `${results.length} files`;
883
+ lines.push(errors || warnings ? `${files}: ${errors} error(s), ${warnings} warning(s)` : `${files}: no problems`);
884
+ }
885
+ return lines.join("\n");
886
+ }
887
+ function run(argv, out = console.log, err = console.error) {
888
+ const [command, ...rest] = argv;
889
+ if (command === "--version" || command === "-v") {
890
+ out(VERSION);
891
+ return 0;
892
+ }
893
+ if (!command || command === "--help" || command === "-h" || command === "help") {
894
+ out(USAGE);
895
+ return command ? 0 : 2;
896
+ }
897
+ if (command === "parse") {
898
+ if (rest.length !== 1) {
899
+ err(USAGE);
900
+ return 2;
901
+ }
902
+ out(JSON.stringify(parse((0, import_node_fs.readFileSync)(rest[0], "utf8")), null, 2));
903
+ return 0;
904
+ }
905
+ if (command === "check") {
906
+ let strict = false;
907
+ let style = "text";
908
+ const paths = [];
909
+ for (let i = 0; i < rest.length; i++) {
910
+ const arg = rest[i];
911
+ if (arg === "--strict") strict = true;
912
+ else if (arg === "--format") {
913
+ const value = rest[++i];
914
+ if (value !== "text" && value !== "github" && value !== "json") {
915
+ err(`Unknown format '${value ?? ""}'; use text, github or json`);
916
+ return 2;
917
+ }
918
+ style = value;
919
+ } else if (arg.startsWith("-")) {
920
+ err(`Unknown option ${arg}
921
+
922
+ ${USAGE}`);
923
+ return 2;
924
+ } else paths.push(arg);
925
+ }
926
+ if (paths.length === 0) {
927
+ err(USAGE);
928
+ return 2;
929
+ }
930
+ let files;
931
+ try {
932
+ files = collectFiles(paths);
933
+ } catch (e) {
934
+ err(String(e.message));
935
+ return 2;
936
+ }
937
+ const results = files.map((file) => ({
938
+ file: displayPath(file),
939
+ diagnostics: parse((0, import_node_fs.readFileSync)(file, "utf8")).diagnostics
940
+ }));
941
+ const text = format(results, style);
942
+ if (text) out(text);
943
+ const failed = results.some((r) => r.diagnostics.some((d) => d.severity === "error" || strict));
944
+ return failed ? 1 : 0;
945
+ }
946
+ err(`Unknown command '${command}'
947
+
948
+ ${USAGE}`);
949
+ return 2;
950
+ }
951
+ if (true) process.exitCode = run(process.argv.slice(2));
952
+ // Annotate the CommonJS export names for ESM import in node:
953
+ 0 && (module.exports = {
954
+ collectFiles,
955
+ format,
956
+ run
957
+ });