@meltemi/diagrams-cli 0.0.0-stage → 1.9.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.
Files changed (163) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +58 -2
  3. package/dist/assets-command.d.ts +16 -0
  4. package/dist/assets-command.d.ts.map +1 -0
  5. package/dist/assets-command.js +145 -0
  6. package/dist/assets-command.js.map +1 -0
  7. package/dist/assist-command.d.ts +19 -0
  8. package/dist/assist-command.d.ts.map +1 -0
  9. package/dist/assist-command.js +268 -0
  10. package/dist/assist-command.js.map +1 -0
  11. package/dist/auth-command.d.ts +14 -0
  12. package/dist/auth-command.d.ts.map +1 -0
  13. package/dist/auth-command.js +233 -0
  14. package/dist/auth-command.js.map +1 -0
  15. package/dist/bin.d.ts +3 -0
  16. package/dist/bin.d.ts.map +1 -0
  17. package/dist/bin.js +60 -0
  18. package/dist/bin.js.map +1 -0
  19. package/dist/build-command.d.ts +23 -0
  20. package/dist/build-command.d.ts.map +1 -0
  21. package/dist/build-command.js +123 -0
  22. package/dist/build-command.js.map +1 -0
  23. package/dist/cli.d.ts +46 -0
  24. package/dist/cli.d.ts.map +1 -0
  25. package/dist/cli.js +823 -0
  26. package/dist/cli.js.map +1 -0
  27. package/dist/cloud-client.d.ts +246 -0
  28. package/dist/cloud-client.d.ts.map +1 -0
  29. package/dist/cloud-client.js +591 -0
  30. package/dist/cloud-client.js.map +1 -0
  31. package/dist/cloud-command.d.ts +56 -0
  32. package/dist/cloud-command.d.ts.map +1 -0
  33. package/dist/cloud-command.js +81 -0
  34. package/dist/cloud-command.js.map +1 -0
  35. package/dist/cloud-context.d.ts +106 -0
  36. package/dist/cloud-context.d.ts.map +1 -0
  37. package/dist/cloud-context.js +185 -0
  38. package/dist/cloud-context.js.map +1 -0
  39. package/dist/cloud-errors.d.ts +37 -0
  40. package/dist/cloud-errors.d.ts.map +1 -0
  41. package/dist/cloud-errors.js +46 -0
  42. package/dist/cloud-errors.js.map +1 -0
  43. package/dist/cloud-host.d.ts +72 -0
  44. package/dist/cloud-host.d.ts.map +1 -0
  45. package/dist/cloud-host.js +129 -0
  46. package/dist/cloud-host.js.map +1 -0
  47. package/dist/cloud-manifest.d.ts +12 -0
  48. package/dist/cloud-manifest.d.ts.map +1 -0
  49. package/dist/cloud-manifest.js +21 -0
  50. package/dist/cloud-manifest.js.map +1 -0
  51. package/dist/cloud-paths.d.ts +48 -0
  52. package/dist/cloud-paths.d.ts.map +1 -0
  53. package/dist/cloud-paths.js +104 -0
  54. package/dist/cloud-paths.js.map +1 -0
  55. package/dist/credentials.d.ts +107 -0
  56. package/dist/credentials.d.ts.map +1 -0
  57. package/dist/credentials.js +243 -0
  58. package/dist/credentials.js.map +1 -0
  59. package/dist/export-command.d.ts +35 -0
  60. package/dist/export-command.d.ts.map +1 -0
  61. package/dist/export-command.js +524 -0
  62. package/dist/export-command.js.map +1 -0
  63. package/dist/import-command.d.ts +20 -0
  64. package/dist/import-command.d.ts.map +1 -0
  65. package/dist/import-command.js +254 -0
  66. package/dist/import-command.js.map +1 -0
  67. package/dist/index.d.ts +4 -0
  68. package/dist/index.d.ts.map +1 -0
  69. package/dist/index.js +4 -0
  70. package/dist/index.js.map +1 -0
  71. package/dist/init-command.d.ts +28 -0
  72. package/dist/init-command.d.ts.map +1 -0
  73. package/dist/init-command.js +199 -0
  74. package/dist/init-command.js.map +1 -0
  75. package/dist/login-listener.d.ts +58 -0
  76. package/dist/login-listener.d.ts.map +1 -0
  77. package/dist/login-listener.js +173 -0
  78. package/dist/login-listener.js.map +1 -0
  79. package/dist/messages/en.d.ts +533 -0
  80. package/dist/messages/en.d.ts.map +1 -0
  81. package/dist/messages/en.js +803 -0
  82. package/dist/messages/en.js.map +1 -0
  83. package/dist/messages/es.d.ts +3 -0
  84. package/dist/messages/es.d.ts.map +1 -0
  85. package/dist/messages/es.js +764 -0
  86. package/dist/messages/es.js.map +1 -0
  87. package/dist/messages/index.d.ts +442 -0
  88. package/dist/messages/index.d.ts.map +1 -0
  89. package/dist/messages/index.js +42 -0
  90. package/dist/messages/index.js.map +1 -0
  91. package/dist/messages/push-pull-en.d.ts +84 -0
  92. package/dist/messages/push-pull-en.d.ts.map +1 -0
  93. package/dist/messages/push-pull-en.js +146 -0
  94. package/dist/messages/push-pull-en.js.map +1 -0
  95. package/dist/messages/push-pull-es.d.ts +7 -0
  96. package/dist/messages/push-pull-es.d.ts.map +1 -0
  97. package/dist/messages/push-pull-es.js +144 -0
  98. package/dist/messages/push-pull-es.js.map +1 -0
  99. package/dist/open-url.d.ts +11 -0
  100. package/dist/open-url.d.ts.map +1 -0
  101. package/dist/open-url.js +36 -0
  102. package/dist/open-url.js.map +1 -0
  103. package/dist/plugins-command.d.ts +15 -0
  104. package/dist/plugins-command.d.ts.map +1 -0
  105. package/dist/plugins-command.js +106 -0
  106. package/dist/plugins-command.js.map +1 -0
  107. package/dist/preview.d.ts +54 -0
  108. package/dist/preview.d.ts.map +1 -0
  109. package/dist/preview.js +249 -0
  110. package/dist/preview.js.map +1 -0
  111. package/dist/project-errors.d.ts +17 -0
  112. package/dist/project-errors.d.ts.map +1 -0
  113. package/dist/project-errors.js +85 -0
  114. package/dist/project-errors.js.map +1 -0
  115. package/dist/projects-command.d.ts +13 -0
  116. package/dist/projects-command.d.ts.map +1 -0
  117. package/dist/projects-command.js +85 -0
  118. package/dist/projects-command.js.map +1 -0
  119. package/dist/publish-command.d.ts +41 -0
  120. package/dist/publish-command.d.ts.map +1 -0
  121. package/dist/publish-command.js +311 -0
  122. package/dist/publish-command.js.map +1 -0
  123. package/dist/pull-command.d.ts +21 -0
  124. package/dist/pull-command.d.ts.map +1 -0
  125. package/dist/pull-command.js +300 -0
  126. package/dist/pull-command.js.map +1 -0
  127. package/dist/push-command.d.ts +31 -0
  128. package/dist/push-command.d.ts.map +1 -0
  129. package/dist/push-command.js +391 -0
  130. package/dist/push-command.js.map +1 -0
  131. package/dist/push-review.d.ts +16 -0
  132. package/dist/push-review.d.ts.map +1 -0
  133. package/dist/push-review.js +72 -0
  134. package/dist/push-review.js.map +1 -0
  135. package/dist/push-set.d.ts +126 -0
  136. package/dist/push-set.d.ts.map +1 -0
  137. package/dist/push-set.js +289 -0
  138. package/dist/push-set.js.map +1 -0
  139. package/dist/push-validation.d.ts +20 -0
  140. package/dist/push-validation.d.ts.map +1 -0
  141. package/dist/push-validation.js +70 -0
  142. package/dist/push-validation.js.map +1 -0
  143. package/dist/redact.d.ts +9 -0
  144. package/dist/redact.d.ts.map +1 -0
  145. package/dist/redact.js +22 -0
  146. package/dist/redact.js.map +1 -0
  147. package/dist/review-command.d.ts +44 -0
  148. package/dist/review-command.d.ts.map +1 -0
  149. package/dist/review-command.js +348 -0
  150. package/dist/review-command.js.map +1 -0
  151. package/package.json +56 -3
  152. package/templates/c4/diagrams/views.meltemi +16 -0
  153. package/templates/c4/models/system.meltemi +23 -0
  154. package/templates/ci/github.yml +96 -0
  155. package/templates/ci/gitlab.yml +53 -0
  156. package/templates/flow/diagrams/overview.meltemi +15 -0
  157. package/templates/free/diagrams/slide.meltemi +39 -0
  158. package/templates/purchase/diagrams/architecture.meltemi +36 -0
  159. package/templates/purchase/diagrams/executive.meltemi +52 -0
  160. package/templates/purchase/diagrams/payment.meltemi +42 -0
  161. package/templates/purchase/models/commerce.meltemi +30 -0
  162. package/templates/purchase/stories/purchase.meltemi +38 -0
  163. package/templates/sequence/diagrams/sign-in.meltemi +36 -0
package/dist/cli.js ADDED
@@ -0,0 +1,823 @@
1
+ import { DEFAULT_LOCALE, isLocale, localeFromEnv, resolveLocale, setLocale, } from "@meltemi/diagrams-i18n";
2
+ import { readFileSync, statSync, writeFileSync } from "node:fs";
3
+ import { isAbsolute, relative, resolve, sep } from "node:path";
4
+ import { parseArgs } from "node:util";
5
+ import { planEdit } from "@meltemi/diagrams-edit";
6
+ import { LANGUAGE_VERSION, applyTextEdits, formatSource, sortDiagnostics, } from "@meltemi/diagrams-language";
7
+ import { BRAND_NAME, layoutDiagram } from "@meltemi/diagrams-layout";
8
+ import { convertOperation } from "@meltemi/diagrams-editor";
9
+ import { ProjectSessions } from "@meltemi/diagrams-editor/node";
10
+ import { diagnosticsToJson, stringifyJson, toJson } from "@meltemi/diagrams-model";
11
+ import { Workspace, formatOptionsFor, projectTheme, themeDefinitionsFor, toProjectPath, } from "@meltemi/diagrams-project";
12
+ import { createNodeHost } from "@meltemi/diagrams-project/node";
13
+ import { assetsCommand } from "./assets-command.js";
14
+ import { assistCommand } from "./assist-command.js";
15
+ import { buildCommand } from "./build-command.js";
16
+ import { cloudCommand, cloudContext, isCloudCommand, } from "./cloud-command.js";
17
+ import { catalogScript } from "@meltemi/diagrams-catalog/node";
18
+ import { exportCommand, noDiagramDiagnostic } from "./export-command.js";
19
+ import { importCommand } from "./import-command.js";
20
+ import { initCommand } from "./init-command.js";
21
+ import { argumentsError, messages } from "./messages/index.js";
22
+ import { pluginsCommand } from "./plugins-command.js";
23
+ import { previewCommand } from "./preview.js";
24
+ import { reviewCommand } from "./review-command.js";
25
+ export { FONT_POLICIES, MOTION_MODES, WRITABLE_FORMATS, exportFileName } from "./export-command.js";
26
+ /** Command name (decision 0002). */
27
+ export const CLI_NAME = "meltemi-diagrams";
28
+ /** Short alias installed next to the full command name. */
29
+ export const CLI_ALIAS = "mdg";
30
+ /** Extension of `.meltemi` sources (decision 0002). */
31
+ export const MELTEMI_EXTENSION = ".meltemi";
32
+ export const CLI_VERSION = "1.9.0";
33
+ export const EXIT_OK = 0;
34
+ export const EXIT_FINDINGS = 1;
35
+ export const EXIT_USAGE = 2;
36
+ /** The cloud commands: not signed in, or the service refused the token (run `login`). */
37
+ export const EXIT_AUTH = 3;
38
+ /** The language `--lang` asked for in this run: of the messages and of the files written. */
39
+ let contentLocale;
40
+ /** Project sessions of this run, writing content in the `--lang` language when given. */
41
+ function sessions(host) {
42
+ return new ProjectSessions({
43
+ toolVersion: CLI_VERSION,
44
+ ...(host ? { host } : {}),
45
+ ...(contentLocale ? { locale: contentLocale } : {}),
46
+ });
47
+ }
48
+ /**
49
+ * Takes `--lang <en|es>` (or `--lang=<en|es>`) out of the arguments, wherever it is written:
50
+ * every command accepts it.
51
+ */
52
+ export function takeLang(argv) {
53
+ const args = [];
54
+ let lang;
55
+ for (let i = 0; i < argv.length; i++) {
56
+ const arg = argv[i];
57
+ const value = arg === "--lang" ? argv[++i] : arg.startsWith("--lang=") ? arg.slice(7) : null;
58
+ if (value === null) {
59
+ args.push(arg);
60
+ continue;
61
+ }
62
+ if (!isLocale(value))
63
+ return { error: messages().common.lang };
64
+ lang = value;
65
+ }
66
+ return { args, lang };
67
+ }
68
+ /** Runs the command line and returns the exit code; `assist`, which waits for a model, a promise of it. */
69
+ export function run(input, io) {
70
+ // Messages: `--lang`, else the environment, else the system; files: `--lang`, else the project.
71
+ const machine = localeFromEnv(io.env ?? {}) ??
72
+ (io.locale !== undefined ? resolveLocale(io.locale) : DEFAULT_LOCALE);
73
+ setLocale(machine);
74
+ const taken = takeLang(input);
75
+ if ("error" in taken) {
76
+ io.stderr(`${CLI_NAME}: ${taken.error}\n`);
77
+ return EXIT_USAGE;
78
+ }
79
+ contentLocale = taken.lang;
80
+ setLocale(taken.lang ?? machine);
81
+ const argv = taken.args;
82
+ const [command, ...rest] = argv;
83
+ if (command === undefined || command === "-h" || command === "--help" || command === "help") {
84
+ io.stdout(messages().help.main(CLI_NAME, CLI_ALIAS));
85
+ return command === undefined ? EXIT_USAGE : EXIT_OK;
86
+ }
87
+ if (command === "-v" || command === "--version") {
88
+ io.stdout(`${CLI_VERSION}\n`);
89
+ return EXIT_OK;
90
+ }
91
+ if (command === "validate")
92
+ return validate(rest, io);
93
+ if (command === "export") {
94
+ return exportCommand(rest, {
95
+ name: CLI_NAME,
96
+ io,
97
+ display: (path) => displayPath(io.cwd, path),
98
+ projects: () => sessions(),
99
+ readSource: (path) => readSource(io, "export", path),
100
+ projectDiagnostics,
101
+ printDiagnostics: (out, path, diagnostics) => printDiagnostics({ ...io, stdout: out }, path, diagnostics),
102
+ });
103
+ }
104
+ if (command === "layout")
105
+ return layoutCommand(rest, io);
106
+ if (command === "convert")
107
+ return convertCommand(rest, io);
108
+ if (command === "format")
109
+ return formatCommand(rest, io);
110
+ if (command === "import") {
111
+ const projects = sessions();
112
+ return importCommand(rest, {
113
+ name: CLI_NAME,
114
+ stdout: io.stdout,
115
+ stderr: io.stderr,
116
+ cwd: io.cwd,
117
+ display: (path) => displayPath(io.cwd, path),
118
+ workspaceFor: (path) => projects.forFile(path),
119
+ contentLocale: (workspace) => projects.contentLocale(workspace),
120
+ });
121
+ }
122
+ if (command === "assets") {
123
+ const projects = sessions();
124
+ return assetsCommand(rest, {
125
+ name: CLI_NAME,
126
+ stdout: io.stdout,
127
+ stderr: io.stderr,
128
+ cwd: io.cwd,
129
+ display: (path) => displayPath(io.cwd, path),
130
+ workspaceFor: (path, isDirectory) => isDirectory ? projects.forFolder(path) : projects.forFile(path),
131
+ printProject: (workspace) => {
132
+ let errors = false;
133
+ for (const [path, diagnostics] of projectDiagnostics(workspace)) {
134
+ printDiagnostics(io, displayPath(io.cwd, path), diagnostics);
135
+ errors ||= diagnostics.some((d) => d.severity === "error");
136
+ }
137
+ return errors;
138
+ },
139
+ });
140
+ }
141
+ if (command === "assist") {
142
+ return assistCommand(rest, {
143
+ name: CLI_NAME,
144
+ stdout: io.stdout,
145
+ stderr: io.stderr,
146
+ cwd: io.cwd,
147
+ display: (path) => displayPath(io.cwd, path),
148
+ toolVersion: CLI_VERSION,
149
+ ...(contentLocale ? { locale: contentLocale } : {}),
150
+ env: io.env ?? process.env,
151
+ ...(io.ask ? { ask: io.ask } : {}),
152
+ });
153
+ }
154
+ if (command === "init") {
155
+ return initCommand(rest, {
156
+ name: CLI_NAME,
157
+ stdout: io.stdout,
158
+ stderr: io.stderr,
159
+ cwd: io.cwd,
160
+ display: (path) => displayPath(io.cwd, path),
161
+ toolVersion: CLI_VERSION,
162
+ languageVersion: LANGUAGE_VERSION,
163
+ });
164
+ }
165
+ if (command === "plugins") {
166
+ const projects = sessions();
167
+ return pluginsCommand(rest, {
168
+ name: CLI_NAME,
169
+ stdout: io.stdout,
170
+ stderr: io.stderr,
171
+ cwd: io.cwd,
172
+ display: (path) => displayPath(io.cwd, path),
173
+ workspaceFor: (path, isDirectory) => isDirectory ? projects.forFolder(path) : projects.forFile(path),
174
+ printDiagnostics: (path, diagnostics) => printDiagnostics(io, path, diagnostics),
175
+ });
176
+ }
177
+ if (command === "preview") {
178
+ return previewCommand(rest, {
179
+ name: CLI_NAME,
180
+ stdout: io.stdout,
181
+ stderr: io.stderr,
182
+ cwd: io.cwd,
183
+ display: (path) => displayPath(io.cwd, path),
184
+ projects: () => sessions(),
185
+ generator: `${BRAND_NAME} ${CLI_VERSION}`,
186
+ script: catalogScript,
187
+ printDiagnostics: (path, diagnostics) => printDiagnostics(io, path, diagnostics),
188
+ });
189
+ }
190
+ if (command === "review") {
191
+ return reviewCommand(rest, {
192
+ name: CLI_NAME,
193
+ stdout: io.stdout,
194
+ stderr: io.stderr,
195
+ cwd: io.cwd,
196
+ display: (path) => displayPath(io.cwd, path),
197
+ toolVersion: CLI_VERSION,
198
+ ...(contentLocale ? { locale: contentLocale } : {}),
199
+ // For `--base cloud[:<n>]`; nothing is read from it, and no request made, otherwise.
200
+ cloud: cloudContext("review", cloudIo(io)),
201
+ });
202
+ }
203
+ if (command === "build") {
204
+ return buildCommand(rest, {
205
+ name: CLI_NAME,
206
+ stdout: io.stdout,
207
+ stderr: io.stderr,
208
+ cwd: io.cwd,
209
+ display: (path) => displayPath(io.cwd, path),
210
+ projects: () => sessions(),
211
+ generator: `${BRAND_NAME} ${CLI_VERSION}`,
212
+ printProject: (workspace) => {
213
+ let errors = false;
214
+ for (const [path, diagnostics] of projectDiagnostics(workspace)) {
215
+ printDiagnostics(io, displayPath(io.cwd, path), diagnostics);
216
+ errors ||= diagnostics.some((d) => d.severity === "error");
217
+ }
218
+ return errors;
219
+ },
220
+ printDiagnostics: (path, diagnostics) => printDiagnostics(io, path, diagnostics),
221
+ });
222
+ }
223
+ if (isCloudCommand(command))
224
+ return cloudCommand(command, rest, cloudIo(io));
225
+ io.stderr(`${messages().common.unknownCommand(CLI_NAME, command)}\n`);
226
+ return EXIT_USAGE;
227
+ }
228
+ /**
229
+ * What the cloud commands need from a run: its streams, environment, terminal and hooks. A command
230
+ * that is not a cloud command but talks to the service (`review --base cloud`) passes this to
231
+ * `cloudContext` (`cloud-command.ts`).
232
+ */
233
+ function cloudIo(io) {
234
+ return {
235
+ name: CLI_NAME,
236
+ stdout: io.stdout,
237
+ stderr: io.stderr,
238
+ cwd: io.cwd,
239
+ display: (path) => displayPath(io.cwd, path),
240
+ toolVersion: CLI_VERSION,
241
+ env: io.env,
242
+ ask: io.ask,
243
+ stdin: io.stdin,
244
+ cloud: io.cloud,
245
+ locale: contentLocale,
246
+ };
247
+ }
248
+ /** Diagnostics of `project.json`, of the project's plugins and of its assets, by file. */
249
+ function projectDiagnostics(workspace) {
250
+ const manifest = workspace.manifest();
251
+ const byPath = new Map();
252
+ if (manifest.diagnostics.length > 0)
253
+ byPath.set(manifest.path, [...manifest.diagnostics]);
254
+ for (const [path, list] of [
255
+ ...workspace.limits(),
256
+ ...workspace.plugins().diagnostics,
257
+ ...workspace.assets().diagnostics,
258
+ ]) {
259
+ byPath.set(path, [...(byPath.get(path) ?? []), ...list]);
260
+ }
261
+ return new Map([...byPath].map(([path, list]) => [path, sortDiagnostics(list)]));
262
+ }
263
+ function printDiagnostics(io, path, diagnostics) {
264
+ for (const d of diagnostics) {
265
+ io.stdout(`${path}:${d.range.start.line}:${d.range.start.column}: ${d.severity} ${d.code} ${d.message}\n`);
266
+ for (const hint of d.hints ?? [])
267
+ io.stdout(` ${messages().common.hint}: ${hint}\n`);
268
+ }
269
+ }
270
+ function readSource(io, command, path) {
271
+ const file = isAbsolute(path) ? path : resolve(io.cwd, path);
272
+ try {
273
+ return { file, text: readFileSync(file, "utf8") };
274
+ }
275
+ catch (error) {
276
+ io.stderr(`${CLI_NAME} ${command}: ${messages().common.cannotRead(path, error.message)}\n`);
277
+ return undefined;
278
+ }
279
+ }
280
+ // ---------------------------------------------------------------------------- validate
281
+ function validate(args, io) {
282
+ let options;
283
+ let paths;
284
+ try {
285
+ const parsed = parseArgs({
286
+ args: [...args],
287
+ options: {
288
+ json: { type: "boolean", default: false },
289
+ strict: { type: "boolean", default: false },
290
+ help: { type: "boolean", short: "h", default: false },
291
+ },
292
+ allowPositionals: true,
293
+ strict: true,
294
+ });
295
+ options = parsed.values;
296
+ paths = parsed.positionals;
297
+ }
298
+ catch (error) {
299
+ io.stderr(`${CLI_NAME} validate: ${argumentsError(error)}\n`);
300
+ return EXIT_USAGE;
301
+ }
302
+ if (options.help) {
303
+ io.stdout(messages().help.main(CLI_NAME, CLI_ALIAS));
304
+ return EXIT_OK;
305
+ }
306
+ if (paths.length === 0) {
307
+ io.stderr(`${CLI_NAME} validate: ${messages().common.atLeastOnePath}\n`);
308
+ return EXIT_USAGE;
309
+ }
310
+ const host = createNodeHost();
311
+ const projects = sessions(host);
312
+ /** A file's diagnostics with the composition of its diagrams, as the editor shows them. */
313
+ const withComposition = (diagnostics, file) => {
314
+ const layout = projects.analyze(file).diagnostics.filter((d) => d.code.startsWith("layout."));
315
+ const seen = new Set(diagnostics.map((d) => `${d.code}@${d.range.start.offset}|${d.message}`));
316
+ return sortDiagnostics([
317
+ ...diagnostics,
318
+ ...layout.filter((d) => !seen.has(`${d.code}@${d.range.start.offset}|${d.message}`)),
319
+ ]);
320
+ };
321
+ const results = [];
322
+ const reported = new Set();
323
+ const add = (path, diagnostics) => {
324
+ if (reported.has(path))
325
+ return;
326
+ reported.add(path);
327
+ results.push({ path: displayPath(io.cwd, path), diagnostics });
328
+ };
329
+ const addManifest = (workspace) => {
330
+ for (const [path, diagnostics] of projectDiagnostics(workspace))
331
+ add(path, diagnostics);
332
+ };
333
+ for (const path of paths) {
334
+ const absolute = toProjectPath(isAbsolute(path) ? path : resolve(io.cwd, path));
335
+ let stats;
336
+ try {
337
+ stats = statSync(absolute);
338
+ }
339
+ catch {
340
+ io.stderr(`${CLI_NAME} validate: ${messages().common.pathNotFound(path)}\n`);
341
+ return EXIT_USAGE;
342
+ }
343
+ if (stats.isDirectory()) {
344
+ const workspace = projects.forFolder(absolute);
345
+ addManifest(workspace);
346
+ const program = workspace.compileProject();
347
+ for (const [file, compiled] of program.files) {
348
+ if (file === absolute || file.startsWith(`${absolute}/`)) {
349
+ add(file, withComposition(compiled.diagnostics, file));
350
+ }
351
+ }
352
+ }
353
+ else {
354
+ const workspace = projects.forFile(absolute);
355
+ if (workspace.text(absolute) === undefined) {
356
+ io.stderr(`${CLI_NAME} validate: ${messages().common.cannotRead(path)}\n`);
357
+ return EXIT_USAGE;
358
+ }
359
+ addManifest(workspace);
360
+ add(absolute, withComposition(workspace.file(absolute).diagnostics, absolute));
361
+ }
362
+ }
363
+ const all = results.flatMap((r) => r.diagnostics);
364
+ const summary = {
365
+ files: results.length,
366
+ errors: all.filter((d) => d.severity === "error").length,
367
+ warnings: all.filter((d) => d.severity === "warning").length,
368
+ infos: all.filter((d) => d.severity === "info").length,
369
+ };
370
+ if (options.json) {
371
+ io.stdout(stringifyJson({
372
+ files: results.map((r) => ({
373
+ path: r.path,
374
+ diagnostics: diagnosticsToJson(r.diagnostics),
375
+ })),
376
+ summary,
377
+ }));
378
+ }
379
+ else {
380
+ for (const result of results)
381
+ printDiagnostics(io, result.path, result.diagnostics);
382
+ io.stdout(`${messages().validate.summary(summary.files, summary.errors, summary.warnings)}\n`);
383
+ }
384
+ const failed = summary.errors > 0 || (options.strict && summary.warnings > 0);
385
+ return failed ? EXIT_FINDINGS : EXIT_OK;
386
+ }
387
+ // ---------------------------------------------------------------------------- layout
388
+ /**
389
+ * Arranges diagrams with ELK and writes the result as `place` statements: for unplaced elements
390
+ * around the placed ones (as `layout assisted` renders them), or for every element with `--all`.
391
+ */
392
+ function layoutCommand(args, io) {
393
+ let options;
394
+ let paths;
395
+ try {
396
+ const parsed = parseArgs({
397
+ args: [...args],
398
+ options: {
399
+ diagram: { type: "string" },
400
+ all: { type: "boolean", default: false },
401
+ check: { type: "boolean", default: false },
402
+ stdout: { type: "boolean", default: false },
403
+ help: { type: "boolean", short: "h", default: false },
404
+ },
405
+ allowPositionals: true,
406
+ strict: true,
407
+ });
408
+ options = parsed.values;
409
+ paths = parsed.positionals;
410
+ }
411
+ catch (error) {
412
+ io.stderr(`${CLI_NAME} layout: ${argumentsError(error)}\n`);
413
+ return EXIT_USAGE;
414
+ }
415
+ if (options.help) {
416
+ io.stdout(messages().help.main(CLI_NAME, CLI_ALIAS));
417
+ return EXIT_OK;
418
+ }
419
+ if (paths.length !== 1) {
420
+ io.stderr(`${CLI_NAME} layout: ${messages().common.oneMeltemiFile}\n`);
421
+ return EXIT_USAGE;
422
+ }
423
+ if (options.stdout && options.check) {
424
+ io.stderr(`${CLI_NAME} layout: ${messages().common.cannotCombine("--stdout", "--check")}\n`);
425
+ return EXIT_USAGE;
426
+ }
427
+ const source = readSource(io, "layout", paths[0]);
428
+ if (!source)
429
+ return EXIT_USAGE;
430
+ // With --stdout, stdout holds only the output; diagnostics go to stderr.
431
+ const log = options.stdout ? { ...io, stdout: io.stderr } : io;
432
+ const path = displayPath(io.cwd, source.file);
433
+ const projectPath = toProjectPath(source.file);
434
+ const projects = sessions();
435
+ const workspace = projects.forFile(projectPath);
436
+ const file = workspace.file(projectPath);
437
+ const compilation = file.compilation;
438
+ const errors = (list) => list.filter((d) => d.severity === "error");
439
+ printDiagnostics(log, path, errors(file.diagnostics));
440
+ let failed = errors(file.diagnostics).length > 0;
441
+ const renderable = compilation.document.diagrams.filter((d) => !d.hasErrors);
442
+ let selected = renderable;
443
+ if (options.diagram !== undefined) {
444
+ selected = renderable.filter((d) => d.id === options.diagram);
445
+ if (selected.length === 0) {
446
+ const available = renderable.map((d) => d.id);
447
+ io.stderr(`${CLI_NAME} layout: ${messages().common.noDiagram(options.diagram, path, available)}\n`);
448
+ return EXIT_USAGE;
449
+ }
450
+ }
451
+ if (selected.length === 0) {
452
+ printDiagnostics(log, path, [noDiagramDiagnostic(compilation)]);
453
+ return EXIT_FINDINGS;
454
+ }
455
+ const scope = options.all ? "all" : "unplaced";
456
+ const edits = [];
457
+ const changed = [];
458
+ for (const diagram of selected) {
459
+ // Sequence diagrams are laid out from the order of their events; they hold no positions.
460
+ if (diagram.type === "sequence") {
461
+ if (options.diagram !== undefined) {
462
+ io.stderr(`${CLI_NAME} layout: ${messages().layout.sequence(diagram.id)}\n`);
463
+ return EXIT_USAGE;
464
+ }
465
+ continue;
466
+ }
467
+ const result = layoutDiagram(compilation, diagram, {
468
+ defaultTheme: projectTheme(file.program),
469
+ themes: themeDefinitionsFor(file),
470
+ measurer: projects.fontsFor(workspace),
471
+ icons: workspace.assets().registry,
472
+ engine: projects.engine,
473
+ images: workspace.imageResolver(projectPath),
474
+ // What `layout assisted` would draw, or a fresh arrangement of everything.
475
+ layout: options.all ? "auto" : "assisted",
476
+ });
477
+ printDiagnostics(log, path, errors(result.diagnostics));
478
+ if (!result.scene || !result.boxes) {
479
+ failed = true;
480
+ continue;
481
+ }
482
+ const positions = new Map();
483
+ const sizes = new Map();
484
+ // Free diagrams place group children inside their group, and give groups a place and a size.
485
+ const free = diagram.type === "free";
486
+ for (const element of diagram.elements) {
487
+ const box = result.boxes.get(element.id);
488
+ if (!box)
489
+ continue;
490
+ const origin = free ? result.origins?.get(element.id) : undefined;
491
+ positions.set(element.id, {
492
+ x: box.x + box.width / 2 - (origin?.x ?? 0),
493
+ y: box.y + box.height / 2 - (origin?.y ?? 0),
494
+ });
495
+ if (element.kind === "group")
496
+ sizes.set(element.id, { width: box.width, height: box.height });
497
+ }
498
+ // `--all` also writes the bends of the connectors the engine drew.
499
+ const routes = options.all && result.connectors
500
+ ? new Map([...result.connectors].map(([key, c]) => [key, c.points]))
501
+ : undefined;
502
+ const planned = planEdit(compilation, {
503
+ kind: "arrange",
504
+ diagram: diagram.id,
505
+ positions,
506
+ sizes,
507
+ ...(routes ? { routes } : {}),
508
+ scope,
509
+ ...(free ? { writeGroups: true } : {}),
510
+ });
511
+ if (!planned.ok) {
512
+ io.stderr(`${CLI_NAME} layout: ${messages().layout.cannotWrite(diagram.id, planned.rejection.reason)}\n`);
513
+ failed = true;
514
+ continue;
515
+ }
516
+ if (planned.plan.edits.length === 0)
517
+ continue;
518
+ edits.push(...planned.plan.edits);
519
+ changed.push(diagram.id);
520
+ }
521
+ const text = applyTextEdits(compilation.text, edits);
522
+ if (options.stdout) {
523
+ io.stdout(text);
524
+ return failed ? EXIT_FINDINGS : EXIT_OK;
525
+ }
526
+ if (changed.length === 0) {
527
+ io.stdout(`${messages().layout.unchanged(path)}\n`);
528
+ return failed ? EXIT_FINDINGS : EXIT_OK;
529
+ }
530
+ if (options.check) {
531
+ io.stdout(`${messages().layout.wouldArrange(changed, path)}\n`);
532
+ return EXIT_FINDINGS;
533
+ }
534
+ try {
535
+ writeFileSync(source.file, text, "utf8");
536
+ }
537
+ catch (error) {
538
+ io.stderr(`${CLI_NAME} layout: ${messages().common.cannotWrite(path, error.message)}\n`);
539
+ return EXIT_USAGE;
540
+ }
541
+ io.stdout(`${messages().layout.arranged(changed, path)}\n`);
542
+ return failed ? EXIT_FINDINGS : EXIT_OK;
543
+ }
544
+ // ---------------------------------------------------------------------------- convert
545
+ /**
546
+ * Copies a flow diagram, laid out as `export` draws it, into a new free diagram after it, with
547
+ * every object linked to its source (decision 0018); or writes a file's model JSON.
548
+ */
549
+ function convertCommand(args, io) {
550
+ let options;
551
+ let paths;
552
+ try {
553
+ const parsed = parseArgs({
554
+ args: [...args],
555
+ options: {
556
+ to: { type: "string" },
557
+ out: { type: "string" },
558
+ diagram: { type: "string" },
559
+ id: { type: "string" },
560
+ check: { type: "boolean", default: false },
561
+ stdout: { type: "boolean", default: false },
562
+ help: { type: "boolean", short: "h", default: false },
563
+ },
564
+ allowPositionals: true,
565
+ strict: true,
566
+ });
567
+ options = parsed.values;
568
+ paths = parsed.positionals;
569
+ }
570
+ catch (error) {
571
+ io.stderr(`${CLI_NAME} convert: ${argumentsError(error)}\n`);
572
+ return EXIT_USAGE;
573
+ }
574
+ if (options.help) {
575
+ io.stdout(messages().help.main(CLI_NAME, CLI_ALIAS));
576
+ return EXIT_OK;
577
+ }
578
+ if (options.to === "json")
579
+ return convertToJson(paths, options, io);
580
+ if (options.to !== "free") {
581
+ io.stderr(`${CLI_NAME} convert: ${messages().convert.giveTo}\n`);
582
+ return EXIT_USAGE;
583
+ }
584
+ if (options.out !== undefined) {
585
+ io.stderr(`${CLI_NAME} convert: ${messages().convert.outWithJson}\n`);
586
+ return EXIT_USAGE;
587
+ }
588
+ if (paths.length !== 1) {
589
+ io.stderr(`${CLI_NAME} convert: ${messages().common.oneMeltemiFile}\n`);
590
+ return EXIT_USAGE;
591
+ }
592
+ if (options.stdout && options.check) {
593
+ io.stderr(`${CLI_NAME} convert: ${messages().common.cannotCombine("--stdout", "--check")}\n`);
594
+ return EXIT_USAGE;
595
+ }
596
+ const source = readSource(io, "convert", paths[0]);
597
+ if (!source)
598
+ return EXIT_USAGE;
599
+ const log = options.stdout ? { ...io, stdout: io.stderr } : io;
600
+ const path = displayPath(io.cwd, source.file);
601
+ const projects = sessions();
602
+ const analysis = projects.analyze(toProjectPath(source.file));
603
+ const flows = analysis.diagrams.filter((d) => d.diagram.type === "flow");
604
+ let chosen = flows[0];
605
+ if (options.diagram !== undefined) {
606
+ chosen = flows.find((d) => d.id === options.diagram);
607
+ if (!chosen) {
608
+ const available = flows.map((d) => d.id);
609
+ io.stderr(`${CLI_NAME} convert: ${messages().convert.noFlowDiagram(options.diagram, path, available)}\n`);
610
+ return EXIT_USAGE;
611
+ }
612
+ }
613
+ else if (flows.length !== 1) {
614
+ const ids = flows.map((d) => d.id);
615
+ io.stderr(`${CLI_NAME} convert: ${ids.length === 0 ? messages().convert.noFlow(path) : messages().convert.severalFlows(path, ids)}\n`);
616
+ return EXIT_USAGE;
617
+ }
618
+ const diagram = chosen;
619
+ const operation = convertOperation(analysis, diagram.id, options.id);
620
+ if (!operation) {
621
+ const problems = analysis.diagnostics.filter((d) => d.severity === "error" &&
622
+ d.range.start.offset >= diagram.diagram.node.start &&
623
+ d.range.start.offset <= diagram.diagram.node.end);
624
+ printDiagnostics(log, path, problems);
625
+ io.stderr(`${CLI_NAME} convert: ${messages().convert.cannotDraw(diagram.id)}\n`);
626
+ return EXIT_FINDINGS;
627
+ }
628
+ const planned = planEdit(analysis.compilation, operation);
629
+ if (!planned.ok) {
630
+ io.stderr(`${CLI_NAME} convert: ${planned.rejection.reason}\n`);
631
+ return planned.rejection.code === "edit.id-conflict" ? EXIT_USAGE : EXIT_FINDINGS;
632
+ }
633
+ const text = applyTextEdits(analysis.text, planned.plan.edits);
634
+ if (options.stdout) {
635
+ io.stdout(text);
636
+ return EXIT_OK;
637
+ }
638
+ if (options.check) {
639
+ io.stdout(`${messages().convert.wouldConvert(diagram.id, operation.id, path)}\n`);
640
+ return EXIT_FINDINGS;
641
+ }
642
+ try {
643
+ writeFileSync(source.file, text, "utf8");
644
+ }
645
+ catch (error) {
646
+ io.stderr(`${CLI_NAME} convert: ${messages().common.cannotWrite(path, error.message)}\n`);
647
+ return EXIT_USAGE;
648
+ }
649
+ io.stdout(`${messages().convert.converted(diagram.id, operation.id, path)}\n`);
650
+ return EXIT_OK;
651
+ }
652
+ /**
653
+ * `convert --to json`: the model JSON of a file (format `model-json`), the interchange format for
654
+ * other tools. Diagrams with errors are left out and their problems are in `diagnostics`; the
655
+ * command exits 1 then.
656
+ */
657
+ function convertToJson(paths, options, io) {
658
+ const clash = options.diagram !== undefined
659
+ ? "--diagram"
660
+ : options.id !== undefined
661
+ ? "--id"
662
+ : options.check
663
+ ? "--check"
664
+ : null;
665
+ if (clash) {
666
+ io.stderr(`${CLI_NAME} convert: ${messages().convert.notForJson(clash)}\n`);
667
+ return EXIT_USAGE;
668
+ }
669
+ if (paths.length !== 1) {
670
+ io.stderr(`${CLI_NAME} convert: ${messages().common.oneMeltemiFile}\n`);
671
+ return EXIT_USAGE;
672
+ }
673
+ if (options.stdout && options.out !== undefined) {
674
+ io.stderr(`${CLI_NAME} convert: ${messages().common.cannotCombine("--stdout", "--out")}\n`);
675
+ return EXIT_USAGE;
676
+ }
677
+ const source = readSource(io, "convert", paths[0]);
678
+ if (!source)
679
+ return EXIT_USAGE;
680
+ const projects = sessions();
681
+ const analysis = projects.analyze(toProjectPath(source.file));
682
+ const json = stringifyJson(toJson(analysis.compilation));
683
+ const ok = analysis.compilation.ok;
684
+ if (options.stdout) {
685
+ io.stdout(json);
686
+ return ok ? EXIT_OK : EXIT_FINDINGS;
687
+ }
688
+ const target = options.out === undefined
689
+ ? `${source.file.replace(/\.meltemi$/i, "")}.model.json`
690
+ : isAbsolute(options.out)
691
+ ? options.out
692
+ : resolve(io.cwd, options.out);
693
+ try {
694
+ writeFileSync(target, json, "utf8");
695
+ }
696
+ catch (error) {
697
+ io.stderr(`${CLI_NAME} convert: ${messages().common.cannotWrite(displayPath(io.cwd, target), error.message)}\n`);
698
+ return EXIT_USAGE;
699
+ }
700
+ io.stdout(`${messages().convert.wroteJson(displayPath(io.cwd, source.file), displayPath(io.cwd, target), ok)}\n`);
701
+ return ok ? EXIT_OK : EXIT_FINDINGS;
702
+ }
703
+ // ---------------------------------------------------------------------------- format
704
+ function formatCommand(args, io) {
705
+ let options;
706
+ let paths;
707
+ try {
708
+ const parsed = parseArgs({
709
+ args: [...args],
710
+ options: {
711
+ check: { type: "boolean", default: false },
712
+ stdout: { type: "boolean", default: false },
713
+ help: { type: "boolean", short: "h", default: false },
714
+ },
715
+ allowPositionals: true,
716
+ strict: true,
717
+ });
718
+ options = parsed.values;
719
+ paths = parsed.positionals;
720
+ }
721
+ catch (error) {
722
+ io.stderr(`${CLI_NAME} format: ${argumentsError(error)}\n`);
723
+ return EXIT_USAGE;
724
+ }
725
+ if (options.help) {
726
+ io.stdout(messages().help.main(CLI_NAME, CLI_ALIAS));
727
+ return EXIT_OK;
728
+ }
729
+ if (paths.length === 0) {
730
+ io.stderr(`${CLI_NAME} format: ${messages().common.atLeastOnePath}\n`);
731
+ return EXIT_USAGE;
732
+ }
733
+ if (options.stdout && (paths.length !== 1 || options.check)) {
734
+ io.stderr(`${CLI_NAME} format: ${messages().format.stdoutOne}\n`);
735
+ return EXIT_USAGE;
736
+ }
737
+ const projects = sessions();
738
+ const targets = [];
739
+ for (const path of paths) {
740
+ const absolute = toProjectPath(isAbsolute(path) ? path : resolve(io.cwd, path));
741
+ let stats;
742
+ try {
743
+ stats = statSync(absolute);
744
+ }
745
+ catch {
746
+ io.stderr(`${CLI_NAME} format: ${messages().common.pathNotFound(path)}\n`);
747
+ return EXIT_USAGE;
748
+ }
749
+ if (stats.isDirectory()) {
750
+ if (options.stdout) {
751
+ io.stderr(`${CLI_NAME} format: ${messages().format.stdoutOneFile}\n`);
752
+ return EXIT_USAGE;
753
+ }
754
+ const workspace = projects.forFolder(absolute);
755
+ for (const file of workspace.sources()) {
756
+ if (file.startsWith(`${absolute}/`))
757
+ targets.push({ file, workspace });
758
+ }
759
+ }
760
+ else {
761
+ targets.push({ file: absolute, workspace: projects.forFile(absolute) });
762
+ }
763
+ }
764
+ const seen = new Set();
765
+ let refused = 0;
766
+ let changed = 0;
767
+ let total = 0;
768
+ for (const { file, workspace } of targets) {
769
+ if (seen.has(file))
770
+ continue;
771
+ seen.add(file);
772
+ total++;
773
+ const shown = displayPath(io.cwd, file);
774
+ const text = workspace.text(file);
775
+ if (text === undefined) {
776
+ io.stderr(`${CLI_NAME} format: ${messages().common.cannotRead(shown)}\n`);
777
+ return EXIT_USAGE;
778
+ }
779
+ const result = formatSource(text, formatOptionsFor(workspace.manifest().manifest));
780
+ if (!result.ok) {
781
+ refused++;
782
+ printDiagnostics(io, shown, result.diagnostics);
783
+ io.stdout(`${shown}:1:1: error format.syntax-errors ${messages().format.syntaxErrors}\n`);
784
+ continue;
785
+ }
786
+ if (options.stdout) {
787
+ io.stdout(result.text);
788
+ continue;
789
+ }
790
+ if (!result.changed)
791
+ continue;
792
+ changed++;
793
+ if (options.check) {
794
+ io.stdout(`${messages().format.wouldFormat(shown)}\n`);
795
+ continue;
796
+ }
797
+ try {
798
+ writeFileSync(file, result.text, "utf8");
799
+ }
800
+ catch (error) {
801
+ io.stderr(`${CLI_NAME} format: ${messages().common.cannotWrite(shown, error.message)}\n`);
802
+ return EXIT_USAGE;
803
+ }
804
+ io.stdout(`${messages().format.formatted(shown)}\n`);
805
+ }
806
+ if (!options.stdout) {
807
+ const t = messages().format;
808
+ if (options.check) {
809
+ io.stdout(`${changed === 0 && refused === 0 ? t.checkedAll(total) : t.checked(total, changed, refused)}\n`);
810
+ }
811
+ else {
812
+ io.stdout(`${t.summary(changed, total)}\n`);
813
+ }
814
+ }
815
+ if (refused > 0 || (options.check && changed > 0))
816
+ return EXIT_FINDINGS;
817
+ return EXIT_OK;
818
+ }
819
+ function displayPath(cwd, file) {
820
+ const rel = relative(cwd, file);
821
+ return (rel === "" ? "." : rel).split(sep).join("/");
822
+ }
823
+ //# sourceMappingURL=cli.js.map