asljs-diagram-cli 0.0.0-stage → 0.1.1

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.md ADDED
@@ -0,0 +1,51 @@
1
+ # Permissive No-Derivatives License 1.0
2
+
3
+ Copyright (c) 2026 Alexandrite Software Ltd.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software (the "Software") to:
7
+
8
+ 1. Use the Software for any purpose, including commercial purposes.
9
+ 2. Copy and redistribute the Software, including as part of another application,
10
+ library, or package.
11
+ 3. Modify the Software for private or internal use.
12
+
13
+ Subject to the following conditions:
14
+
15
+ ## 1. No Distribution of Modified Versions
16
+
17
+ Modified versions of the Software, or substantial portions thereof, must not be
18
+ distributed without prior written permission from the copyright holder.
19
+
20
+ Routine build transformations, including minification, transpilation, and
21
+ bundling, are permitted for distribution, provided they do not intentionally
22
+ alter the Software's functionality.
23
+
24
+ ## 2. Licence and Copyright Preservation
25
+
26
+ All distributed copies of the Software must retain this copyright notice and
27
+ licence.
28
+
29
+ The Software itself, including copies incorporated into other applications,
30
+ libraries, or packages, remains subject to this licence.
31
+
32
+ The Software must not be sublicensed or redistributed under a different licence.
33
+
34
+ ## 3. Use in Other Software
35
+
36
+ Applications and libraries that merely use, import, link to, or bundle the
37
+ Software are not considered modified versions of the Software and may be
38
+ distributed under any licence.
39
+
40
+ Such use, incorporation, or bundling does not grant permission to relicense the
41
+ Software or distribute modified versions of it.
42
+
43
+ ### 4. No Warranty
44
+
45
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
46
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
47
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
48
+
49
+ IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR
50
+ OTHER LIABILITY ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR ITS
51
+ USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,85 @@
1
- # Temporary Holding Version
1
+ # diagram-cli
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ > Part of [Alexandrite Software Library][#1] - a set of high-quality and
4
+ > performant JavaScript libraries for everyday use.
5
+
6
+ ## Overview
7
+
8
+ `asljs-diagram-cli` is the `diagram` command. `diagram view <path>` reads a
9
+ diagram file, a graph of nodes and links or a diagram of visual elements, and
10
+ shows it in the browser with [asljs-ui-diagram][LIB]: pan, zoom, select, move
11
+ and resize, and for a graph, add, link and delete nodes.
12
+
13
+ ```bash
14
+ diagram view build/ts-classes.diagram.json
15
+ ```
16
+
17
+ ```text
18
+ Showing build/ts-classes.diagram.json at http://127.0.0.1:52144/
19
+ Press Ctrl+C to stop.
20
+ ```
21
+
22
+ ## Scope
23
+
24
+ - **Files.** A JSON document in the `asljs-ui-diagram` format: `"type": "graph"`
25
+ with `nodes` and `links`, or `"type": "diagram"` with `elements`. The file is
26
+ checked before anything is served, and an error names the invalid value.
27
+ - **Local and in memory.** The command serves the page on `127.0.0.1` and opens
28
+ the default browser. Edits stay in the page; the file is read again on each
29
+ reload and never written.
30
+ - **Generated diagrams.** `toolkit build-ts-files-diagram` writes the import
31
+ graph of TypeScript sources in this format, and `toolkit
32
+ build-ts-class-diagram` their class diagram.
33
+
34
+ ## Installation
35
+
36
+ ```bash
37
+ npm install --global asljs-diagram-cli
38
+ ```
39
+
40
+ NPM Package: [asljs-diagram-cli][NPM]
41
+
42
+ ## Usage
43
+
44
+ ```bash
45
+ # A free port, and the browser opens
46
+ diagram view graph.json
47
+
48
+ # A fixed port, without opening the browser
49
+ diagram view graph.json --port 8080 --no-open
50
+ ```
51
+
52
+ A minimal graph file:
53
+
54
+ ```json
55
+ { "type": "graph",
56
+ "nodes": [ { "id": "a", "label": "Start", "x": 0, "y": 0 },
57
+ { "id": "b", "label": "End", "x": 220, "y": 0 } ],
58
+ "links": [ { "from": "a", "to": "b" } ] }
59
+ ```
60
+
61
+ ## Further reading
62
+
63
+ - [diagram view][DOC] - the options, what the page does, and the errors.
64
+ - [Diagram][FMT] - the document format, in `asljs-ui-diagram`.
65
+
66
+ Questions and bugs: [asljs/issues][ISS].
67
+
68
+ ## Related packages
69
+
70
+ - [ui-diagram][LIB] - the diagram component and models the page uses.
71
+ - [toolkit][TLK] - `build-ts-files-diagram` and `build-ts-class-diagram` turn
72
+ `.structure.json` files into diagram files.
73
+
74
+ ## License
75
+
76
+ MIT License. See [LICENSE][LIC] for details.
77
+
78
+ [#1]: https://github.com/AlexandriteSoftware/asljs
79
+ [DOC]: <docs/Diagram CLI.md>
80
+ [FMT]: ../../ui/diagram/docs/Diagram.md#documents
81
+ [ISS]: https://github.com/AlexandriteSoftware/asljs/issues
82
+ [LIB]: ../../ui/diagram/README.md
83
+ [LIC]: LICENSE.md
84
+ [NPM]: https://www.npmjs.com/package/asljs-diagram-cli
85
+ [TLK]: ../toolkit/README.md
package/bin/diagram.js ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { runCli }
4
+ from '../dist/cli.js';
5
+
6
+ process.exitCode =
7
+ await runCli(
8
+ process.argv.slice(2));
package/dist/cli.d.ts ADDED
@@ -0,0 +1,26 @@
1
+ import { type CommandIo } from 'asljs-appbase';
2
+ import { type Viewer } from './server.js';
3
+ /**
4
+ * The configuration file, looked for in the current directory and its
5
+ * parents.
6
+ */
7
+ export declare const CONFIG_FILE = "diagram.json";
8
+ /**
9
+ * What the command runs with: the folder `diagram.json` is looked for from,
10
+ * the environment, the output, and how it opens and waits; replaced in tests.
11
+ */
12
+ export interface CliIo extends CommandIo {
13
+ /** Opens the viewer's address; the default opens the browser. */
14
+ open?: (url: string) => void;
15
+ /**
16
+ * Resolves when the viewer should stop; the default waits for Ctrl+C or
17
+ * SIGTERM.
18
+ */
19
+ untilStopped?: (viewer: Viewer) => Promise<void>;
20
+ }
21
+ /**
22
+ * Runs the `diagram` command line, with a logger provider made from
23
+ * `--loglevel`, `--logfile`, `--logformat`, the `DIAGRAM_LOG_` variables and
24
+ * `diagram.json`, and returns the exit code.
25
+ */
26
+ export declare function runCli(args: string[], io?: CliIo): Promise<number>;
package/dist/cli.js ADDED
@@ -0,0 +1,80 @@
1
+ import { commandLine, configBoolean, configPort, configString, loadConfig, packageVersion, parsePort, runMain } from 'asljs-appbase';
2
+ import { Command } from 'commander';
3
+ import { openInBrowser } from './open.js';
4
+ import { startViewer } from './server.js';
5
+ /**
6
+ * The configuration file, looked for in the current directory and its
7
+ * parents.
8
+ */
9
+ export const CONFIG_FILE = 'diagram.json';
10
+ /**
11
+ * Runs the `diagram` command line, with a logger provider made from
12
+ * `--loglevel`, `--logfile`, `--logformat`, the `DIAGRAM_LOG_` variables and
13
+ * `diagram.json`, and returns the exit code.
14
+ */
15
+ export async function runCli(args, io = { cwd: process.cwd(),
16
+ env: process.env,
17
+ stdout: process.stdout,
18
+ stderr: process.stderr }) {
19
+ return await runMain(args, io, { prefix: 'DIAGRAM_LOG_',
20
+ configFile: CONFIG_FILE }, async (loggerProvider) => {
21
+ const cli = createCli(io, loggerProvider);
22
+ if (args.length === 0) {
23
+ cli.outputHelp();
24
+ return 0;
25
+ }
26
+ await cli.parseAsync(args, { from: 'user' });
27
+ return 0;
28
+ });
29
+ }
30
+ function createCli(io, loggerProvider) {
31
+ const cli = commandLine(new Command('diagram'), io);
32
+ cli
33
+ .description('Shows diagram files in the browser.')
34
+ .helpCommand(false);
35
+ cli.command('view')
36
+ .description('Serve a diagram file, a graph or a diagram of visual elements, and open it in the browser.')
37
+ .argument('<path>', 'the diagram JSON file')
38
+ .option('--port <port>', 'the port to listen on; 0, the default, picks a free one')
39
+ .option('--host <host>', 'the address to listen on; 127.0.0.1 by default')
40
+ .option('--no-open', 'do not open the browser')
41
+ .action(async (file, options, command) => {
42
+ const config = await loadConfig(io.cwd, CONFIG_FILE);
43
+ const port = parsePort(options.port)
44
+ ?? configPort(config)
45
+ ?? 0;
46
+ const host = options.host
47
+ ?? configString(config, 'host')
48
+ ?? '127.0.0.1';
49
+ const open = command.getOptionValueSource('open') === 'cli'
50
+ ? options.open
51
+ : configBoolean(config, 'open')
52
+ ?? true;
53
+ const viewer = await startViewer({ file,
54
+ port,
55
+ host,
56
+ logger: loggerProvider.getLogger('diagram') });
57
+ io.stdout.write(`Showing ${file} at ${viewer.url}\nPress Ctrl+C to stop.\n`);
58
+ if (open) {
59
+ (io.open ?? openInBrowser)(viewer.url);
60
+ }
61
+ try {
62
+ await (io.untilStopped ?? untilSignal)(viewer);
63
+ }
64
+ finally {
65
+ await viewer.close();
66
+ }
67
+ });
68
+ cli.command('version')
69
+ .description('Print the version.')
70
+ .action(() => {
71
+ io.stdout.write(`${packageVersion(import.meta.url)}\n`);
72
+ });
73
+ return cli;
74
+ }
75
+ function untilSignal() {
76
+ return new Promise((resolve) => {
77
+ process.once('SIGINT', () => resolve());
78
+ process.once('SIGTERM', () => resolve());
79
+ });
80
+ }
@@ -0,0 +1,6 @@
1
+ export { CONFIG_FILE, runCli } from './cli.js';
2
+ export type { CliIo } from './cli.js';
3
+ export { createImportMap, findBrowserModules } from 'asljs-appbase';
4
+ export type { BrowserModule } from 'asljs-appbase';
5
+ export { readDocument, startViewer } from './server.js';
6
+ export type { Viewer, ViewerOptions } from './server.js';
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { CONFIG_FILE, runCli } from './cli.js';
2
+ export { createImportMap, findBrowserModules } from 'asljs-appbase';
3
+ export { readDocument, startViewer } from './server.js';
package/dist/open.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ /** Opens `url` in the default browser, without waiting for it. */
2
+ export declare function openInBrowser(url: string, platform?: NodeJS.Platform): void;
package/dist/open.js ADDED
@@ -0,0 +1,20 @@
1
+ import { spawn } from 'node:child_process';
2
+ /** Opens `url` in the default browser, without waiting for it. */
3
+ export function openInBrowser(url, platform = process.platform) {
4
+ const [command, args] = platform === 'win32'
5
+ ? ['cmd',
6
+ ['/c',
7
+ 'start',
8
+ '""',
9
+ url]]
10
+ : platform === 'darwin'
11
+ ? ['open',
12
+ [url]]
13
+ : ['xdg-open',
14
+ [url]];
15
+ const child = spawn(command, args, { detached: true,
16
+ stdio: 'ignore',
17
+ windowsVerbatimArguments: platform === 'win32' });
18
+ child.on('error', () => { });
19
+ child.unref();
20
+ }
package/dist/page.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * The viewer page: a toolbar, the diagram and a status line. It loads the
3
+ * document from `/document`, creates its model, and fits it on first show. For
4
+ * a graph, the toolbar adds, links and deletes nodes.
5
+ */
6
+ export declare function renderPage(title: string, importMap: object): string;
package/dist/page.js ADDED
@@ -0,0 +1,161 @@
1
+ import { escapeHtml, page, scriptJson } from 'asljs-appbase';
2
+ /**
3
+ * The viewer page: a toolbar, the diagram and a status line. It loads the
4
+ * document from `/document`, creates its model, and fits it on first show. For
5
+ * a graph, the toolbar adds, links and deletes nodes.
6
+ */
7
+ export function renderPage(title, importMap) {
8
+ // The import map goes first in the body, before the module script.
9
+ return page(title, `<script type="importmap">${scriptJson(importMap)}</script>
10
+ <header>
11
+ <h1>${escapeHtml(title)}</h1>
12
+ <asljs-diagram-zoom id="zoom"></asljs-diagram-zoom>
13
+ <asljs-diagram-search id="search"></asljs-diagram-search>
14
+ <span class="graph-only" hidden>
15
+ <button type="button" id="add-node">Add node</button>
16
+ <button type="button" id="add-link" title="Link the two selected nodes, first to second">Link</button>
17
+ </span>
18
+ <button type="button" id="delete" title="Delete the selection (Delete key)">Delete</button>
19
+ </header>
20
+ <asljs-diagram id="diagram"></asljs-diagram>
21
+ <footer>
22
+ <span id="status"></span>
23
+ <span id="error"></span>
24
+ </footer>
25
+ <script type="module">
26
+ import { createModelFromDocument,
27
+ isNode,
28
+ parseDiagramDocument } from 'asljs-ui-diagram';
29
+
30
+ const diagram = document.querySelector('#diagram');
31
+ const status = document.querySelector('#status');
32
+ const error = document.querySelector('#error');
33
+
34
+ try {
35
+ const response = await fetch('document');
36
+
37
+ if (!response.ok) {
38
+ throw new Error((await response.json()).error);
39
+ }
40
+
41
+ const data = parseDiagramDocument(await response.json());
42
+ const model = createModelFromDocument(data);
43
+
44
+ diagram.model = model;
45
+ document.querySelector('#zoom').viewport = model.viewport;
46
+ document.querySelector('#search').model = model;
47
+ window.diagramModel = model;
48
+
49
+ const isGraph = data.type === 'graph';
50
+
51
+ for (const element of document.querySelectorAll('.graph-only')) {
52
+ element.hidden = !isGraph;
53
+ }
54
+
55
+ const showStatus = () =>
56
+ {
57
+ const elements = model.diagram.elements;
58
+ const nodes = elements.filter(isNode).length;
59
+
60
+ status.textContent =
61
+ (isGraph
62
+ ? nodes + ' nodes, ' + (elements.length - nodes) + ' links'
63
+ : elements.length + ' elements')
64
+ + (model.selection.length > 0
65
+ ? '; selected: ' + model.selection.join(', ')
66
+ : '');
67
+ };
68
+
69
+ model.on('change', showStatus);
70
+ model.diagram.elements.on('change', showStatus);
71
+ showStatus();
72
+
73
+ document.querySelector('#delete').addEventListener('click', () =>
74
+ model.deleteElements([ ...model.selection ]));
75
+
76
+ document.querySelector('#add-node').addEventListener('click', () =>
77
+ {
78
+ const viewport = model.viewport;
79
+ let index = 1;
80
+
81
+ while (model.find('node-' + index)) {
82
+ index++;
83
+ }
84
+
85
+ const id = 'node-' + index;
86
+
87
+ model.addNode(
88
+ { id,
89
+ x: (viewport.width / 2 - viewport.x) / viewport.zoom - 60,
90
+ y: (viewport.height / 2 - viewport.y) / viewport.zoom - 24 });
91
+
92
+ model.select([ id ]);
93
+ });
94
+
95
+ document.querySelector('#add-link').addEventListener('click', () =>
96
+ {
97
+ const ids = model.selection.filter(id => isNode(model.find(id)));
98
+
99
+ if (ids.length !== 2) {
100
+ error.textContent = 'Select two nodes to link, the first one first.';
101
+ return;
102
+ }
103
+
104
+ error.textContent = '';
105
+ model.select([ model.addLink({ from: ids[0], to: ids[1] }) ]);
106
+ });
107
+
108
+ if (!data.viewport) {
109
+ requestAnimationFrame(() => model.viewport.fit());
110
+ }
111
+ } catch (failure) {
112
+ error.textContent = String(failure?.message ?? failure);
113
+ }
114
+ </script>`, `html, body { height: 100%; margin: 0; }
115
+ body {
116
+ max-width: none;
117
+ padding: 0;
118
+ line-height: normal;
119
+ display: grid;
120
+ grid-template-rows: auto 1fr auto;
121
+ font: 14px system-ui, sans-serif;
122
+ color: #1f2937;
123
+ }
124
+ header, footer {
125
+ display: flex;
126
+ flex-wrap: wrap;
127
+ gap: 8px;
128
+ align-items: center;
129
+ padding: 6px 12px;
130
+ background: #f3f4f6;
131
+ }
132
+ header h1 { font-size: 15px; margin: 0 8px 0 0; }
133
+ asljs-diagram {
134
+ min-height: 0;
135
+ background: #ffffff;
136
+ border-block: 1px solid #d1d5db;
137
+ outline: none;
138
+ }
139
+ .asljs-diagram-zoom { display: flex; gap: 4px; align-items: center; }
140
+ .zoom-level { min-width: 4ch; text-align: right; }
141
+ .asljs-select-lookup { position: relative; display: inline-block; }
142
+ .asljs-select-lookup [data-role="menu"] {
143
+ position: absolute;
144
+ z-index: 10;
145
+ top: 100%;
146
+ left: 0;
147
+ min-width: 240px;
148
+ padding: 4px;
149
+ background: #ffffff;
150
+ border: 1px solid #d1d5db;
151
+ box-shadow: 0 4px 12px rgb(0 0 0 / 0.12);
152
+ }
153
+ .asljs-select-lookup [data-role="menu"][hidden] { display: none; }
154
+ .asljs-select-lookup [data-role="filter"] { box-sizing: border-box; width: 100%; }
155
+ .asljs-select-lookup [data-role="results"] { list-style: none; margin: 4px 0 0; padding: 0; }
156
+ .asljs-select-lookup-item { display: block; padding: 2px 6px; color: inherit; text-decoration: none; }
157
+ .asljs-select-lookup-item:hover, .asljs-select-lookup-active { background: #e5edff; }
158
+ .asljs-select-lookup-hint { color: #6b7280; }
159
+ .graph-only[hidden] { display: none; }
160
+ #error { color: #b91c1c; white-space: pre-wrap; }`);
161
+ }
@@ -0,0 +1,26 @@
1
+ import { type BrowserModule, type ListenOptions } from 'asljs-appbase';
2
+ export interface ViewerOptions {
3
+ /** The diagram file to show. */
4
+ file: string;
5
+ /** Default 0: any free port. */
6
+ port?: number;
7
+ /** Default `127.0.0.1`. */
8
+ host?: string;
9
+ /** The packages the page loads; found from `asljs-ui-diagram` by default. */
10
+ modules?: BrowserModule[];
11
+ /** Where the server logs; nothing is logged by default. */
12
+ logger?: ListenOptions['logger'];
13
+ }
14
+ export interface Viewer {
15
+ url: string;
16
+ close(): Promise<void>;
17
+ }
18
+ export { MODULES_PREFIX } from 'asljs-appbase';
19
+ /**
20
+ * Reads and checks the diagram file, then serves the viewer page, the file as
21
+ * `/document`, and the packages the page imports. The file is read again on
22
+ * every request for it, so a reload shows its current content.
23
+ */
24
+ export declare function startViewer(options: ViewerOptions): Promise<Viewer>;
25
+ /** Reads a diagram file and checks it; the error names the file. */
26
+ export declare function readDocument(file: string): Promise<unknown>;
package/dist/server.js ADDED
@@ -0,0 +1,63 @@
1
+ import { createHandler, createImportMap, findBrowserModules, jsonReply, MODULES_PREFIX, serve, serverUrl } from 'asljs-appbase';
2
+ import { parseDiagramDocument } from 'asljs-ui-diagram';
3
+ import { readFile } from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { renderPage } from './page.js';
7
+ export { MODULES_PREFIX } from 'asljs-appbase';
8
+ /**
9
+ * Reads and checks the diagram file, then serves the viewer page, the file as
10
+ * `/document`, and the packages the page imports. The file is read again on
11
+ * every request for it, so a reload shows its current content.
12
+ */
13
+ export async function startViewer(options) {
14
+ const file = path.resolve(options.file);
15
+ await readDocument(file);
16
+ const modules = options.modules
17
+ ?? findBrowserModules('asljs-ui-diagram', path.dirname(fileURLToPath(import.meta.url)));
18
+ const page = renderPage(path.basename(file), createImportMap(modules, MODULES_PREFIX));
19
+ // Nothing but the page, the document and the scripts of the packages.
20
+ const server = await serve(createHandler({ folder: path.dirname(file),
21
+ index: async () => page,
22
+ json: { '/document': async () => {
23
+ try {
24
+ return await readDocument(file);
25
+ }
26
+ catch (error) {
27
+ return jsonReply(422, { error: error.message });
28
+ }
29
+ } },
30
+ modules,
31
+ methods: ['GET',
32
+ 'HEAD'],
33
+ moduleTypes: ['.js',
34
+ '.mjs',
35
+ '.map'],
36
+ allow: () => false }), { port: options.port ?? 0,
37
+ host: options.host,
38
+ logger: options.logger });
39
+ return { url: serverUrl(server),
40
+ close: () => new Promise((resolve, reject) => {
41
+ server.closeAllConnections();
42
+ server.close(error => error
43
+ ? reject(error)
44
+ : resolve());
45
+ }) };
46
+ }
47
+ /** Reads a diagram file and checks it; the error names the file. */
48
+ export async function readDocument(file) {
49
+ let text;
50
+ try {
51
+ text =
52
+ await readFile(file, 'utf8');
53
+ }
54
+ catch (error) {
55
+ throw new Error(`Cannot read ${file}: ${error.message}`);
56
+ }
57
+ try {
58
+ return parseDiagramDocument(JSON.parse(text));
59
+ }
60
+ catch (error) {
61
+ throw new Error(`${file}: ${error.message}`);
62
+ }
63
+ }
@@ -0,0 +1,78 @@
1
+ # Diagram CLI
2
+
3
+ ## Purpose
4
+
5
+ What the `diagram` command does: `diagram view`, its options, the page it
6
+ serves, and its errors.
7
+
8
+ ## Logging
9
+
10
+ Every command also takes `--loglevel <level>`, `--logfile <target>` and
11
+ `--logformat <format>`, or `DIAGRAM_LOG_LEVEL`, `DIAGRAM_LOG_FILE` and
12
+ `DIAGRAM_LOG_FORMAT`, or `logLevel`, `logFile` and `logFormat` of the nearest
13
+ `diagram.json`, in that order. Nothing is logged without them. At `information`
14
+ the log has the address the server listens on, at `trace` each request, and at
15
+ `error` a request that failed.
16
+
17
+ ## diagram view
18
+
19
+ ```text
20
+ diagram view <path> [--port <port>] [--host <host>] [--no-open]
21
+ ```
22
+
23
+ - `<path>` - the diagram file, JSON in the `asljs-ui-diagram` document format.
24
+ - `--port` - the port to listen on; 0, the default, picks a free one.
25
+ - `--host` - the address to listen on, `127.0.0.1` by default.
26
+ - `--no-open` - print the address without opening the browser.
27
+
28
+ The options can also be set in `diagram.json`, the nearest one in the current
29
+ directory or a parent, as the [configuration][CFG] describes:
30
+
31
+ - `host` - a string, for `--host`.
32
+ - `port` - an integer from 0 to 65535, for `--port`.
33
+ - `open` - `true` or `false`; `false` is `--no-open`.
34
+
35
+ An option beats the key: `--no-open` keeps the browser closed with `"open":
36
+ true`. A value of the wrong type ends the command with exit code 1 and a message
37
+ naming the file and the key, such as `diagram.json: "port" must be a port
38
+ number.`
39
+
40
+ The command reads and checks the file first. A file that cannot be read, is not
41
+ JSON, or does not match the format ends the command with exit code 1 and a
42
+ message naming the file and the first invalid value, such as `graph.json:
43
+ nodes[2].x: expected a number`, before anything is served.
44
+
45
+ It then serves, until Ctrl+C or SIGTERM:
46
+
47
+ - `/` - the page.
48
+ - `/document` - the file, read and checked again on every request, so reloading
49
+ the page shows the file's current content. A file that has become invalid is
50
+ answered with status 422 and `{ "error": <the message> }`.
51
+ - `/modules/<package>/<path>` - the JavaScript of `asljs-ui-diagram` and the
52
+ packages it depends on, `asljs-ui-select-lookup` and lit among them, found the
53
+ way Node finds them from the installed `asljs-ui-diagram`. The page loads them
54
+ through an import map. Only `.js`, `.mjs` and `.map` files inside a package's
55
+ folder are served.
56
+
57
+ Anything else is a 404, and a method other than `GET` and `HEAD` a 405.
58
+
59
+ ## The page
60
+
61
+ The page shows the file name, the zoom control, the diagram and a status line
62
+ with the number of nodes and links, or of elements, and the selection.
63
+
64
+ - Without a `viewport` in the file, the diagram is fitted to the window when it
65
+ first shows.
66
+ - The wheel zooms, dragging the background pans, pressing an element selects it,
67
+ dragging moves the selection, the corner handles resize, and Delete or the
68
+ Delete button deletes the selection with its links.
69
+ - For a graph, Add node adds a node in the middle of the window and selects it,
70
+ and Link links the two selected nodes, from the one selected first.
71
+
72
+ Edits change the page's model only; nothing is written to the file.
73
+
74
+ ## diagram version
75
+
76
+ Prints the version of `asljs-diagram-cli`.
77
+
78
+ [CFG]: ../../../docs/Configuration.md
package/package.json CHANGED
@@ -1,6 +1,68 @@
1
1
  {
2
2
  "name": "asljs-diagram-cli",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "The diagram command: shows a diagram file, such as a graph, in the browser with asljs-diagram.",
5
+ "type": "module",
6
+ "asljs": {
7
+ "type": "app"
8
+ },
9
+ "main": "dist/index.js",
10
+ "types": "dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "default": "./dist/index.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist/**",
19
+ "bin/**",
20
+ "docs/**",
21
+ "README.md",
22
+ "LICENSE.md"
23
+ ],
24
+ "bin": {
25
+ "diagram": "bin/diagram.js"
26
+ },
27
+ "scripts": {
28
+ "flint": "toolkit flint",
29
+ "clean": "toolkit clean",
30
+ "build": "npm run codegen --if-present && tsc -p tsconfig.build.json",
31
+ "build:dist": "npm run codegen --if-present && tsc -p tsconfig.dist.json",
32
+ "typecheck": "npm run codegen --if-present && tsc -p tsconfig.build.json --noEmit",
33
+ "guard:clean-git": "toolkit ensure-clean-working-directory",
34
+ "prepack": "npm run clean && npm run build:dist && npm run build && npm run test && npm run guard:clean-git",
35
+ "postpublish": "toolkit tag-release-revision",
36
+ "test": "node --test --enable-source-maps \"build/**/*.test.js\"",
37
+ "test:watch": "node --watch --test \"build/**/*.test.js\"",
38
+ "release:patch": "toolkit release-patch",
39
+ "all": "npm run clean && npm run build:dist && npm run flint && npm run typecheck && npm run build && npm run test"
40
+ },
41
+ "keywords": [
42
+ "diagram",
43
+ "graph",
44
+ "viewer",
45
+ "cli"
46
+ ],
47
+ "homepage": "https://github.com/AlexandriteSoftware/asljs#readme",
48
+ "bugs": {
49
+ "url": "https://github.com/AlexandriteSoftware/asljs/issues"
50
+ },
51
+ "repository": {
52
+ "type": "git",
53
+ "url": "git+https://github.com/AlexandriteSoftware/asljs.git"
54
+ },
55
+ "author": "\"Alex Netkachov\" <alex.netkachov@gmail.com>",
56
+ "license": "SEE LICENSE IN LICENSE.md",
57
+ "dependencies": {
58
+ "asljs-logging": "^0.1.2",
59
+ "asljs-appbase": "^0.1.3",
60
+ "asljs-ui-diagram": "^0.1.1",
61
+ "commander": "^15.0.0"
62
+ },
63
+ "devDependencies": {
64
+ "asljs-testing": "^0.1.0",
65
+ "asljs-tmpdir": "^0.1.13",
66
+ "asljs-toolkit": "^0.1.0"
67
+ }
68
+ }