asljs-part-srv 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 +51 -0
- package/README.md +79 -2
- package/bin/part-srv.js +12 -0
- package/dist/app.d.ts +10 -0
- package/dist/app.js +58 -0
- package/dist/browser/locations.d.ts +6 -0
- package/dist/browser/locations.js +13 -0
- package/dist/browser/plugin.d.ts +14 -0
- package/dist/browser/plugin.js +121 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/main.d.ts +15 -0
- package/dist/main.js +49 -0
- package/dist/project.d.ts +96 -0
- package/dist/project.js +134 -0
- package/dist/view.d.ts +20 -0
- package/dist/view.js +32 -0
- package/docs/part-srv.md +126 -0
- package/package.json +67 -4
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,80 @@
|
|
|
1
|
-
#
|
|
1
|
+
# part-srv
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> Part of [Alexandrite Software Library][#1] - a set of high-quality, performant
|
|
4
|
+
> JavaScript libraries and tools for everyday use.
|
|
5
|
+
|
|
6
|
+
## Overview
|
|
7
|
+
|
|
8
|
+
`asljs-part-srv` is the `part-srv` web server: the files of projects in an
|
|
9
|
+
editor in the browser, as a tree of folders, each opened as a tab of its
|
|
10
|
+
details - path, size, modification time - and the artefact definitions that
|
|
11
|
+
match it. Each `part.json` of the working directory, else of its folders, is a
|
|
12
|
+
project, chosen in a list box. It reads a project on every request and never
|
|
13
|
+
serves the content of a file.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install -g asljs-part-srv
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
part-srv
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
serves the projects of the working folder at the address it prints, until
|
|
28
|
+
stopped with Ctrl+C; `part-srv --working-dir ~/work` serves those of `~/work`.
|
|
29
|
+
Each project takes its root, definitions and state folder from its own
|
|
30
|
+
`part.json` only.
|
|
31
|
+
|
|
32
|
+
As a library:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { NullLoggerProvider } from 'asljs-logging';
|
|
36
|
+
import { serve } from 'asljs-appbase';
|
|
37
|
+
import { createApp } from 'asljs-part-srv';
|
|
38
|
+
|
|
39
|
+
const app = await createApp(
|
|
40
|
+
{ cwd: process.cwd(),
|
|
41
|
+
base: '/',
|
|
42
|
+
env: process.env,
|
|
43
|
+
loggerProvider: new NullLoggerProvider() });
|
|
44
|
+
|
|
45
|
+
const server = await serve(app.handler, { port: 8080 });
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`createApp(context)` is the application as an editor plugin, an `EditorApp` of
|
|
49
|
+
`asljs-appbase`, served at `context.base`, with a project for each `part.json`
|
|
50
|
+
found from `context.cwd`. `listProject(project)` gives the files and the other
|
|
51
|
+
artefacts the Artefacts panel lists, and `describeArtefact(project, location)`
|
|
52
|
+
the details a tab shows; the server answers them as JSON at `DATA_PATH` and
|
|
53
|
+
`ARTEFACT_PATH` of each project, and the plugin renders them in the browser.
|
|
54
|
+
`main(args, environment)` is the `part-srv` command.
|
|
55
|
+
|
|
56
|
+
## Further reading
|
|
57
|
+
|
|
58
|
+
- [part-srv][DOC] - the command, the projects, the editor and the data the
|
|
59
|
+
server answers.
|
|
60
|
+
- [Requirements][REQ] - what the server guarantees, as rq requirements.
|
|
61
|
+
|
|
62
|
+
## Related packages
|
|
63
|
+
|
|
64
|
+
- [asljs-part][PRT] - definitions, artefacts and plugins, as a library.
|
|
65
|
+
- [asljs-part-cli][CLI] - the `part` command line.
|
|
66
|
+
- [asljs-ui-editor][EDT] - the editor and its plugins.
|
|
67
|
+
- [appbase][MDC] - the document server behind `part-srv`.
|
|
68
|
+
|
|
69
|
+
## License
|
|
70
|
+
|
|
71
|
+
See [LICENSE][LIC] for details.
|
|
72
|
+
|
|
73
|
+
[#1]: https://github.com/AlexandriteSoftware/asljs
|
|
74
|
+
[CLI]: ../part-cli/README.md
|
|
75
|
+
[DOC]: docs/part-srv.md
|
|
76
|
+
[EDT]: ../../../ui/editor/README.md
|
|
77
|
+
[LIC]: LICENSE.md
|
|
78
|
+
[MDC]: ../../../libs/appbase/README.md
|
|
79
|
+
[PRT]: ../part/README.md
|
|
80
|
+
[REQ]: <dev/rq/R32 part-srv server.md>
|
package/bin/part-srv.js
ADDED
package/dist/app.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type EditorApp, type EditorAppContext } from 'asljs-appbase';
|
|
2
|
+
/**
|
|
3
|
+
* The part application as an editor plugin: its projects, by `part.json`.
|
|
4
|
+
* Each `part.json` `findProjects` finds from the working directory is a
|
|
5
|
+
* project. A project's root, definitions and state folder come from its own
|
|
6
|
+
* `part.json` only, else the default, when it is first requested; no option
|
|
7
|
+
* or environment variable sets them, since one value cannot suit every
|
|
8
|
+
* project.
|
|
9
|
+
*/
|
|
10
|
+
export declare function createApp(context: EditorAppContext): Promise<EditorApp>;
|
package/dist/app.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { configPath, configStrings, createEditorApp, findBrowserModules, findProjects, stateFolder } from 'asljs-appbase';
|
|
2
|
+
import { CONFIG_FILE, resolveDefinitionSource } from 'asljs-part';
|
|
3
|
+
import { ICONS } from 'asljs-ui-editor';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { createProjectHandler } from './view.js';
|
|
7
|
+
/**
|
|
8
|
+
* The style of an artefact's details: a scrolling view of the tab, its
|
|
9
|
+
* table of the location, path, size and modification time, and its list of
|
|
10
|
+
* definitions.
|
|
11
|
+
*/
|
|
12
|
+
const STYLE = `.part-view { height: 100%; box-sizing: border-box; overflow: auto; padding: 0.75rem 1rem; line-height: 1.5; }
|
|
13
|
+
.part-details { border-collapse: collapse; margin: 0 0 1rem; }
|
|
14
|
+
.part-details th { text-align: left; color: #555; font-weight: normal; padding: 2px 1rem 2px 0; vertical-align: top; }
|
|
15
|
+
.part-details td { padding: 2px 0; word-break: break-all; }
|
|
16
|
+
.part-definitions { padding-left: 1.25rem; }
|
|
17
|
+
.part-definitions li { margin: 0 0 0.6rem; }
|
|
18
|
+
.part-note { color: #757575; }`;
|
|
19
|
+
/**
|
|
20
|
+
* The part application as an editor plugin: its projects, by `part.json`.
|
|
21
|
+
* Each `part.json` `findProjects` finds from the working directory is a
|
|
22
|
+
* project. A project's root, definitions and state folder come from its own
|
|
23
|
+
* `part.json` only, else the default, when it is first requested; no option
|
|
24
|
+
* or environment variable sets them, since one value cannot suit every
|
|
25
|
+
* project.
|
|
26
|
+
*/
|
|
27
|
+
export async function createApp(context) {
|
|
28
|
+
const projects = await findProjects(context.cwd, CONFIG_FILE, context.files);
|
|
29
|
+
return createEditorApp({ id: 'part',
|
|
30
|
+
title: 'Part',
|
|
31
|
+
icon: ICONS.document,
|
|
32
|
+
base: context.base,
|
|
33
|
+
projects,
|
|
34
|
+
project: project => createProjectHandler(resolveProject(project, context)),
|
|
35
|
+
plugin: fileURLToPath(new URL('./browser/plugin.js', import.meta.url)),
|
|
36
|
+
// The page loads the plugin's packages, and what they import, from
|
|
37
|
+
// the server.
|
|
38
|
+
modules: findBrowserModules(['asljs-ui-editor'], path.dirname(fileURLToPath(import.meta.url))),
|
|
39
|
+
style: STYLE });
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The project part reads in an application project: `project`,
|
|
43
|
+
* `definitions` and `stateDir` of its `part.json`, each path resolved
|
|
44
|
+
* against the file's folder; the root defaults to the project's folder, the
|
|
45
|
+
* state folder to `.part` in the root (`stateFolder`). A definition source
|
|
46
|
+
* may be a package specifier, not a path, so `configPaths` does not apply.
|
|
47
|
+
*/
|
|
48
|
+
function resolveProject(project, context) {
|
|
49
|
+
const { config } = project;
|
|
50
|
+
const root = path.normalize(configPath(config, 'project')
|
|
51
|
+
?? project.folder);
|
|
52
|
+
return { root,
|
|
53
|
+
definitions: (configStrings(config, 'definitions')
|
|
54
|
+
?? [])
|
|
55
|
+
.map(source => resolveDefinitionSource(source, config.folder)),
|
|
56
|
+
stateDir: stateFolder(root, 'part', config),
|
|
57
|
+
loggerProvider: context.loggerProvider };
|
|
58
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The segments of a location, for `pathTree`: its scheme with its colon, as
|
|
3
|
+
* `file:`, then each segment of its path, anything between `/` or `\`. A
|
|
4
|
+
* location without a scheme, a relative path, is a `file:` one.
|
|
5
|
+
*/
|
|
6
|
+
export declare function locationSegments(location: string): string[];
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The segments of a location, for `pathTree`: its scheme with its colon, as
|
|
3
|
+
* `file:`, then each segment of its path, anything between `/` or `\`. A
|
|
4
|
+
* location without a scheme, a relative path, is a `file:` one.
|
|
5
|
+
*/
|
|
6
|
+
export function locationSegments(location) {
|
|
7
|
+
const scheme = /^[a-z][a-z0-9+.-]*:/i.exec(location)?.[0];
|
|
8
|
+
return [scheme ?? 'file:',
|
|
9
|
+
...location
|
|
10
|
+
.slice(scheme?.length ?? 0)
|
|
11
|
+
.split(/[/\\]/)
|
|
12
|
+
.filter(segment => segment !== '')];
|
|
13
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type EditorPluginFactory } from 'asljs-ui-editor';
|
|
2
|
+
/**
|
|
3
|
+
* The plugin of a project, the browser module of `asljs-ui-editor` the
|
|
4
|
+
* server serves. It loads the project's files and other artefacts from
|
|
5
|
+
* `_/data.json` once, when it is made. Its Artefacts operation is a
|
|
6
|
+
* `pathTree` of their locations, grouped by `locationSegments`, its groups
|
|
7
|
+
* folded. Choosing an entry opens its details as a document whose id is
|
|
8
|
+
* `part:<location>`, rendered here from `_/artefact.json`. Its search finds
|
|
9
|
+
* artefacts by location (`searchMatcher`), at most `MAX_RESULTS` of them: a
|
|
10
|
+
* file by its path, with or without `file:`, and another artefact by its
|
|
11
|
+
* location. Every URL is relative to the project's `base`.
|
|
12
|
+
*/
|
|
13
|
+
declare const createPlugin: EditorPluginFactory;
|
|
14
|
+
export default createPlugin;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { element, fetchJson, ICONS, note, pathTree, searchMatcher, tree } from 'asljs-ui-editor';
|
|
2
|
+
import { locationSegments } from './locations.js';
|
|
3
|
+
/**
|
|
4
|
+
* How many matches a search shows.
|
|
5
|
+
*/
|
|
6
|
+
const MAX_RESULTS = 200;
|
|
7
|
+
/**
|
|
8
|
+
* The plugin of a project, the browser module of `asljs-ui-editor` the
|
|
9
|
+
* server serves. It loads the project's files and other artefacts from
|
|
10
|
+
* `_/data.json` once, when it is made. Its Artefacts operation is a
|
|
11
|
+
* `pathTree` of their locations, grouped by `locationSegments`, its groups
|
|
12
|
+
* folded. Choosing an entry opens its details as a document whose id is
|
|
13
|
+
* `part:<location>`, rendered here from `_/artefact.json`. Its search finds
|
|
14
|
+
* artefacts by location (`searchMatcher`), at most `MAX_RESULTS` of them: a
|
|
15
|
+
* file by its path, with or without `file:`, and another artefact by its
|
|
16
|
+
* location. Every URL is relative to the project's `base`.
|
|
17
|
+
*/
|
|
18
|
+
const createPlugin = async ({ id, base, model }) => {
|
|
19
|
+
const { files, others } = await fetchJson(base, '_/data.json');
|
|
20
|
+
const documentOf = (location, title) => ({ id: `${id}:${location}`,
|
|
21
|
+
title,
|
|
22
|
+
data: { location } });
|
|
23
|
+
const artefactsPanel = () => {
|
|
24
|
+
const locations = [...files.map(file => `file:${file}`),
|
|
25
|
+
...others.map(other => other.location)];
|
|
26
|
+
if (locations.length === 0) {
|
|
27
|
+
return note('The project has no artefacts.');
|
|
28
|
+
}
|
|
29
|
+
return tree(model, pathTree(locations, { segments: locationSegments,
|
|
30
|
+
document: location => documentOf(location, locationSegments(location).at(-1)),
|
|
31
|
+
open: false }));
|
|
32
|
+
};
|
|
33
|
+
const renderDocument = (opened) => {
|
|
34
|
+
const { location } = opened.data;
|
|
35
|
+
const view = element('div', 'part-view');
|
|
36
|
+
void fetchJson(base, `_/artefact.json?location=${encodeURIComponent(location)}`)
|
|
37
|
+
.then(details => view.append(...detailsView(details)), (error) => view.append(note(error instanceof Error
|
|
38
|
+
? error.message
|
|
39
|
+
: String(error))));
|
|
40
|
+
return view;
|
|
41
|
+
};
|
|
42
|
+
return { id,
|
|
43
|
+
title: 'Part',
|
|
44
|
+
icon: ICONS.document,
|
|
45
|
+
operations: [{ id: 'artefacts',
|
|
46
|
+
title: 'Artefacts',
|
|
47
|
+
icon: ICONS.document,
|
|
48
|
+
render: artefactsPanel }],
|
|
49
|
+
renderDocument,
|
|
50
|
+
search: async (query) => {
|
|
51
|
+
const matches = searchMatcher(query);
|
|
52
|
+
return [...files
|
|
53
|
+
.filter(file => matches(`file:${file}`))
|
|
54
|
+
.map(file => ({ document: documentOf(`file:${file}`, file),
|
|
55
|
+
title: file })),
|
|
56
|
+
...others
|
|
57
|
+
.filter(other => matches(other.location))
|
|
58
|
+
.map(other => ({ document: documentOf(other.location, other.location),
|
|
59
|
+
title: other.location,
|
|
60
|
+
detail: other.name }))]
|
|
61
|
+
.slice(0, MAX_RESULTS);
|
|
62
|
+
} };
|
|
63
|
+
};
|
|
64
|
+
export default createPlugin;
|
|
65
|
+
/**
|
|
66
|
+
* The details of an artefact: its name, a table of its location and, for a
|
|
67
|
+
* file, its path, size and modification time, and the definitions that
|
|
68
|
+
* match it.
|
|
69
|
+
*/
|
|
70
|
+
function detailsView(details) {
|
|
71
|
+
const table = element('table', 'part-details');
|
|
72
|
+
table.append(row('Location', text(details.location)));
|
|
73
|
+
if (details.path !== null) {
|
|
74
|
+
table.append(row('Path', text(details.path)));
|
|
75
|
+
}
|
|
76
|
+
if (details.size !== null) {
|
|
77
|
+
table.append(row('Size', text(`${details.size} bytes`)));
|
|
78
|
+
}
|
|
79
|
+
if (details.modified !== null) {
|
|
80
|
+
const time = element('time', undefined, details.modified);
|
|
81
|
+
time.dateTime = details.modified;
|
|
82
|
+
table.append(row('Modified', time));
|
|
83
|
+
}
|
|
84
|
+
return [element('h1', undefined, details.name),
|
|
85
|
+
table,
|
|
86
|
+
element('h2', undefined, 'Definitions'),
|
|
87
|
+
definitionsView(details.definitions)];
|
|
88
|
+
}
|
|
89
|
+
function row(label, value) {
|
|
90
|
+
const made = element('tr');
|
|
91
|
+
const cell = element('td');
|
|
92
|
+
cell.append(value);
|
|
93
|
+
made.append(element('th', undefined, label), cell);
|
|
94
|
+
return made;
|
|
95
|
+
}
|
|
96
|
+
function text(value) {
|
|
97
|
+
return document.createTextNode(value);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The definitions that match an artefact, each with where it comes from and
|
|
101
|
+
* its description, or a note that none does.
|
|
102
|
+
*/
|
|
103
|
+
function definitionsView(definitions) {
|
|
104
|
+
if (definitions.length === 0) {
|
|
105
|
+
return note('No artefact definition matches it.');
|
|
106
|
+
}
|
|
107
|
+
const list = element('ul', 'part-definitions');
|
|
108
|
+
for (const definition of definitions) {
|
|
109
|
+
const origin = [definition.source,
|
|
110
|
+
definition.document]
|
|
111
|
+
.filter((part) => part !== null)
|
|
112
|
+
.join(', ');
|
|
113
|
+
const item = element('li');
|
|
114
|
+
item.append(element('strong', undefined, definition.name), ' ', element('span', 'part-note', `(${origin})`));
|
|
115
|
+
if (definition.description !== '') {
|
|
116
|
+
item.append(element('br'), definition.description);
|
|
117
|
+
}
|
|
118
|
+
list.append(item);
|
|
119
|
+
}
|
|
120
|
+
return list;
|
|
121
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { createApp } from './app.js';
|
|
2
|
+
export { main } from './main.js';
|
|
3
|
+
export { describeArtefact, listProject, type ArtefactDetails, type MatchingDefinition, type Project, type ProjectEntries } from './project.js';
|
|
4
|
+
export { ARTEFACT_PATH, DATA_PATH } from './view.js';
|
package/dist/index.js
ADDED
package/dist/main.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type CommandIo } from 'asljs-appbase';
|
|
2
|
+
/**
|
|
3
|
+
* Run the `part-srv` command: serve the projects in a browser until SIGINT or
|
|
4
|
+
* SIGTERM, then return 0; return 1, with the message on standard error, when
|
|
5
|
+
* the arguments are wrong or the server cannot start.
|
|
6
|
+
*
|
|
7
|
+
* Each `part.json` of the working folder, `--working-dir` else the working
|
|
8
|
+
* directory, else of its folders, is a project (`createApp`), its root,
|
|
9
|
+
* definitions and state folder from that file only. The port, the host and
|
|
10
|
+
* logging come from the options, else the nearest `part.json` of the working
|
|
11
|
+
* folder or a parent: `port` and `host`, and, below the `PART_LOG_`
|
|
12
|
+
* variables, the logging keys of `docs/Logging.md` at the repository root
|
|
13
|
+
* (`runMain`). A path of `part.json` resolves against its folder.
|
|
14
|
+
*/
|
|
15
|
+
export declare function main(args: string[], io?: CommandIo): Promise<number>;
|
package/dist/main.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { commandLine, configPort, configString, loadConfig, packageVersion, parsePort, runMain, serveApp } from 'asljs-appbase';
|
|
2
|
+
import { CONFIG_FILE } from 'asljs-part';
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { createApp } from './app.js';
|
|
6
|
+
/**
|
|
7
|
+
* Run the `part-srv` command: serve the projects in a browser until SIGINT or
|
|
8
|
+
* SIGTERM, then return 0; return 1, with the message on standard error, when
|
|
9
|
+
* the arguments are wrong or the server cannot start.
|
|
10
|
+
*
|
|
11
|
+
* Each `part.json` of the working folder, `--working-dir` else the working
|
|
12
|
+
* directory, else of its folders, is a project (`createApp`), its root,
|
|
13
|
+
* definitions and state folder from that file only. The port, the host and
|
|
14
|
+
* logging come from the options, else the nearest `part.json` of the working
|
|
15
|
+
* folder or a parent: `port` and `host`, and, below the `PART_LOG_`
|
|
16
|
+
* variables, the logging keys of `docs/Logging.md` at the repository root
|
|
17
|
+
* (`runMain`). A path of `part.json` resolves against its folder.
|
|
18
|
+
*/
|
|
19
|
+
export async function main(args, io = { cwd: process.cwd(),
|
|
20
|
+
env: process.env,
|
|
21
|
+
stdout: process.stdout,
|
|
22
|
+
stderr: process.stderr }) {
|
|
23
|
+
return await runMain(args, io, { prefix: 'PART_LOG_',
|
|
24
|
+
configFile: CONFIG_FILE }, async (loggerProvider) => {
|
|
25
|
+
const cli = commandLine(new Command('part-srv'), io, { workingDir: 'The folder the projects are looked for in; defaults to the current directory' });
|
|
26
|
+
cli
|
|
27
|
+
.description('Serve the projects of part.json files in a browser: the files of each as a tree, each with its details and the artefact definitions that match it.')
|
|
28
|
+
.version(packageVersion(import.meta.url), '--version')
|
|
29
|
+
.allowExcessArguments(false)
|
|
30
|
+
.option('--port <port>', 'Port to listen on, exactly; 0 picks a free one. By default port of part.json, else the first free port from 3000 on')
|
|
31
|
+
.option('--host <host>', 'Address to listen on. Defaults to host of part.json, then 127.0.0.1')
|
|
32
|
+
.action(async (options) => {
|
|
33
|
+
const cwd = path.resolve(io.cwd, options.workingDir ?? '.');
|
|
34
|
+
const config = await loadConfig(cwd, CONFIG_FILE);
|
|
35
|
+
await serveApp(await createApp({ cwd,
|
|
36
|
+
base: '/',
|
|
37
|
+
env: io.env,
|
|
38
|
+
loggerProvider }), { io,
|
|
39
|
+
cwd,
|
|
40
|
+
port: parsePort(options.port)
|
|
41
|
+
?? configPort(config),
|
|
42
|
+
host: options.host
|
|
43
|
+
?? configString(config, 'host'),
|
|
44
|
+
loggerProvider });
|
|
45
|
+
});
|
|
46
|
+
await cli.parseAsync(args, { from: 'user' });
|
|
47
|
+
return 0;
|
|
48
|
+
});
|
|
49
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { LoggerProvider } from 'asljs-logging';
|
|
2
|
+
/**
|
|
3
|
+
* The project part-srv shows, and how to read it.
|
|
4
|
+
*/
|
|
5
|
+
export interface Project {
|
|
6
|
+
/**
|
|
7
|
+
* Absolute path of the project root.
|
|
8
|
+
*/
|
|
9
|
+
root: string;
|
|
10
|
+
/**
|
|
11
|
+
* Definition sources, as `providersFactory` takes them.
|
|
12
|
+
*/
|
|
13
|
+
definitions: readonly string[];
|
|
14
|
+
/**
|
|
15
|
+
* The folder part keeps its caches in; `.part` under the root when absent.
|
|
16
|
+
*/
|
|
17
|
+
stateDir?: string;
|
|
18
|
+
loggerProvider: LoggerProvider;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* What the Files panel lists: the files of the project, and the artefacts
|
|
22
|
+
* that are not files.
|
|
23
|
+
*/
|
|
24
|
+
export interface ProjectEntries {
|
|
25
|
+
/**
|
|
26
|
+
* Project-relative POSIX paths, sorted: every file `.gitignore` does not
|
|
27
|
+
* leave out, outside `node_modules` and dot folders, and every file a
|
|
28
|
+
* definition covers.
|
|
29
|
+
*/
|
|
30
|
+
files: string[];
|
|
31
|
+
/**
|
|
32
|
+
* The artefacts that are not files, e.g. `npm:` or `git:` ones, sorted by
|
|
33
|
+
* location.
|
|
34
|
+
*/
|
|
35
|
+
others: {
|
|
36
|
+
location: string;
|
|
37
|
+
name: string;
|
|
38
|
+
}[];
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* A definition that matches an artefact.
|
|
42
|
+
*/
|
|
43
|
+
export interface MatchingDefinition {
|
|
44
|
+
name: string;
|
|
45
|
+
/**
|
|
46
|
+
* The first paragraph of its description.
|
|
47
|
+
*/
|
|
48
|
+
description: string;
|
|
49
|
+
/**
|
|
50
|
+
* Where it comes from: `markdown`, or the name of the plugin that gives it.
|
|
51
|
+
*/
|
|
52
|
+
source: string;
|
|
53
|
+
/**
|
|
54
|
+
* Project-relative path of its definition document, or the absolute path
|
|
55
|
+
* of one outside the project; `null` for a definition a plugin gives
|
|
56
|
+
* without a document.
|
|
57
|
+
*/
|
|
58
|
+
document: string | null;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* What part-srv shows of a file or another artefact.
|
|
62
|
+
*/
|
|
63
|
+
export interface ArtefactDetails {
|
|
64
|
+
location: string;
|
|
65
|
+
name: string;
|
|
66
|
+
/**
|
|
67
|
+
* Project-relative POSIX path of a file; `null` for another artefact.
|
|
68
|
+
*/
|
|
69
|
+
path: string | null;
|
|
70
|
+
/**
|
|
71
|
+
* The size of a file in bytes; `null` for another artefact.
|
|
72
|
+
*/
|
|
73
|
+
size: number | null;
|
|
74
|
+
/**
|
|
75
|
+
* When a file was last modified, as an ISO 8601 UTC time; `null` for
|
|
76
|
+
* another artefact.
|
|
77
|
+
*/
|
|
78
|
+
modified: string | null;
|
|
79
|
+
/**
|
|
80
|
+
* The definitions that match it, in the order part reports them; none for
|
|
81
|
+
* a file no definition covers.
|
|
82
|
+
*/
|
|
83
|
+
definitions: MatchingDefinition[];
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The files and other artefacts of the project.
|
|
87
|
+
*/
|
|
88
|
+
export declare function listProject(project: Project): Promise<ProjectEntries>;
|
|
89
|
+
/**
|
|
90
|
+
* The details of a file or another artefact, by its location: a `file:`
|
|
91
|
+
* location, or the path of a file, gives the file, whatever definitions
|
|
92
|
+
* match it; another location gives the artefact of that location. Throws
|
|
93
|
+
* for a file that does not exist or is outside the project, and for another
|
|
94
|
+
* location no artefact has.
|
|
95
|
+
*/
|
|
96
|
+
export declare function describeArtefact(project: Project, location: string): Promise<ArtefactDetails>;
|
package/dist/project.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { isInside } from 'asljs-locator';
|
|
2
|
+
import { toDisplayPath } from 'asljs-appbase';
|
|
3
|
+
import { FILE_SCHEME, providersFactory } from 'asljs-part';
|
|
4
|
+
import { stat } from 'node:fs/promises';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
/**
|
|
7
|
+
* The files and other artefacts of the project.
|
|
8
|
+
*/
|
|
9
|
+
export async function listProject(project) {
|
|
10
|
+
return await withProviders(project, async (providers) => {
|
|
11
|
+
const files = new Set((await providers.locationResolver.resolve(project.root, { pattern: '/**/*',
|
|
12
|
+
exclude: ['/**/node_modules/**',
|
|
13
|
+
'/**/.*/**'],
|
|
14
|
+
filters: [{ name: 'GitIgnore' }] }))
|
|
15
|
+
.map(file => toDisplayPath(project.root, file)));
|
|
16
|
+
const others = [];
|
|
17
|
+
for (const artefact of await providers.artefactProvider.getArtefacts()) {
|
|
18
|
+
if (artefact.location.startsWith(FILE_SCHEME)) {
|
|
19
|
+
files.add(artefact.location.slice(FILE_SCHEME.length));
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
others.push({ location: artefact.location,
|
|
23
|
+
name: artefact.name });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return { files: [...files].sort(),
|
|
27
|
+
others: others.sort((left, right) => left.location.localeCompare(right.location)) };
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The details of a file or another artefact, by its location: a `file:`
|
|
32
|
+
* location, or the path of a file, gives the file, whatever definitions
|
|
33
|
+
* match it; another location gives the artefact of that location. Throws
|
|
34
|
+
* for a file that does not exist or is outside the project, and for another
|
|
35
|
+
* location no artefact has.
|
|
36
|
+
*/
|
|
37
|
+
export async function describeArtefact(project, location) {
|
|
38
|
+
const filePath = fileLocationPath(location);
|
|
39
|
+
return await withProviders(project, async (providers) => {
|
|
40
|
+
const artefact = await providers.artefactProvider.tryGetArtefact(fileLocationOf(location, filePath));
|
|
41
|
+
const definitions = await matchingDefinitions(project, providers, artefact?.definitions ?? []);
|
|
42
|
+
if (filePath !== null) {
|
|
43
|
+
return await describeFile(project, filePath, artefact?.name, definitions);
|
|
44
|
+
}
|
|
45
|
+
if (artefact === null) {
|
|
46
|
+
throw new Error(`No artefact at ${location}`);
|
|
47
|
+
}
|
|
48
|
+
return { location: artefact.location,
|
|
49
|
+
name: artefact.name,
|
|
50
|
+
path: null,
|
|
51
|
+
size: null,
|
|
52
|
+
modified: null,
|
|
53
|
+
definitions };
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The project-relative path of a file location, or of a location without a
|
|
58
|
+
* scheme; `null` for any other location.
|
|
59
|
+
*/
|
|
60
|
+
function fileLocationPath(location) {
|
|
61
|
+
if (location.startsWith(FILE_SCHEME)) {
|
|
62
|
+
return location.slice(FILE_SCHEME.length);
|
|
63
|
+
}
|
|
64
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(location)) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
return location;
|
|
68
|
+
}
|
|
69
|
+
function fileLocationOf(location, filePath) {
|
|
70
|
+
if (filePath === null) {
|
|
71
|
+
return location;
|
|
72
|
+
}
|
|
73
|
+
return `${FILE_SCHEME}${filePath}`;
|
|
74
|
+
}
|
|
75
|
+
async function describeFile(project, filePath, name, definitions) {
|
|
76
|
+
const absolute = path.resolve(project.root, filePath);
|
|
77
|
+
const relative = toDisplayPath(project.root, absolute);
|
|
78
|
+
const stats = isInside(project.root, absolute, { orSame: false })
|
|
79
|
+
&& await stat(absolute).catch(() => null);
|
|
80
|
+
if (!stats
|
|
81
|
+
|| !stats.isFile()) {
|
|
82
|
+
throw new Error(`No file at ${filePath}`);
|
|
83
|
+
}
|
|
84
|
+
return { location: `${FILE_SCHEME}${relative}`,
|
|
85
|
+
name: name ?? path.posix.basename(relative),
|
|
86
|
+
path: relative,
|
|
87
|
+
size: stats.size,
|
|
88
|
+
modified: stats.mtime.toISOString(),
|
|
89
|
+
definitions };
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The definitions of the names, in their order.
|
|
93
|
+
*/
|
|
94
|
+
async function matchingDefinitions(project, providers, names) {
|
|
95
|
+
if (names.length === 0) {
|
|
96
|
+
return [];
|
|
97
|
+
}
|
|
98
|
+
const definitions = await providers.artefactDefinitionProvider
|
|
99
|
+
.getDefinitions();
|
|
100
|
+
return names
|
|
101
|
+
.map(name => definitions.find(definition => definition.name === name))
|
|
102
|
+
.filter((definition) => definition !== undefined)
|
|
103
|
+
.map(definition => ({ name: definition.name,
|
|
104
|
+
description: firstParagraph(definition.description),
|
|
105
|
+
source: definition.source,
|
|
106
|
+
document: documentOf(project.root, definition.path) }));
|
|
107
|
+
}
|
|
108
|
+
function documentOf(root, document) {
|
|
109
|
+
if (document === undefined) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
if (!isInside(root, document, { orSame: false })) {
|
|
113
|
+
return document;
|
|
114
|
+
}
|
|
115
|
+
return toDisplayPath(root, document);
|
|
116
|
+
}
|
|
117
|
+
function firstParagraph(text) {
|
|
118
|
+
return (text.trim().split(/\n\s*\n/)[0] ?? '')
|
|
119
|
+
.replace(/\s+/g, ' ')
|
|
120
|
+
.trim();
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Runs an action with providers made for it, and closes the content store
|
|
124
|
+
* after it, so that each request sees the project as it is.
|
|
125
|
+
*/
|
|
126
|
+
async function withProviders(project, action) {
|
|
127
|
+
const providers = providersFactory(project.loggerProvider, project.root, project.definitions, { stateFolder: project.stateDir });
|
|
128
|
+
try {
|
|
129
|
+
return await action(providers);
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
providers.contentStore.close();
|
|
133
|
+
}
|
|
134
|
+
}
|
package/dist/view.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type RequestHandler } from 'asljs-appbase';
|
|
2
|
+
import { type Project } from './project.js';
|
|
3
|
+
/**
|
|
4
|
+
* The details of an artefact, `describeArtefact`, by its `location` in the
|
|
5
|
+
* query, as JSON, relative to the project's path.
|
|
6
|
+
*/
|
|
7
|
+
export declare const ARTEFACT_PATH = "/_/artefact.json";
|
|
8
|
+
/**
|
|
9
|
+
* The files and other artefacts of the project, `listProject`, as JSON,
|
|
10
|
+
* relative to the project's path.
|
|
11
|
+
*/
|
|
12
|
+
export declare const DATA_PATH = "/_/data.json";
|
|
13
|
+
/**
|
|
14
|
+
* The handler of a project, relative to where it is mounted. It answers
|
|
15
|
+
* data, which the plugin renders: `DATA_PATH` the files and the other
|
|
16
|
+
* artefacts of the project, and `ARTEFACT_PATH` the details of an artefact,
|
|
17
|
+
* or 404 with `{ error }` saying why there is none. No file of the project
|
|
18
|
+
* is served; the project is read on every request.
|
|
19
|
+
*/
|
|
20
|
+
export declare function createProjectHandler(project: Project): RequestHandler;
|
package/dist/view.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { createHandler, jsonReply, messageOf } from 'asljs-appbase';
|
|
2
|
+
import { describeArtefact, listProject } from './project.js';
|
|
3
|
+
/**
|
|
4
|
+
* The details of an artefact, `describeArtefact`, by its `location` in the
|
|
5
|
+
* query, as JSON, relative to the project's path.
|
|
6
|
+
*/
|
|
7
|
+
export const ARTEFACT_PATH = '/_/artefact.json';
|
|
8
|
+
/**
|
|
9
|
+
* The files and other artefacts of the project, `listProject`, as JSON,
|
|
10
|
+
* relative to the project's path.
|
|
11
|
+
*/
|
|
12
|
+
export const DATA_PATH = '/_/data.json';
|
|
13
|
+
/**
|
|
14
|
+
* The handler of a project, relative to where it is mounted. It answers
|
|
15
|
+
* data, which the plugin renders: `DATA_PATH` the files and the other
|
|
16
|
+
* artefacts of the project, and `ARTEFACT_PATH` the details of an artefact,
|
|
17
|
+
* or 404 with `{ error }` saying why there is none. No file of the project
|
|
18
|
+
* is served; the project is read on every request.
|
|
19
|
+
*/
|
|
20
|
+
export function createProjectHandler(project) {
|
|
21
|
+
return createHandler({ folder: project.root,
|
|
22
|
+
json: { [DATA_PATH]: async () => await listProject(project),
|
|
23
|
+
[ARTEFACT_PATH]: async (url) => {
|
|
24
|
+
try {
|
|
25
|
+
return await describeArtefact(project, url.searchParams.get('location') ?? '');
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
return jsonReply(404, { error: `${messageOf(error)}.` });
|
|
29
|
+
}
|
|
30
|
+
} },
|
|
31
|
+
allow: () => false });
|
|
32
|
+
}
|
package/docs/part-srv.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# part-srv
|
|
2
|
+
|
|
3
|
+
Starts a web server that shows the files of projects and the artefact
|
|
4
|
+
definitions that match each, and runs until stopped, by SIGINT (Ctrl+C) or
|
|
5
|
+
SIGTERM.
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
part-srv [--working-dir <folder>] [--port <port>] [--host <host>]
|
|
9
|
+
[--loglevel <level>] [--logfile <target>] [--logformat <format>]
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
The server listens on `127.0.0.1` and prints the folder of its projects and its
|
|
13
|
+
address when it starts.
|
|
14
|
+
|
|
15
|
+
```text
|
|
16
|
+
part-srv --working-dir ~/work --port 8080
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Projects
|
|
20
|
+
|
|
21
|
+
A project is a folder with its own `part.json`, looked for in the working
|
|
22
|
+
folder, `--working-dir` or else the working directory:
|
|
23
|
+
|
|
24
|
+
- When the working folder has a `part.json`, it is the one project.
|
|
25
|
+
- Otherwise, every folder below it that has a `part.json` is a project, by its
|
|
26
|
+
path from the working folder, leaving out what `.gitignore` ignores, `.git`
|
|
27
|
+
and `node_modules`.
|
|
28
|
+
- With none, the working folder is the one project, with the nearest `part.json`
|
|
29
|
+
of a parent, or none.
|
|
30
|
+
|
|
31
|
+
The root, the definitions and the state folder of each project come from its own
|
|
32
|
+
`part.json` only, `project`, `definitions` and `stateDir`, its paths resolved
|
|
33
|
+
against its folder; see [Options][OPT] for the keys. Without `project`, the root
|
|
34
|
+
is the project's folder; without `definitions`, no definition matches anything;
|
|
35
|
+
without `stateDir`, the state folder is `.part` under the project. No option or
|
|
36
|
+
environment variable of the server sets them, since one value cannot suit every
|
|
37
|
+
project: `PART_PROJECT`, `PART_DEFINITIONS` and `PART_STATE_DIR` are read by
|
|
38
|
+
`part` only. They are read when the project is first opened: an invalid key of
|
|
39
|
+
one project's `part.json` fails the requests of that project only.
|
|
40
|
+
|
|
41
|
+
## Pages
|
|
42
|
+
|
|
43
|
+
The paths are relative to where the server serves `part-srv`, `/` alone; a
|
|
44
|
+
server that shows several applications serves it at `/part/`.
|
|
45
|
+
|
|
46
|
+
- `/` - an editor filling the window, built with [`asljs-editor`][ED] of
|
|
47
|
+
`asljs-ui-editor`, whose only plugin is part. Down the left is a column of
|
|
48
|
+
operations, and beside it the open details as tabs. Pressing the operation's
|
|
49
|
+
icon opens its panel next to the column; the button at the bottom of the
|
|
50
|
+
column hides and shows the panel. Dragging the panel's inner edge resizes it,
|
|
51
|
+
and the browser remembers the width, and the project shown, for the next
|
|
52
|
+
visit. With more than one project, a list box heads the panel; choosing
|
|
53
|
+
another project closes the details of the one before.
|
|
54
|
+
- **Artefacts** - the artefacts of the project as one tree of their locations,
|
|
55
|
+
folded at first: grouped by scheme, such as `file:`, `npm:` or `git:`, then
|
|
56
|
+
by each segment of the path, anything between `/` or `\`, as `npm:` >
|
|
57
|
+
`apps` > `kb` > `package.json#dependencies` > `asljs-part`; a location
|
|
58
|
+
without a scheme is a `file:` one. In each group its groups come first, then
|
|
59
|
+
its artefacts, by name. The files are every file `.gitignore` does not leave
|
|
60
|
+
out, outside `node_modules` and dot folders, and every file a definition
|
|
61
|
+
covers.
|
|
62
|
+
- **Search** - the shared search of the editor: the files and other artefacts
|
|
63
|
+
whose location holds the text, regardless of case unless Match case is
|
|
64
|
+
checked, or matches it as a regular expression: a file by its path, with or
|
|
65
|
+
without `file:`. At most 200 are listed.
|
|
66
|
+
|
|
67
|
+
Choosing a file or an artefact opens its details as a tab, or shows its tab
|
|
68
|
+
when it is open; the active one is marked in the list. The tab is a preview,
|
|
69
|
+
with its title in italics, which the next one chosen replaces, until it is
|
|
70
|
+
double clicked. A tab closes with its ×, a middle click, or Delete on the tab.
|
|
71
|
+
The details are rendered in the browser from `_/artefact.json`: the artefact's
|
|
72
|
+
name and location; for a file, its path, its size and when it was last
|
|
73
|
+
modified, in UTC; the artefact definitions that match it, each with where it
|
|
74
|
+
comes from - `markdown` or a plugin, and its definition document - and the
|
|
75
|
+
first paragraph of its description; a file no definition matches says so. A
|
|
76
|
+
file that does not exist, a path outside the project and an unknown location
|
|
77
|
+
give a note that says so.
|
|
78
|
+
- `/_/app.json` - the application and its projects, each with the path it is
|
|
79
|
+
served at; `/_/plugin/plugin.js` - the plugin the editor loads, a browser
|
|
80
|
+
module, with the modules beside it under `/_/plugin/`.
|
|
81
|
+
- `/p/<project>/` - a project, by its path from the working folder, `~` for the
|
|
82
|
+
working folder itself. It answers data only, which the plugin renders:
|
|
83
|
+
- `/p/<project>/_/data.json` - `{ files, others }`, the files and the other
|
|
84
|
+
artefacts of the project, which the plugin loads once, when the project is
|
|
85
|
+
shown;
|
|
86
|
+
- `/p/<project>/_/artefact.json?location=<location>` - the details of a file
|
|
87
|
+
or another artefact: `{ location, name, path, size, modified, definitions
|
|
88
|
+
}`, `path`, `size` and `modified` `null` for an artefact that is not a file,
|
|
89
|
+
and each definition `{ name, description, source, document }`. A location is
|
|
90
|
+
a `file:` location, a path relative to the project root, or the location of
|
|
91
|
+
another artefact. When there is none, the answer is 404 with `{ error }`,
|
|
92
|
+
the message the details tab shows.
|
|
93
|
+
|
|
94
|
+
The content of a file is never read or served: the server answers the details
|
|
95
|
+
only, and serves nothing of a project but its data. The project is read on every
|
|
96
|
+
request, so a reload shows the files as they are. The page loads
|
|
97
|
+
`asljs-ui-editor` and what it imports from the server, under `/modules/`, so it
|
|
98
|
+
needs no network.
|
|
99
|
+
|
|
100
|
+
## Options
|
|
101
|
+
|
|
102
|
+
- `--working-dir <folder>` - the folder the projects are looked for in, resolved
|
|
103
|
+
against the working directory; the working directory by default.
|
|
104
|
+
- `--port <port>` - the port, exactly: one in use is an error, and 0 picks any
|
|
105
|
+
free one. Without it, `port` of `part.json`, else the first free port from
|
|
106
|
+
3000 on, up to 3099.
|
|
107
|
+
- `--host <host>` - the address to listen on; `host` of `part.json`, else
|
|
108
|
+
`127.0.0.1`.
|
|
109
|
+
- `--loglevel`, `--logfile`, `--logformat` - logging, silent by default; they
|
|
110
|
+
take precedence over `PART_LOG_LEVEL`, `PART_LOG_FILE` and `PART_LOG_FORMAT`,
|
|
111
|
+
which take precedence over `logLevel`, `logFile` and `logFormat` of
|
|
112
|
+
`part.json`. See [Logging][LG].
|
|
113
|
+
- `--version`, `--help` - print the version of `asljs-part-srv`, or the help.
|
|
114
|
+
|
|
115
|
+
The `part.json` of `port`, `host` and logging is the nearest one in the working
|
|
116
|
+
folder or a parent of it; that of a project's settings is the project's, see
|
|
117
|
+
[Projects][PRJ]. A path of the file resolves against its folder; see
|
|
118
|
+
[Configuration][CFG] and the keys in [Options][OPT]. A `part.json` that cannot
|
|
119
|
+
be read, or a wrong `port`, `host` or logging key, stops the server with a
|
|
120
|
+
message naming the file.
|
|
121
|
+
|
|
122
|
+
[CFG]: ../../../../docs/Configuration.md
|
|
123
|
+
[ED]: ../../../../ui/editor/docs/Editor.md
|
|
124
|
+
[LG]: ../../../../docs/Logging.md
|
|
125
|
+
[OPT]: ../../part-cli/docs/Options.md
|
|
126
|
+
[PRJ]: #projects
|
package/package.json
CHANGED
|
@@ -1,6 +1,69 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "asljs-part-srv",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "The part-srv web server: a project's files in an editor, each with its details and the artefact definitions that match it.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"asljs": {
|
|
7
|
+
"type": "app"
|
|
8
|
+
},
|
|
9
|
+
"main": "dist/index.js",
|
|
10
|
+
"types": "dist/index.d.ts",
|
|
11
|
+
"bin": {
|
|
12
|
+
"part-srv": "bin/part-srv.js"
|
|
13
|
+
},
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"default": "./dist/index.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"bin/**",
|
|
22
|
+
"dist/**",
|
|
23
|
+
"docs/**",
|
|
24
|
+
"README.md",
|
|
25
|
+
"LICENSE.md"
|
|
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
|
+
"artefacts",
|
|
43
|
+
"project",
|
|
44
|
+
"server"
|
|
45
|
+
],
|
|
46
|
+
"homepage": "https://github.com/AlexandriteSoftware/asljs#readme",
|
|
47
|
+
"bugs": {
|
|
48
|
+
"url": "https://github.com/AlexandriteSoftware/asljs/issues"
|
|
49
|
+
},
|
|
50
|
+
"repository": {
|
|
51
|
+
"type": "git",
|
|
52
|
+
"url": "git+https://github.com/AlexandriteSoftware/asljs.git"
|
|
53
|
+
},
|
|
54
|
+
"author": "\"Alex Netkachov\" <alex.netkachov@gmail.com>",
|
|
55
|
+
"license": "SEE LICENSE IN LICENSE.md",
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"asljs-locator": "^0.1.1",
|
|
58
|
+
"asljs-logging": "^0.1.1",
|
|
59
|
+
"asljs-appbase": "^0.1.3",
|
|
60
|
+
"asljs-part": "^0.3.2",
|
|
61
|
+
"asljs-ui-editor": "^0.1.1",
|
|
62
|
+
"commander": "^15.0.0"
|
|
63
|
+
},
|
|
64
|
+
"devDependencies": {
|
|
65
|
+
"asljs-testing": "^0.1.0",
|
|
66
|
+
"asljs-tmpdir": "^0.1.13",
|
|
67
|
+
"asljs-toolkit": "^0.1.0"
|
|
68
|
+
}
|
|
69
|
+
}
|