asljs-rq-srv 0.1.2 → 0.1.3

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/README.md CHANGED
@@ -22,7 +22,7 @@ rq-srv
22
22
  ```
23
23
 
24
24
  serves every project of the current folder, each folder with an `rq.json`, in
25
- one editor with a list box to switch between them, until stopped with Ctrl+C.
25
+ one editor with a selector to switch between them, until stopped with Ctrl+C.
26
26
 
27
27
  ```bash
28
28
  rq-srv --working-dir ~/work --port 8080
@@ -2,11 +2,11 @@ import { type EditorPluginFactory } from 'asljs-ui-editor';
2
2
  /**
3
3
  * The plugin of a project, the browser module of `asljs-ui-editor` the
4
4
  * server serves. It loads the graph from `_/data.json` of the project's
5
- * `base`. The graph is the first document, `rq:graph`, and stays open; a
6
- * double click on a node opens its document as another tab,
7
- * `rq:doc:<path>`. The diagram's selection is the editor's: a click on a node
8
- * selects it, and so does choosing it in the Nodes list, which also moves the
9
- * view to it. The graph's preview, on its right, views or edits the selected
5
+ * `base`. The graph is the first document, `rq:graph`; closed, it opens again
6
+ * from the Graph item at the top of the Nodes list. A double click on a node opens
7
+ * its document as another tab, `rq:doc:<path>`. The diagram's selection is
8
+ * the editor's: a click on a node selects it, and so does choosing it in the
9
+ * Nodes list, which also opens the graph and moves the view to it. The graph's preview, on its right, views or edits the selected
10
10
  * node, as the View and Edit tabs of its action bar choose. The diagram is
11
11
  * read-only: nodes move, but delete and resize do nothing. The search finds
12
12
  * the nodes by their titles, and opens their documents.
@@ -1,6 +1,7 @@
1
- import { createModelFromDocument, createPreviewModel, searchElements } from 'asljs-ui-diagram';
1
+ import { createModelFromDocument, createPreviewModel, diagramSvg, searchElements } from 'asljs-ui-diagram';
2
2
  import { element, fetchJson, ICONS as EDITOR_ICONS, note, pathHref, projectPath, searchMatcher, trackActive } from 'asljs-ui-editor';
3
3
  import { markdownDocument } from 'asljs-ui-markdown';
4
+ import { pdfOfSvg, showPdf } from 'asljs-ui-pdf';
4
5
  import { pathLine, renderText } from './document.js';
5
6
  import { ICONS } from './icons.js';
6
7
  import { buildOutline } from './outline.js';
@@ -15,11 +16,11 @@ const EDIT_NOTE = 'Implementation, Coverage and Status are kept as they are, aft
15
16
  /**
16
17
  * The plugin of a project, the browser module of `asljs-ui-editor` the
17
18
  * server serves. It loads the graph from `_/data.json` of the project's
18
- * `base`. The graph is the first document, `rq:graph`, and stays open; a
19
- * double click on a node opens its document as another tab,
20
- * `rq:doc:<path>`. The diagram's selection is the editor's: a click on a node
21
- * selects it, and so does choosing it in the Nodes list, which also moves the
22
- * view to it. The graph's preview, on its right, views or edits the selected
19
+ * `base`. The graph is the first document, `rq:graph`; closed, it opens again
20
+ * from the Graph item at the top of the Nodes list. A double click on a node opens
21
+ * its document as another tab, `rq:doc:<path>`. The diagram's selection is
22
+ * the editor's: a click on a node selects it, and so does choosing it in the
23
+ * Nodes list, which also opens the graph and moves the view to it. The graph's preview, on its right, views or edits the selected
23
24
  * node, as the View and Edit tabs of its action bar choose. The diagram is
24
25
  * read-only: nodes move, but delete and resize do nothing. The search finds
25
26
  * the nodes by their titles, and opens their documents.
@@ -32,7 +33,9 @@ const EDIT_NOTE = 'Implementation, Coverage and Status are kept as they are, aft
32
33
  */
33
34
  const createPlugin = async ({ id, base, model }) => {
34
35
  const { title, problems, diagram: { document: data, hrefs } } = await fetchJson(base, '_/data.json');
35
- const graphId = `${id}:graph`;
36
+ const graphDocument = { id: `${id}:graph`,
37
+ title };
38
+ const graphId = graphDocument.id;
36
39
  const labels = new Map(data.nodes.map(node => [node.id,
37
40
  node.label]));
38
41
  // The path of each node's document, relative to the project; none for a
@@ -44,7 +47,8 @@ const createPlugin = async ({ id, base, model }) => {
44
47
  .map(([node, file]) => [file,
45
48
  labels.get(node) ?? file]));
46
49
  const graph = createModelFromDocument(data);
47
- graph.deleteElements = () => { };
50
+ graph.deleteElements =
51
+ () => { };
48
52
  graph.resizeElement = () => false;
49
53
  const toDocument = (file) => ({ id: `${id}:doc:${file}`,
50
54
  title: titles.get(file)
@@ -60,7 +64,7 @@ const createPlugin = async ({ id, base, model }) => {
60
64
  : toDocument(file);
61
65
  };
62
66
  const selectNode = (node) => {
63
- model.activate(graphId);
67
+ model.open(graphDocument, { preview: false });
64
68
  graph.select([node]);
65
69
  requestAnimationFrame(() => graph.reveal(node));
66
70
  };
@@ -120,6 +124,7 @@ const createPlugin = async ({ id, base, model }) => {
120
124
  : undefined,
121
125
  note: EDIT_NOTE,
122
126
  editing: options.editing,
127
+ modes: options.modes,
123
128
  openLink: (url) => {
124
129
  const target = projectPath(base, url);
125
130
  if (target === null
@@ -142,6 +147,22 @@ const createPlugin = async ({ id, base, model }) => {
142
147
  button.addEventListener('dblclick', () => openNode(node));
143
148
  return button;
144
149
  };
150
+ // The Graph item, a list of its own above the nodes, titled as the graph's
151
+ // tab, whose title is also the first node's label: it opens the graph,
152
+ // or activates it, as an ordinary document, never the preview, which a node
153
+ // it opens would replace.
154
+ const graphItem = () => {
155
+ const button = element('button', 'asljs-plugin-item', 'Graph');
156
+ button.type = 'button';
157
+ button.dataset.document = graphId;
158
+ button.title = title;
159
+ button.addEventListener('click', () => model.open(graphDocument, { preview: false }));
160
+ const entry = element('li');
161
+ entry.append(button);
162
+ const list = element('ul', 'asljs-plugin-list');
163
+ list.append(entry);
164
+ return list;
165
+ };
145
166
  // The graph's preview: View shows the selected node's document, Edit
146
167
  // edits it; its state is kept in this browser.
147
168
  let stored = {};
@@ -176,7 +197,8 @@ const createPlugin = async ({ id, base, model }) => {
176
197
  title: 'Edit',
177
198
  isEnabled: selection => previewed(selection) !== null,
178
199
  render: selection => documentView(previewed(selection), { editing: true,
179
- save: true }) }];
200
+ save: true,
201
+ modes: false }) }];
180
202
  const graphView = () => {
181
203
  const view = element('div', 'rq-graph');
182
204
  const diagram = document.createElement('asljs-diagram');
@@ -203,8 +225,16 @@ const createPlugin = async ({ id, base, model }) => {
203
225
  const tabs = document.createElement('asljs-diagram-preview-tabs');
204
226
  tabs.modes = previewModes;
205
227
  tabs.preview = preview;
228
+ // Print shows a PDF of the whole graph, as it is drawn, in a dialog.
229
+ const print = element('button', 'rq-print', 'Print');
230
+ print.type = 'button';
231
+ print.title =
232
+ 'Print the graph as a PDF';
233
+ print.addEventListener('click', () => showPdf(pdfOfSvg(diagramSvg(document, graph.diagram.elements), { title }), { title }));
234
+ const end = element('div', 'rq-actionbar-end');
235
+ end.append(print, tabs);
206
236
  const bar = element('div', 'rq-actionbar');
207
- bar.append(zoom, tabs);
237
+ bar.append(zoom, end);
208
238
  view.append(stage, bar);
209
239
  requestAnimationFrame(() => graph.viewport.fit());
210
240
  return view;
@@ -237,9 +267,7 @@ const createPlugin = async ({ id, base, model }) => {
237
267
  return { id,
238
268
  title: 'Requirements',
239
269
  icon: ICONS.graph,
240
- documents: [{ id: graphId,
241
- title,
242
- closable: false }],
270
+ documents: [graphDocument],
243
271
  operations: [{ id: 'nodes',
244
272
  title: 'Nodes',
245
273
  icon: EDITOR_ICONS.list,
@@ -247,7 +275,10 @@ const createPlugin = async ({ id, base, model }) => {
247
275
  const list = outline(buildOutline(data.nodes, data.links));
248
276
  trackActive(model, list, { attribute: 'node',
249
277
  current: () => model.selection?.id ?? null });
250
- return list;
278
+ const panel = element('div');
279
+ panel.append(graphItem(), list);
280
+ trackActive(model, panel);
281
+ return panel;
251
282
  } },
252
283
  ...problems.length === 0
253
284
  ? []
@@ -259,11 +290,16 @@ const createPlugin = async ({ id, base, model }) => {
259
290
  list.append(...problems.map(note));
260
291
  return list;
261
292
  } }]],
262
- renderDocument: (opened) => opened.id === graphId
293
+ renderDocument: opened => opened.id === graphId
263
294
  ? graphView()
264
295
  : documentView(opened.data.path, { head: true,
265
296
  save: true,
266
297
  keep: opened.id }),
298
+ documentOf: location => location === 'graph'
299
+ ? graphDocument
300
+ : location.startsWith('doc:')
301
+ ? toDocument(location.slice('doc:'.length))
302
+ : null,
267
303
  search: async (query) => matching(query)
268
304
  .filter(node => labels.has(node))
269
305
  .flatMap((node) => {
@@ -272,7 +308,6 @@ const createPlugin = async ({ id, base, model }) => {
272
308
  ? []
273
309
  : [{ document: found,
274
310
  title: labels.get(node) }];
275
- }),
276
- dispose: () => model.select(null) };
311
+ }) };
277
312
  };
278
313
  export default createPlugin;
package/dist/view.d.ts CHANGED
@@ -12,7 +12,7 @@ export interface RqAppOptions {
12
12
  }
13
13
  /**
14
14
  * The `rq` application of `asljs-ui-editor`, an `EditorApp` of
15
- * `createEditorApp`. Each `rq.json` that `findProjects` finds from the
15
+ * `createEditorApp`. Each `rq.json` that `findAppProjects` finds from the
16
16
  * context's `cwd` is a project, whose folders `resolveFolders` reads from
17
17
  * that file only. A project serves its documents folder, or the folder of its
18
18
  * graph when none is set, which must contain the graph's folder. It answers
package/dist/view.js CHANGED
@@ -1,5 +1,5 @@
1
+ import { createEditorApp, createHandler, findAppProjects, findBrowserModules, jsonReply, postProcess, takeWritten, toDisplayPath, versionConflict, writeMarkdown } from 'asljs-appbase';
1
2
  import { isInside } from 'asljs-locator';
2
- import { createEditorApp, createHandler, findBrowserModules, findProjects, jsonReply, postProcess, takeWritten, toDisplayPath, versionConflict, writeMarkdown } from 'asljs-appbase';
3
3
  import { CONFIG_FILE, findTarget, getLogger, getNodeKind, getRequirementsDir, getResults, getVersion, isFile, loadGraph, readEditable, readIds, readImplementation, resolveFolders, toGraphDiagram, writeEdited } from 'asljs-rq';
4
4
  import { readFile } from 'node:fs/promises';
5
5
  import path from 'node:path';
@@ -120,6 +120,9 @@ asljs-diagram-preview { width: min(40rem, 45vw); border-left: 1px solid #ddd; ba
120
120
  .asljs-diagram-preview-placeholder { margin: 1rem; color: #757575; }
121
121
  .rq-actionbar { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: 2px 8px; background: #f3f4f6; border-top: 1px solid #ddd; }
122
122
  .rq-actionbar .asljs-diagram-zoom { display: flex; gap: 0.25rem; align-items: center; }
123
+ .rq-actionbar-end { display: flex; align-items: center; gap: 0.5rem; }
124
+ .rq-print { border: 0; border-radius: 4px; background: none; padding: 3px 10px; font: inherit; color: #555; cursor: pointer; }
125
+ .rq-print:hover { background: #e5e7eb; }
123
126
  .asljs-diagram-preview-tabs { gap: 2px; }
124
127
  .asljs-diagram-preview-tab { border: 0; border-radius: 4px; background: none; padding: 3px 10px; font: inherit; color: #555; cursor: pointer; }
125
128
  .asljs-diagram-preview-tab:hover:not(:disabled) { background: #e5e7eb; }
@@ -137,7 +140,7 @@ asljs-diagram-preview { width: min(40rem, 45vw); border-left: 1px solid #ddd; ba
137
140
  .rq-not-run { color: #757575; }`;
138
141
  /**
139
142
  * The `rq` application of `asljs-ui-editor`, an `EditorApp` of
140
- * `createEditorApp`. Each `rq.json` that `findProjects` finds from the
143
+ * `createEditorApp`. Each `rq.json` that `findAppProjects` finds from the
141
144
  * context's `cwd` is a project, whose folders `resolveFolders` reads from
142
145
  * that file only. A project serves its documents folder, or the folder of its
143
146
  * graph when none is set, which must contain the graph's folder. It answers
@@ -171,8 +174,9 @@ export async function createApp(context, options = {}) {
171
174
  target: found,
172
175
  folder };
173
176
  };
174
- const projects = await findProjects(context.cwd, CONFIG_FILE, context.files);
175
- return createEditorApp({ id: 'rq',
177
+ const projects = await findAppProjects(context, CONFIG_FILE);
178
+ return createEditorApp({
179
+ id: 'rq',
176
180
  title: 'Requirements',
177
181
  icon: ICONS.graph,
178
182
  base: context.base,
@@ -196,8 +200,10 @@ export async function createApp(context, options = {}) {
196
200
  // the server.
197
201
  modules: findBrowserModules(['asljs-ui-diagram',
198
202
  'asljs-ui-editor',
199
- 'asljs-ui-markdown'], path.dirname(fileURLToPath(import.meta.url))),
200
- style: APP_STYLE });
203
+ 'asljs-ui-markdown',
204
+ 'asljs-ui-pdf'], path.dirname(fileURLToPath(import.meta.url))),
205
+ style: APP_STYLE
206
+ });
201
207
  }
202
208
  /**
203
209
  * What `_/data.json` of a project answers: the title of the graph, the root's
package/docs/rq-srv.md CHANGED
@@ -51,20 +51,22 @@ documents.
51
51
  shows the panel, giving the documents the room. Dragging the panel's inner
52
52
  edge resizes it, and a double click on the edge resets it; the browser
53
53
  remembers the width for the next visit.
54
- - Projects: with more than one, a list box heads the panels and switches the
55
- project, closing the documents of the one before; the browser remembers the
56
- choice.
57
- - Documents: the first tab is the graph, which stays open. Double-clicking a
58
- node, in the graph or in a list, opens its document as another tab, which
59
- closes with its ×, a middle click, or Delete on the tab; the arrow keys,
60
- Home and End move between tabs.
54
+ - Projects: with more than one, a selector heads the panels and switches the
55
+ project, its filter box finding a project by its title; the documents of the
56
+ project before stay open, each tab's tooltip naming its project, and the
57
+ browser remembers the choice.
58
+ - Documents: the first tab is the graph, which closes like any other; the
59
+ Graph item at the top of the Nodes list opens it again, and so does choosing
60
+ a node. Double-clicking a node, in the graph or in a list, opens its
61
+ document as another tab, which closes with its ×, a middle click, or Delete
62
+ on the tab; the arrow keys, Home and End move between tabs.
61
63
  - Operations: **Nodes**, every requirement and test as an outline, each under
62
64
  the requirement that reaches it first; when the graph has structure errors,
63
65
  **Problems**, listing them; and the editor's **Search**, the nodes whose
64
66
  title contains what is typed, or matches it as a regular expression, which
65
67
  open their documents. Choosing a node in the Nodes list selects it, shows
66
- the graph and moves its view to the node; the selected node is marked in the
67
- list.
68
+ the graph, opening it when it is closed, and moves its view to the node; the
69
+ selected node is marked in the list.
68
70
  - `/_/app.json` - the application: its id, title, icon, the URL of its plugin
69
71
  and its projects, each with the path it is served at.
70
72
  - `/_/plugin/plugin.js` - the plugin, a module of the browser, and the modules
@@ -122,10 +124,14 @@ state, from its `## Status` section:
122
124
 
123
125
  Clicking a node selects it. The mouse wheel zooms, dragging empty space pans,
124
126
  and the zoom controls in its action bar, along its bottom, zoom and fit it; it
125
- is fitted when the page opens. Nodes can be dragged aside, but nothing on the
126
- page changes the graph's files: Delete and resizing do nothing, and a reload
127
- lays the graph out again. All roots are in the one diagram; the graph's tab is
128
- titled with the root's heading when there is one root, `Requirements` otherwise.
127
+ is fitted when the page opens. Print, at the right of the action bar, opens a
128
+ dialog with a PDF of the whole graph, made in the browser, on one A4 page under
129
+ the graph's title, to print or download: `diagramSvg` of `asljs-ui-diagram` and
130
+ `pdfOfSvg` of `asljs-ui-pdf`. Its text is Latin-1 only. Nodes can be dragged
131
+ aside, but nothing on the page changes the graph's files: Delete and resizing do
132
+ nothing, and a reload lays the graph out again. All roots are in the one
133
+ diagram; the graph's tab is titled with the root's heading when there is one
134
+ root, `Requirements` otherwise.
129
135
 
130
136
  The server is for local use: it serves every file inside the served folder,
131
137
  including hidden ones, and the editor renders the documents' HTML as it is. It
@@ -136,36 +142,38 @@ Nodes outside the served folder are drawn but do not open.
136
142
 
137
143
  ## Editing
138
144
 
139
- A requirement or test has an Edit button, in a tab next to its path. It opens:
145
+ A requirement or test opened as a tab has a bar docked at its bottom: View and
146
+ Edit, then a toolbar. Edit shows, in a panel docked at the top:
140
147
 
141
148
  - the title, in a field after the id, which stays as it is; the file keeps its
142
149
  name, which [`rq move`][MV] changes;
143
- - the rest of the text in a [Milkdown][MK] editor, without `## Implementation`,
144
- `## Coverage` and `## Status`, which the commands own. A link to a requirement
145
- or test by its id shows as the id alone, e.g. `R2`, and any other reference
146
- link as an inline link.
147
-
148
- Save, or Ctrl+S, writes the heading, the id and the title, then the text, then
149
- the definitions the owned sections use and the owned sections as they were. Each
150
- id of a requirement or test of the requirements folder in the text, outside
151
- code, becomes a reference link to it again, e.g. `[R2][]`, with its definition
152
- after the text. The document is then post-processed with
153
- `markdownPostProcessing` of `rq.json`, as after a command. A save is refused
154
- when the file changed since the editor opened it: reload the page and edit
155
- again. The save is a POST to `_/document.json`; Save and Cancel then show the
156
- document again. Milkdown, bundled with `asljs-ui-markdown`, is loaded from the
157
- server when Edit is pressed.
150
+ - under it, filling the tab, the rest of the text in a [Milkdown][MK] editor,
151
+ without `## Implementation`, `## Coverage` and `## Status`, which the commands
152
+ own. A link to a requirement or test by its id shows as the id alone, e.g.
153
+ `R2`, and any other reference link as an inline link.
154
+
155
+ Save, in the toolbar while editing, or Ctrl+S, writes the heading, the id and
156
+ the title, then the text, then the definitions the owned sections use and the
157
+ owned sections as they were. Each id of a requirement or test of the
158
+ requirements folder in the text, outside code, becomes a reference link to it
159
+ again, e.g. `[R2][]`, with its definition after the text. The document is then
160
+ post-processed with `markdownPostProcessing` of `rq.json`, as after a command. A
161
+ save is refused when the file changed since the editor opened it: reload the
162
+ page and edit again. The save is a POST to `_/document.json`; the editing then
163
+ goes on with the text as saved, and View shows it. Milkdown, bundled with
164
+ `asljs-ui-markdown`, is loaded from the server when Edit is first chosen.
158
165
 
159
166
  The graph has a preview of the selected node on its right, an
160
167
  `asljs-diagram-preview` of `asljs-ui-diagram`. The View and Edit tabs at the
161
168
  right of its action bar choose what it shows, and the button after them shows
162
169
  and hides it; dragging its left edge resizes it, and the browser remembers its
163
170
  width, whether it is shown and its tab. View shows the document without its path
164
- and without its Edit button; a link in it opens its document as a tab. Edit
165
- shows it editing at once. A document opened as a tab, with a double click, has
166
- its Edit button; the tab is a preview, with its title in italics, which the next
167
- document opened replaces, until it is double clicked or the document is edited.
168
- A title changed while editing shows in the graph after a reload.
171
+ and without a bar; a link in it opens its document as a tab. Edit shows it
172
+ editing at once, its bar with Save only, as the preview's own tabs are its
173
+ modes. A document opened as a tab, with a double click, has its bar; the tab is
174
+ a preview, with its title in italics, which the next document opened replaces,
175
+ until it is double clicked or the document is edited. A title changed while
176
+ editing shows in the graph after a reload.
169
177
 
170
178
  ## Options
171
179
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "asljs-rq-srv",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "The rq-srv web server: the requirements graph in an editor, and the documents rendered and edited in the browser.",
5
5
  "type": "module",
6
6
  "asljs": {
@@ -22,22 +22,37 @@
22
22
  "dist/**",
23
23
  "docs/**",
24
24
  "README.md",
25
- "LICENSE.md"
25
+ "LICENSE.md",
26
+ "!dist/*.tsbuildinfo"
26
27
  ],
27
28
  "scripts": {
28
29
  "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",
30
+ "clean:build": "node ../../../scripts/cleanBuildOutDir.js",
31
+ "clean:dist": "node ../../../scripts/cleanDistOutDir.js",
32
+ "compile:build": "npm run codegen --if-present && tsc -p tsconfig.build.json",
33
+ "compile:dist": "npm run codegen --if-present && tsc -p tsconfig.dist.json",
34
+ "build:dist": "node ../../../scripts/buildDistDeps.js && npm run compile:dist",
32
35
  "typecheck": "npm run codegen --if-present && tsc -p tsconfig.build.json --noEmit",
33
36
  "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",
37
+ "prepack": "npm run clean:build && npm run clean:dist && npm run build:dist && npm run compile:build && npm run test && npm run guard:clean-git",
35
38
  "postpublish": "toolkit tag-release-revision",
36
39
  "test": "node --test --enable-source-maps \"build/**/*.test.js\"",
37
40
  "test:watch": "node --watch --test \"build/**/*.test.js\"",
38
41
  "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"
42
+ "build": "npm run clean:build && npm run flint && npm run typecheck && npm run compile:build && npm run test"
40
43
  },
44
+ "distDependencies": [
45
+ "asljs-appbase",
46
+ "asljs-locator",
47
+ "asljs-logging",
48
+ "asljs-rq",
49
+ "asljs-testing",
50
+ "asljs-tmpdir",
51
+ "asljs-ui-diagram",
52
+ "asljs-ui-editor",
53
+ "asljs-ui-markdown",
54
+ "asljs-ui-pdf"
55
+ ],
41
56
  "keywords": [
42
57
  "requirements",
43
58
  "markdown",
@@ -60,7 +75,8 @@
60
75
  "asljs-rq": "^0.1.6",
61
76
  "asljs-ui-diagram": "^0.1.1",
62
77
  "asljs-ui-editor": "^0.1.1",
63
- "asljs-ui-markdown": "^0.1.0",
78
+ "asljs-ui-markdown": "^0.1.1",
79
+ "asljs-ui-pdf": "^0.1.0",
64
80
  "commander": "^15.0.0"
65
81
  },
66
82
  "devDependencies": {