asljs-appbase 0.1.4 → 0.1.5

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.
@@ -32,8 +32,35 @@ export interface Project {
32
32
  * nearest file of a parent, or with none (`loadConfig`). The files are
33
33
  * `files`, the files of `folder` as `listFiles` gives them, so that a server
34
34
  * of several applications lists them once; `listFiles(folder)` when absent.
35
+ *
36
+ * With `required`, a project needs the file: when the folder, the folders
37
+ * below it and those above it have none, it throws, for an application whose
38
+ * projects cannot be told without it, e.g. which artefacts a part project
39
+ * supports.
35
40
  */
36
- export declare function findProjects(folder: string, name: string, files?: readonly string[]): Promise<Project[]>;
41
+ export declare function findProjects(folder: string, name: string, files?: readonly string[], options?: {
42
+ required?: boolean;
43
+ }): Promise<Project[]>;
44
+ /**
45
+ * The projects of an application in the folders of `context` by its
46
+ * configuration file `name`: `findProjects` of `cwd` and `files` alone, or of
47
+ * each of `folders` when there are several. Then the id of a project starts
48
+ * with the name of its folder, `docs/notes` for `notes` of `docs`, the name
49
+ * alone for the folder itself, and a name that two folders share gets `-2`,
50
+ * `-3` and so on after the first. With `required`, a folder without the file
51
+ * has no project, and an error is thrown only when no folder has one.
52
+ */
53
+ export declare function findAppProjects(context: Pick<EditorAppContext, 'cwd' | 'files' | 'folders'>, name: string, options?: {
54
+ required?: boolean;
55
+ }): Promise<Project[]>;
56
+ /**
57
+ * A folder whose projects an application serves, and its files as `listFiles`
58
+ * gives them, when they are listed already.
59
+ */
60
+ export interface WorkingFolder {
61
+ cwd: string;
62
+ files?: readonly string[];
63
+ }
37
64
  /**
38
65
  * What the `createApp` of an application server, e.g. `asljs-kb-srv`, makes
39
66
  * its `EditorApp` with, whether it serves alone or in an aggregating server.
@@ -61,6 +88,12 @@ export interface EditorAppContext {
61
88
  * `host` of `createHandler`, which a `POST` must be sent to.
62
89
  */
63
90
  host?: string;
91
+ /**
92
+ * The folders whose projects are served, when an aggregating server serves
93
+ * several, `cwd` and `files` being those of the first: `findAppProjects`
94
+ * finds the projects of them all.
95
+ */
96
+ folders?: readonly WorkingFolder[];
64
97
  }
65
98
  /**
66
99
  * What a project handler is made with.
@@ -2,7 +2,7 @@ import { loadConfig } from 'asljs-cmdbase';
2
2
  import path from 'node:path';
3
3
  import { createImportMap, MODULES_PREFIX } from './browser-modules.js';
4
4
  import { listFiles } from './files.js';
5
- import { createHandler, mountHandlers, page, scriptJson, serve, serverUrl, untilStopped, send } from './server.js';
5
+ import { createHandler, mountHandlers, page, scriptJson, send, serve, serverUrl, untilStopped } from './server.js';
6
6
  /**
7
7
  * The path an application serves its plugin's folder at.
8
8
  */
@@ -14,8 +14,13 @@ const PLUGIN_PREFIX = '/_/plugin/';
14
14
  * nearest file of a parent, or with none (`loadConfig`). The files are
15
15
  * `files`, the files of `folder` as `listFiles` gives them, so that a server
16
16
  * of several applications lists them once; `listFiles(folder)` when absent.
17
+ *
18
+ * With `required`, a project needs the file: when the folder, the folders
19
+ * below it and those above it have none, it throws, for an application whose
20
+ * projects cannot be told without it, e.g. which artefacts a part project
21
+ * supports.
17
22
  */
18
- export async function findProjects(folder, name, files) {
23
+ export async function findProjects(folder, name, files, options = {}) {
19
24
  const root = path.resolve(folder);
20
25
  const listed = files
21
26
  ?? await listFiles(root);
@@ -30,7 +35,69 @@ export async function findProjects(folder, name, files) {
30
35
  return (await Promise.all(folders.map(found => toProject(root, found, name))))
31
36
  .sort((a, b) => a.id.localeCompare(b.id));
32
37
  }
33
- return [await toProject(root, root, name)];
38
+ const project = await toProject(root, root, name);
39
+ if (options.required === true
40
+ && project.config.file === null) {
41
+ throw new Error(`No ${name} in ${root}, in a folder below it, or in a folder above it.`);
42
+ }
43
+ return [project];
44
+ }
45
+ /**
46
+ * The projects of an application in the folders of `context` by its
47
+ * configuration file `name`: `findProjects` of `cwd` and `files` alone, or of
48
+ * each of `folders` when there are several. Then the id of a project starts
49
+ * with the name of its folder, `docs/notes` for `notes` of `docs`, the name
50
+ * alone for the folder itself, and a name that two folders share gets `-2`,
51
+ * `-3` and so on after the first. With `required`, a folder without the file
52
+ * has no project, and an error is thrown only when no folder has one.
53
+ */
54
+ export async function findAppProjects(context, name, options = {}) {
55
+ const folders = context.folders
56
+ ?? [{ cwd: context.cwd,
57
+ files: context.files }];
58
+ if (folders.length === 1) {
59
+ return await findProjects(folders[0].cwd, name, folders[0].files, options);
60
+ }
61
+ const labels = folderLabels(folders.map(folder => folder.cwd));
62
+ const projects = [];
63
+ for (const [index, folder] of folders.entries()) {
64
+ for (const project of await findProjects(folder.cwd, name, folder.files)) {
65
+ if (options.required === true
66
+ && project.config.file === null) {
67
+ continue;
68
+ }
69
+ const id = project.id === '.'
70
+ ? labels[index]
71
+ : `${labels[index]}/${project.id}`;
72
+ projects.push({ ...project,
73
+ id,
74
+ title: id });
75
+ }
76
+ }
77
+ if (projects.length === 0) {
78
+ throw new Error(`No ${name} in ${folders
79
+ .map(folder => path.resolve(folder.cwd))
80
+ .join(', ')}, in a folder below them, or in a folder above them.`);
81
+ }
82
+ return projects;
83
+ }
84
+ /**
85
+ * The name of each folder, by its base name, `-2`, `-3` and so on added to a
86
+ * name an earlier folder has.
87
+ */
88
+ function folderLabels(folders) {
89
+ const labels = [];
90
+ for (const folder of folders) {
91
+ const resolved = path.resolve(folder);
92
+ const base = path.basename(resolved)
93
+ || resolved;
94
+ let label = base;
95
+ for (let n = 2; labels.includes(label); n++) {
96
+ label = `${base}-${n}`;
97
+ }
98
+ labels.push(label);
99
+ }
100
+ return labels;
34
101
  }
35
102
  async function toProject(root, folder, name) {
36
103
  const id = path.relative(root, folder)
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  export { createImportMap, findBrowserModules, findModuleFile, MODULES_PREFIX, type BrowserModule } from './browser-modules.js';
2
2
  export { commandLine, configBoolean, configLogging, configPath, configPaths, configPort, configString, configStrings, loadCommandLineConfig, loadConfig, messageOf, packageVersion, parsePort, readOptionValue, runMain, workingFolder, type CommandIo, type Config } from 'asljs-cmdbase';
3
- export { combineEditorApps, createEditorApp, editorPage, findProjects, projectSegment, serveApp, type EditorApp, type EditorAppContext, type EditorAppDescriptor, type EditorAppOptions, type Project, type ProjectContext } from './editor-app.js';
3
+ export { combineEditorApps, createEditorApp, editorPage, findAppProjects, findProjects, projectSegment, serveApp, type EditorApp, type EditorAppContext, type EditorAppDescriptor, type EditorAppOptions, type Project, type ProjectContext, type WorkingFolder } from './editor-app.js';
4
4
  export { findMarkdownFiles, listFiles, toDisplayPath, type FindOptions } from './files.js';
5
5
  export { findSectionHeading, formatUrl, getSection, parseMarkdown, plainText, splitLocalUrl, type MarkdownNode } from './markdown.js';
6
6
  export { findConfig, postProcess, takeWritten, writeMarkdown, type PostProcessIo, type ToolConfig } from './post-process.js';
7
- export { createHandler, DEFAULT_PORT, escapeHtml, mountHandlers, jsonReply, page, scriptJson, send, serve, serverUrl, startServer, untilStopped, versionConflict, type HandlerOptions, type JsonReply, type ListenOptions, type PostReply, type RequestHandler, type ServerOptions } from './server.js';
7
+ export { createHandler, DEFAULT_PORT, escapeHtml, jsonReply, mountHandlers, page, scriptJson, send, serve, serverUrl, startServer, untilStopped, versionConflict, type HandlerOptions, type JsonReply, type ListenOptions, type PostReply, type RequestHandler, type ServerOptions } from './server.js';
8
8
  export { stateFolder, type GivenStateFolder } from './state-folder.js';
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  export { createImportMap, findBrowserModules, findModuleFile, MODULES_PREFIX } from './browser-modules.js';
2
2
  export { commandLine, configBoolean, configLogging, configPath, configPaths, configPort, configString, configStrings, loadCommandLineConfig, loadConfig, messageOf, packageVersion, parsePort, readOptionValue, runMain, workingFolder } from 'asljs-cmdbase';
3
- export { combineEditorApps, createEditorApp, editorPage, findProjects, projectSegment, serveApp } from './editor-app.js';
3
+ export { combineEditorApps, createEditorApp, editorPage, findAppProjects, findProjects, projectSegment, serveApp } from './editor-app.js';
4
4
  export { findMarkdownFiles, listFiles, toDisplayPath } from './files.js';
5
5
  export { findSectionHeading, formatUrl, getSection, parseMarkdown, plainText, splitLocalUrl } from './markdown.js';
6
6
  export { findConfig, postProcess, takeWritten, writeMarkdown } from './post-process.js';
7
- export { createHandler, DEFAULT_PORT, escapeHtml, mountHandlers, jsonReply, page, scriptJson, send, serve, serverUrl, startServer, untilStopped, versionConflict } from './server.js';
7
+ export { createHandler, DEFAULT_PORT, escapeHtml, jsonReply, mountHandlers, page, scriptJson, send, serve, serverUrl, startServer, untilStopped, versionConflict } from './server.js';
8
8
  export { stateFolder } from './state-folder.js';
package/docs/appbase.md CHANGED
@@ -118,14 +118,21 @@ values; the only HTML it sends is `index`, a page that loads its data.
118
118
  An application served as a plugin of the editor of `asljs-ui-editor`, with a
119
119
  selector of its projects; one server can serve one application, or several.
120
120
 
121
- - `findProjects(folder, name, files?)` - the projects of an application by its
122
- configuration file `name`, e.g. `kb.json`: `folder` itself when it has the
123
- file; else every folder below it that has one, by id; else `folder` itself
124
- with the nearest file of a parent, or none. It looks for the file in `files`,
125
- the files of `folder` as `listFiles` gives them, so that a server of several
126
- applications lists them once; it lists them itself without. A `Project` is `{
127
- id, title, folder, config }`; `id` is the folder relative to `folder`, `.` for
128
- itself.
121
+ - `findProjects(folder, name, files?, { required? })` - the projects of an
122
+ application by its configuration file `name`, e.g. `kb.json`: `folder` itself
123
+ when it has the file; else every folder below it that has one, by id; else
124
+ `folder` itself with the nearest file of a parent, or none; with `required`,
125
+ none throws. It looks for the file in `files`, the files of `folder` as
126
+ `listFiles` gives them, so that a server of several applications lists them
127
+ once; it lists them itself without. A `Project` is `{ id, title, folder,
128
+ config }`; `id` is the folder relative to `folder`, `.` for itself.
129
+ - `findAppProjects(context, name, { required? })` - the projects of an
130
+ application in the folders of an `EditorAppContext`: `findProjects` of its
131
+ `cwd` and `files`, or, with several `folders`, the projects of each, whose ids
132
+ start with the folder's name, such as `docs/notes`, the name alone for the
133
+ folder itself; a name two folders share gets `-2`, `-3` after the first. With
134
+ `required`, a folder without the file has no project, and it throws only when
135
+ no folder has one.
129
136
  - `createEditorApp({ id, title, icon, base?, projects, project, plugin, modules,
130
137
  style? })` - an `EditorApp`, whose `handler` answers, relative to `base`, `/`
131
138
  with the editor whose only plugin is the application, `/_/app.json` with an
@@ -140,11 +147,15 @@ selector of its projects; one server can serve one application, or several.
140
147
  prefix of the URLs its plugin reads, and `onClose` registers what `close()` of
141
148
  the application runs. `projectSegment(id)` is the segment of a project in a
142
149
  path.
143
- - `EditorAppContext` - `{ cwd, base, env, loggerProvider, files?, host? }`, what
144
- the `createApp` of an application server makes its `EditorApp` with, alone or
145
- in an aggregating server; `files`, the files of `cwd` an aggregating server
146
- listed once, go to `findProjects`, and `host`, the address the server listens
147
- on, to the `host` of each project's handler.
150
+ - `EditorAppContext` - `{ cwd, base, env, loggerProvider, files?, host?,
151
+ folders? }`, what the `createApp` of an application server makes its
152
+ `EditorApp` with, alone or in an aggregating server; `files`, the files of
153
+ `cwd` an aggregating server listed once, go to `findProjects`, and `host`, the
154
+ address the server listens on, to the `host` of each project's handler.
155
+ `folders`, each a `WorkingFolder` `{ cwd, files? }`, are the folders of an
156
+ aggregating server that serves several, `cwd` and `files` being the first's;
157
+ `createApp` finds its projects with `findAppProjects`, in one folder or in
158
+ all.
148
159
  - `combineEditorApps(title, apps)` - a handler that mounts each application at
149
160
  `/<id>/`, the `base` it was made with, and answers `/` with the editor whose
150
161
  plugins are all of them and `/modules/...` with the modules of them all.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "asljs-appbase",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "The base of the applications: configuration files and state folders, the entry point of an executable, markdown, the document server and editor applications.",
5
5
  "type": "module",
6
6
  "asljs": {
@@ -18,23 +18,33 @@
18
18
  "dist/**",
19
19
  "docs/**",
20
20
  "README.md",
21
- "LICENSE.md"
21
+ "LICENSE.md",
22
+ "!dist/*.tsbuildinfo"
22
23
  ],
23
24
  "scripts": {
24
25
  "flint": "toolkit flint",
25
- "clean": "toolkit clean",
26
- "build": "npm run codegen --if-present && tsc -p tsconfig.build.json",
27
- "build:dist": "npm run codegen --if-present && tsc -p tsconfig.dist.json",
26
+ "clean:build": "node ../../scripts/cleanBuildOutDir.js",
27
+ "clean:dist": "node ../../scripts/cleanDistOutDir.js",
28
+ "compile:build": "npm run codegen --if-present && tsc -p tsconfig.build.json",
29
+ "compile:dist": "npm run codegen --if-present && tsc -p tsconfig.dist.json",
30
+ "build:dist": "node ../../scripts/buildDistDeps.js && npm run compile:dist",
28
31
  "typecheck": "npm run codegen --if-present && tsc -p tsconfig.build.json --noEmit",
29
32
  "guard:clean-git": "toolkit ensure-clean-working-directory",
30
- "prepack": "npm run clean && npm run build:dist && npm run build && npm run test && npm run guard:clean-git",
33
+ "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",
31
34
  "postpublish": "toolkit tag-release-revision",
32
35
  "test": "node --test --enable-source-maps \"build/**/*.test.js\"",
33
36
  "test:watch": "node --watch --test \"build/**/*.test.js\"",
34
- "coverage": "npm run build && node --test --experimental-test-coverage --enable-source-maps --test-coverage-exclude=\"**/*.test.js\" \"build/**/*.test.js\"",
37
+ "coverage": "npm run compile:build && node --test --experimental-test-coverage --enable-source-maps --test-coverage-exclude=\"**/*.test.js\" \"build/**/*.test.js\"",
35
38
  "release:patch": "toolkit release-patch",
36
- "all": "npm run clean && npm run build:dist && npm run flint && npm run typecheck && npm run build && npm run test"
39
+ "build": "npm run clean:build && npm run flint && npm run typecheck && npm run compile:build && npm run test"
37
40
  },
41
+ "distDependencies": [
42
+ "asljs-agents",
43
+ "asljs-cmdbase",
44
+ "asljs-locator",
45
+ "asljs-logging",
46
+ "asljs-tmpdir"
47
+ ],
38
48
  "keywords": [
39
49
  "markdown",
40
50
  "cli"