bimplus-webclient 0.1.69 → 0.1.71
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/.github/documentation.instructions.md +17 -11
- package/JSDOC_MIGRATION_PLAN.md +60 -18
- package/README.md +10 -7
- package/jsdoc.json +19 -0
- package/package.json +19 -10
- package/scripts/generate-api-markdown.js +344 -0
- package/vitest.config.mjs +31 -2
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
---
|
|
2
|
-
applyTo: "src/**/*.js,
|
|
2
|
+
applyTo: "src/**/*.js, scripts/generate-api-markdown.js, jsdoc.json, documentation/**"
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
# Generating API Documentation
|
|
6
6
|
|
|
7
|
-
This project uses
|
|
7
|
+
This project uses [`jsdoc`](https://jsdoc.app/) to generate HTML API docs and parse doclets. The repository-owned `scripts/generate-api-markdown.js` renders those doclets into Markdown without a separate Markdown-generation dependency. `jsdoc.json` controls source discovery and HTML output to `./documentation`; the default JSDoc template creates `index.html` and API pages there, using `README.md` as landing-page content.
|
|
8
8
|
|
|
9
9
|
## Prerequisites
|
|
10
|
-
- Node dependencies installed (`npm install`) — `
|
|
10
|
+
- Node dependencies installed (`npm install`) — `jsdoc` is a devDependency.
|
|
11
11
|
- Python 3 available on PATH (only required for the Confluence conversion step, which runs `documentation/convert_to_confluence_md.py`).
|
|
12
12
|
- All exported functions/classes in `src/*.js` should have valid JSDoc comments; `lint-doc` will fail the build on invalid JSDoc.
|
|
13
13
|
|
|
@@ -15,11 +15,11 @@ This project uses the [`documentation`](https://github.com/documentationjs/docum
|
|
|
15
15
|
|
|
16
16
|
| Script | Purpose |
|
|
17
17
|
| --- | --- |
|
|
18
|
-
| `npm run lint-doc` | Runs `
|
|
19
|
-
| `npm run build-doc` | Lints, then
|
|
20
|
-
| `npm run build-docMd` | Lints, then
|
|
18
|
+
| `npm run lint-doc` | Runs `jsdoc --pedantic --configure jsdoc.json --explain` to parse source and validate JSDoc comments. |
|
|
19
|
+
| `npm run build-doc` | Lints, then generates **HTML** docs into `./documentation` with `jsdoc.json`. |
|
|
20
|
+
| `npm run build-docMd` | Lints, then renders JSDoc doclets into `./documentation/Bimplus_WebClient_doc.md` with the repository-owned generator. |
|
|
21
21
|
| `npm run build-docConf` | Runs `build-docMd`, then runs `documentation/convert_to_confluence_md.py` to produce `Bimplus_WebClient_doc_confluence.md`, a Confluence-friendly variant with fixed anchor links and an injected version header. |
|
|
22
|
-
| `npm run build-docAll` | Runs `build-doc`, `build-
|
|
22
|
+
| `npm run build-docAll` | Runs `build-doc`, `build-docMd`, and `build-docConf` to regenerate HTML, Markdown, and Confluence docs. |
|
|
23
23
|
|
|
24
24
|
## Typical Workflow
|
|
25
25
|
|
|
@@ -28,14 +28,18 @@ This project uses the [`documentation`](https://github.com/documentationjs/docum
|
|
|
28
28
|
3. Run `npm run build-docAll` to regenerate all doc formats at once (recommended before a release/publish).
|
|
29
29
|
- Or run an individual script (`build-doc`, `build-docMd`, `build-docConf`) if you only need one output format.
|
|
30
30
|
4. Review generated output:
|
|
31
|
-
- `./documentation/index.html`
|
|
31
|
+
- `./documentation/index.html` and the generated API pages for the HTML site.
|
|
32
32
|
- `./documentation/Bimplus_WebClient_doc.md` for the raw Markdown API doc.
|
|
33
33
|
- `./documentation/Bimplus_WebClient_doc_confluence.md` for the Confluence-paste-ready version.
|
|
34
34
|
5. Commit the regenerated files under `./documentation` along with your source changes.
|
|
35
35
|
|
|
36
|
-
## Notes on `
|
|
37
|
-
- Controls
|
|
38
|
-
-
|
|
36
|
+
## Notes on `jsdoc.json`
|
|
37
|
+
- Controls recursive JavaScript source discovery, ES module parsing, HTML destination, and README landing-page content.
|
|
38
|
+
- Update this configuration if the source layout or generated HTML destination changes.
|
|
39
|
+
|
|
40
|
+
## Notes on the Markdown renderer
|
|
41
|
+
- `scripts/generate-api-markdown.js` consumes JSDoc's structured `--explain` output and renders documented namespaces, methods, callbacks, parameters, returns, and explicit ID anchors. It fails if an internal link has no matching ID.
|
|
42
|
+
- The generator checks that each documented symbol has an anchor. Preserve the `BimExplorer.BimObjectProperties+load` anchor convention because Confluence links depend on it.
|
|
39
43
|
|
|
40
44
|
## Notes on the Confluence conversion (`convert_to_confluence_md.py`)
|
|
41
45
|
- Reads `./Bimplus_WebClient_doc.md` (must be generated first via `build-docMd`) and writes `./Bimplus_WebClient_doc_confluence.md`.
|
|
@@ -47,3 +51,5 @@ This project uses the [`documentation`](https://github.com/documentationjs/docum
|
|
|
47
51
|
- **`lint-doc` fails**: fix the JSDoc syntax/tag errors it reports; the `build-*` scripts depend on it succeeding first.
|
|
48
52
|
- **Confluence conversion fails with "file not found"**: run `npm run build-docMd` first, or use `npm run build-docConf` which does it automatically.
|
|
49
53
|
- **Python not found**: only `build-docConf`/`build-docAll` need Python; ensure `python` resolves on PATH (or update the script invocation to `python3` if needed on your OS).
|
|
54
|
+
|
|
55
|
+
Generated HTML and Markdown must be regenerated after changing source JSDoc comments, then reviewed and committed with the source changes.
|
package/JSDOC_MIGRATION_PLAN.md
CHANGED
|
@@ -18,15 +18,23 @@ Files expected to change:
|
|
|
18
18
|
- `package.json`
|
|
19
19
|
- `package-lock.json`
|
|
20
20
|
- New root-level `jsdoc.json` configuration
|
|
21
|
+
- New `scripts/generate-api-markdown.js` renderer
|
|
21
22
|
- `.github/documentation.instructions.md`
|
|
23
|
+
- `README.md`
|
|
24
|
+
- `documentation/convert_to_confluence_md.py`
|
|
22
25
|
- Generated files under `documentation/`
|
|
23
26
|
|
|
24
27
|
The following should remain functionally unchanged:
|
|
25
28
|
|
|
26
29
|
- JSDoc comments in `src/*.js`
|
|
27
|
-
- `documentation/convert_to_confluence_md.py`
|
|
28
30
|
- Application source, build, and test configuration
|
|
29
31
|
|
|
32
|
+
## Porting This Plan to Other Projects
|
|
33
|
+
|
|
34
|
+
Use this file as a migration template for each repository, not as a blind file-copy checklist. Before implementation, record the current source glob, generated output paths, existing `build-doc*` script names, Markdown heading/anchor conventions, and any downstream Confluence or publishing workflow. Preserve established output names and script interfaces when consumers depend on them; adapt paths and symbol grouping to that repository's JSDoc conventions.
|
|
35
|
+
|
|
36
|
+
The reference custom renderer in [scripts/generate-api-markdown.js](scripts/generate-api-markdown.js) is the implementation model. Copy it only after checking that the other repository's doclets use compatible `kind`, `memberof`, `scope`, and `longname` values. Add project-specific filters or display rules only when required by its documented public API.
|
|
37
|
+
|
|
30
38
|
## Implementation Steps
|
|
31
39
|
|
|
32
40
|
### 1. Replace development dependencies
|
|
@@ -34,14 +42,17 @@ The following should remain functionally unchanged:
|
|
|
34
42
|
Remove `documentation` from `devDependencies` and add:
|
|
35
43
|
|
|
36
44
|
- `jsdoc` for parsing source comments and generating HTML
|
|
37
|
-
-
|
|
45
|
+
- A repository-owned Markdown renderer using JSDoc's structured doclets
|
|
38
46
|
|
|
39
|
-
|
|
47
|
+
Remove any old generator dependencies and install JSDoc:
|
|
40
48
|
|
|
41
49
|
```powershell
|
|
42
|
-
npm
|
|
50
|
+
npm uninstall documentation jsdoc-to-markdown
|
|
51
|
+
npm install --save-dev jsdoc
|
|
43
52
|
```
|
|
44
53
|
|
|
54
|
+
Do not add a third-party Markdown generator. `scripts/generate-api-markdown.js` renders JSDoc doclets directly and validates symbol anchors.
|
|
55
|
+
|
|
45
56
|
Do not run `npm audit fix --force`; it may apply a breaking downgrade and does not provide a patched version of the vulnerable Vue 2 package.
|
|
46
57
|
|
|
47
58
|
### 2. Add `jsdoc.json`
|
|
@@ -53,7 +64,7 @@ Create a root-level JSDoc configuration with these requirements:
|
|
|
53
64
|
- Parse ES module syntax
|
|
54
65
|
- Write HTML output to `./documentation`
|
|
55
66
|
- Use `README.md` as the documentation landing-page content when supported
|
|
56
|
-
- Enable Markdown
|
|
67
|
+
- Enable JSDoc's Markdown plugin and recursive source discovery
|
|
57
68
|
|
|
58
69
|
Proposed configuration:
|
|
59
70
|
|
|
@@ -74,13 +85,12 @@ Proposed configuration:
|
|
|
74
85
|
"monospaceLinks": true
|
|
75
86
|
},
|
|
76
87
|
"plugins": [
|
|
77
|
-
"plugins/markdown"
|
|
78
|
-
"plugins/recurse"
|
|
88
|
+
"plugins/markdown"
|
|
79
89
|
]
|
|
80
90
|
}
|
|
81
91
|
```
|
|
82
92
|
|
|
83
|
-
Validate the exact configuration against the installed JSDoc version before committing it.
|
|
93
|
+
`opts.recurse: true` enables recursion. Do not add `plugins/recurse`: it is not shipped by JSDoc 4 and makes the command fail with "Unable to find the plugin". Validate the exact configuration against the installed JSDoc version before committing it.
|
|
84
94
|
|
|
85
95
|
### 3. Update npm scripts
|
|
86
96
|
|
|
@@ -90,13 +100,24 @@ Replace the current `documentation` commands with JSDoc equivalents while retain
|
|
|
90
100
|
{
|
|
91
101
|
"lint-doc": "jsdoc --pedantic --configure jsdoc.json --explain",
|
|
92
102
|
"build-doc": "npm run lint-doc && jsdoc --configure jsdoc.json",
|
|
93
|
-
"build-docMd": "npm run lint-doc &&
|
|
103
|
+
"build-docMd": "npm run lint-doc && node scripts/generate-api-markdown.js",
|
|
94
104
|
"build-docConf": "npm run build-docMd && cd ./documentation && python convert_to_confluence_md.py",
|
|
95
105
|
"build-docAll": "npm run build-doc && npm run build-docMd && npm run build-docConf"
|
|
96
106
|
}
|
|
97
107
|
```
|
|
98
108
|
|
|
99
|
-
|
|
109
|
+
`scripts/generate-api-markdown.js` replaces `jsdoc2md`; do not add a third-party Markdown renderer. The renderer should:
|
|
110
|
+
|
|
111
|
+
1. Invoke the installed JSDoc CLI with `process.execPath`, the local `node_modules/jsdoc/jsdoc.js` entrypoint, `--configure jsdoc.json`, and `--explain`. Run with the repository root as `cwd`, capture stdout/stderr, and fail clearly on a non-zero exit or invalid JSON.
|
|
112
|
+
2. Parse the JSON doclets and include documented public namespaces and supported API doclets only. Exclude undocumented, private, anonymous, and implementation-only symbols. Deduplicate repeated doclets by `longname`; group members by `memberof`; retain documented global functions.
|
|
113
|
+
3. Render the information present on doclets: namespace parameters, method signatures, descriptions, parameter tables, return values, callbacks/properties, types/defaults, deprecation/since/throws/see tags, and examples where present. Preserve the Markdown/HTML descriptions produced by JSDoc.
|
|
114
|
+
4. Emit both a useful API index and a section for each documented symbol. Escape values placed in code or Markdown tables without altering trusted JSDoc description markup.
|
|
115
|
+
5. Emit explicit `<a id="…" name="…"></a>` targets. Keep each fragment used in a Markdown link identical to an emitted ID. Normalize instance-method `#` separators to the existing `+` anchor form; preserve dots for static members/callbacks. For example, `BimExplorer#getUrl` maps to `BimExplorer+getUrl`, while `BimExplorer.this.onObjectSelected` keeps its dotted name. Handle nested names such as `BimExplorer.BimObjectProperties#load` explicitly.
|
|
116
|
+
6. Before writing the file, fail if a rendered API symbol has no anchor or if any generated `](#target)` link has no matching anchor ID. Create the destination directory if needed and write UTF-8.
|
|
117
|
+
|
|
118
|
+
The checks in the renderer protect the source Markdown structure. They do not replace rendering the Markdown and testing real click navigation in the target preview/browser.
|
|
119
|
+
|
|
120
|
+
The `lint-doc` command should be tested with the installed JSDoc CLI. `--explain` emits a large doclet JSON report; suppress or capture that output in automated logs if it becomes unwieldy, while preserving the command's exit status.
|
|
100
121
|
|
|
101
122
|
### 4. Regenerate documentation
|
|
102
123
|
|
|
@@ -115,7 +136,14 @@ Then run the complete workflow:
|
|
|
115
136
|
npm run build-docAll
|
|
116
137
|
```
|
|
117
138
|
|
|
118
|
-
The Confluence converter should continue reading the generated Markdown from its current location.
|
|
139
|
+
The Confluence converter should continue reading the generated Markdown from its current location. It must:
|
|
140
|
+
|
|
141
|
+
- Read and write UTF-8 explicitly so Unicode output works on Windows.
|
|
142
|
+
- Rewrite links case-insensitively when JSDoc preserves case in fragments.
|
|
143
|
+
- When rewriting a target to `WebClientApidescription-{fragment}`, emit a matching `<a id="WebClientApidescription-{fragment}" name="WebClientApidescription-{fragment}"></a>` alias. Rewriting links without creating matching targets leaves the converted document's links dead.
|
|
144
|
+
- Validate every converted internal Markdown link against an emitted ID before writing the final Confluence file.
|
|
145
|
+
|
|
146
|
+
Keep the raw Markdown IDs and Confluence aliases separate: the raw file uses the API fragments; only the Confluence variant uses the required `WebClientApidescription-` prefix.
|
|
119
147
|
|
|
120
148
|
### 5. Review generated output
|
|
121
149
|
|
|
@@ -124,32 +152,38 @@ Check the following:
|
|
|
124
152
|
- `documentation/index.html` opens successfully
|
|
125
153
|
- HTML assets load without missing-file errors
|
|
126
154
|
- All public classes, methods, namespaces, callbacks, parameters, and return values are represented
|
|
155
|
+
- Every rendered API symbol has a generated Markdown anchor, and internal Markdown links resolve
|
|
127
156
|
- Markdown headings and links are readable
|
|
128
157
|
- Confluence anchors contain the expected `WebClientApidescription-` prefix
|
|
158
|
+
- Confluence-prefixed links have matching prefixed target IDs
|
|
129
159
|
- No stale files from the previous generator remain in the generated output
|
|
130
160
|
|
|
161
|
+
Do not treat matching fragment strings in source files as proof of working links. Render both Markdown files with the parser/preview used by their consumers, inspect the resulting `href` and `id` values, and click representative links: a namespace, an instance method containing `+`, a static callback containing `.`, and the nested `BimObjectProperties` method. If the consumer is VS Code Markdown Preview, test there; if the Confluence Markdown is pasted into Confluence, verify links in Confluence's preview/page because its anchor generation rules differ from local Markdown renderers.
|
|
162
|
+
|
|
131
163
|
JSDoc will produce a different HTML structure and may use different anchor names than documentation.js. This is expected, but no public API should disappear.
|
|
132
164
|
|
|
133
165
|
### 6. Update repository guidance
|
|
134
166
|
|
|
135
|
-
Update `.github/documentation.instructions.md` to describe:
|
|
167
|
+
Update `.github/documentation.instructions.md` and `README.md` to describe:
|
|
136
168
|
|
|
137
169
|
- `jsdoc.json`
|
|
138
|
-
- `jsdoc` and
|
|
170
|
+
- `jsdoc` and the repository-owned Markdown renderer
|
|
139
171
|
- The revised npm scripts
|
|
140
172
|
- The new HTML output structure
|
|
141
173
|
- The continued Markdown-to-Confluence conversion
|
|
142
174
|
- The fact that generated output must be regenerated after source JSDoc changes
|
|
175
|
+
- The repository-owned Markdown renderer and its symbol/link validation
|
|
176
|
+
- The distinction between raw Markdown IDs and Confluence-prefixed alias IDs
|
|
143
177
|
|
|
144
178
|
### 7. Remove obsolete dependency output
|
|
145
179
|
|
|
146
180
|
After a successful install, verify that the old packages are gone:
|
|
147
181
|
|
|
148
182
|
```powershell
|
|
149
|
-
npm ls documentation vue-template-compiler
|
|
183
|
+
npm ls documentation jsdoc-to-markdown vue-template-compiler
|
|
150
184
|
```
|
|
151
185
|
|
|
152
|
-
The command should report that
|
|
186
|
+
The command should report that none of these packages is installed, or that they are not found in the dependency tree. Search scripts, instructions, READMEs, and the lockfile for stale `jsdoc2md`/`jsdoc-to-markdown` references; a removal-check command in this plan may name the package intentionally.
|
|
153
187
|
|
|
154
188
|
Review `package-lock.json` for removal of the obsolete dependency chain. Do not remove unrelated lockfile changes produced by the package manager.
|
|
155
189
|
|
|
@@ -166,6 +200,12 @@ npm test
|
|
|
166
200
|
npm audit
|
|
167
201
|
```
|
|
168
202
|
|
|
203
|
+
Also lint the renderer directly if the repository ESLint script does not include it:
|
|
204
|
+
|
|
205
|
+
```powershell
|
|
206
|
+
npx eslint scripts/generate-api-markdown.js
|
|
207
|
+
```
|
|
208
|
+
|
|
169
209
|
Expected results:
|
|
170
210
|
|
|
171
211
|
- Documentation commands complete successfully
|
|
@@ -173,6 +213,8 @@ Expected results:
|
|
|
173
213
|
- The application build remains successful
|
|
174
214
|
- Existing tests remain successful
|
|
175
215
|
- The `vue-template-compiler` audit finding is no longer introduced by this documentation toolchain
|
|
216
|
+
- `jsdoc-to-markdown` is absent from the manifest, lockfile dependency tree, and generation scripts
|
|
217
|
+
- Rendered raw Markdown and Confluence Markdown links navigate to their intended targets in their actual consumers
|
|
176
218
|
|
|
177
219
|
`npm audit` may still report unrelated vulnerabilities. Record those separately instead of applying a forced dependency downgrade.
|
|
178
220
|
|
|
@@ -181,9 +223,9 @@ Expected results:
|
|
|
181
223
|
If the generated output loses API content or the Confluence conversion cannot be adapted safely:
|
|
182
224
|
|
|
183
225
|
1. Restore the previous `package.json` and `package-lock.json` documentation dependencies.
|
|
184
|
-
2. Remove `jsdoc.json`.
|
|
226
|
+
2. Remove `jsdoc.json` and `scripts/generate-api-markdown.js`.
|
|
185
227
|
3. Restore the previous generated `documentation/` output.
|
|
186
|
-
4. Reassess the JSDoc configuration and
|
|
228
|
+
4. Reassess the JSDoc configuration and renderer before retrying.
|
|
187
229
|
|
|
188
230
|
Do not modify application source code as part of rollback.
|
|
189
231
|
|
|
@@ -193,7 +235,7 @@ The migration is complete when:
|
|
|
193
235
|
|
|
194
236
|
- `documentation` is no longer a development dependency
|
|
195
237
|
- JSDoc generates readable HTML from `src/*.js`
|
|
196
|
-
-
|
|
238
|
+
- The repository-owned renderer generates the API Markdown file
|
|
197
239
|
- The existing Confluence conversion succeeds
|
|
198
240
|
- `build-doc`, `build-docMd`, `build-docConf`, and `build-docAll` all work
|
|
199
241
|
- Generated documentation is reviewed and committed
|
package/README.md
CHANGED
|
@@ -74,19 +74,22 @@ goto your application folder which uses the webclient
|
|
|
74
74
|
npm install
|
|
75
75
|
|
|
76
76
|
### Unit testing
|
|
77
|
-
The
|
|
77
|
+
The tests run with Vitest in jsdom, Chromium, and Firefox. Install the browser
|
|
78
|
+
binaries with `npx playwright install` before the first run. Run the tests
|
|
79
|
+
with coverage using:
|
|
78
80
|
|
|
79
|
-
npm test
|
|
81
|
+
npm run test
|
|
82
|
+
|
|
83
|
+
The browser test API binds to `127.0.0.1`, starting at port `41315`, outside the
|
|
84
|
+
Windows default dynamic port range. If the port is in use, Vitest selects the
|
|
85
|
+
next available port so multiple test runs can execute concurrently.
|
|
80
86
|
|
|
81
87
|
Run Vitest in watch mode during development with:
|
|
82
88
|
|
|
83
89
|
npm run test-watch
|
|
84
90
|
|
|
85
91
|
### Documentation
|
|
86
|
-
Bimplus webclient uses
|
|
87
|
-
To generate a documentation please install documentation plugin via npm :
|
|
88
|
-
|
|
89
|
-
npm install -g documentation
|
|
92
|
+
Bimplus webclient uses JSDoc for HTML documentation and a repository-owned renderer for Markdown API documentation. Install project dependencies with `npm install` before generating docs.
|
|
90
93
|
|
|
91
94
|
To create documentation in html format go to your webclient folder and run :
|
|
92
95
|
|
|
@@ -98,7 +101,7 @@ To create documentation in markdown format go to your webclient folder and run :
|
|
|
98
101
|
|
|
99
102
|
npm run build-docMd
|
|
100
103
|
|
|
101
|
-
Documentation output will be in webclient/documentation/
|
|
104
|
+
Documentation output will be in `webclient/documentation/Bimplus_WebClient_doc.md`.
|
|
102
105
|
|
|
103
106
|
To create a markdown suitable for Confluence run script:
|
|
104
107
|
|
package/jsdoc.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"source": {
|
|
3
|
+
"include": ["src"],
|
|
4
|
+
"includePattern": ".*\\.js$",
|
|
5
|
+
"excludePattern": "(^|[\\\\/])node_modules[\\\\/]"
|
|
6
|
+
},
|
|
7
|
+
"opts": {
|
|
8
|
+
"destination": "./documentation",
|
|
9
|
+
"recurse": true,
|
|
10
|
+
"readme": "./README.md"
|
|
11
|
+
},
|
|
12
|
+
"templates": {
|
|
13
|
+
"cleverLinks": false,
|
|
14
|
+
"monospaceLinks": true
|
|
15
|
+
},
|
|
16
|
+
"plugins": [
|
|
17
|
+
"plugins/markdown"
|
|
18
|
+
]
|
|
19
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bimplus-webclient",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.71",
|
|
4
4
|
"description": "bim+ web client",
|
|
5
5
|
"types": "types/bimplus-webclient.d.ts",
|
|
6
6
|
"keywords": [
|
|
@@ -24,31 +24,40 @@
|
|
|
24
24
|
"bump": "npm run bump:patch",
|
|
25
25
|
"bump:patch": "npm version patch --no-git-tag-version",
|
|
26
26
|
"npm-pack": "npm run build-prod && npm pack",
|
|
27
|
-
"lint-doc": "
|
|
28
|
-
"build-doc": "npm run lint-doc &&
|
|
29
|
-
"build-docMd": "npm run lint-doc &&
|
|
27
|
+
"lint-doc": "jsdoc --pedantic --configure jsdoc.json --explain",
|
|
28
|
+
"build-doc": "npm run lint-doc && jsdoc --configure jsdoc.json",
|
|
29
|
+
"build-docMd": "npm run lint-doc && node scripts/generate-api-markdown.js",
|
|
30
30
|
"build-docConf": "npm run build-docMd && cd ./documentation && python convert_to_confluence_md.py",
|
|
31
|
-
"build-docAll": "npm run build-doc && npm run build-
|
|
31
|
+
"build-docAll": "npm run build-doc && npm run build-docMd && npm run build-docConf",
|
|
32
32
|
"test": "vitest run --coverage",
|
|
33
33
|
"test-watch": "vitest"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@babel/core": "^8.0.
|
|
37
|
-
"@babel/preset-env": "^8.0.
|
|
36
|
+
"@babel/core": "^8.0.7",
|
|
37
|
+
"@babel/preset-env": "^8.0.7",
|
|
38
38
|
"@eslint/js": "^10.0.1",
|
|
39
|
-
"@vitest/
|
|
39
|
+
"@vitest/browser-playwright": "^5.0.4",
|
|
40
|
+
"@vitest/coverage-istanbul": "^5.0.4",
|
|
40
41
|
"babel-loader": "^10.1.1",
|
|
41
|
-
"documentation": "^14.0.3",
|
|
42
42
|
"eslint": "^10.12.0",
|
|
43
43
|
"globals": "17.13.0",
|
|
44
|
+
"jsdoc": "^4.0.5",
|
|
44
45
|
"jsdom": "^30.1.2",
|
|
46
|
+
"playwright": "^1.64.0",
|
|
45
47
|
"terser-webpack-plugin": "^5.6.1",
|
|
46
|
-
"vitest": "^5.0.
|
|
48
|
+
"vitest": "^5.0.4",
|
|
47
49
|
"webpack": "^5.111.1",
|
|
48
50
|
"webpack-cli": "^7.2.3",
|
|
49
51
|
"webpack-merge": "^6.0.1"
|
|
50
52
|
},
|
|
51
53
|
"dependencies": {
|
|
52
54
|
"jquery": "4.0.0"
|
|
55
|
+
},
|
|
56
|
+
"devEngines": {
|
|
57
|
+
"runtime": {
|
|
58
|
+
"name": "node",
|
|
59
|
+
"version": "^22.22.2 || ^24.15.0 || >=26.0.0",
|
|
60
|
+
"onFail": "error"
|
|
61
|
+
}
|
|
53
62
|
}
|
|
54
63
|
}
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
const fs = require("node:fs");
|
|
2
|
+
const path = require("node:path");
|
|
3
|
+
const { spawnSync } = require("node:child_process");
|
|
4
|
+
const process = require("node:process");
|
|
5
|
+
|
|
6
|
+
const rootDirectory = path.resolve(__dirname, "..");
|
|
7
|
+
const outputPath = path.join(rootDirectory, "documentation", "Bimplus_WebClient_doc.md");
|
|
8
|
+
const namespaceKinds = new Set(["function", "member", "constant", "event", "typedef"]);
|
|
9
|
+
|
|
10
|
+
function escapeCode(value) {
|
|
11
|
+
return String(value).replace(/[&<>]/g, character => ({
|
|
12
|
+
"&": "&",
|
|
13
|
+
"<": "<",
|
|
14
|
+
">": ">"
|
|
15
|
+
})[character]);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function formatType(type) {
|
|
19
|
+
const names = type && type.names ? type.names : [];
|
|
20
|
+
return names.map(name => `<code>${escapeCode(name)}</code>`).join(" | ") || "";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function formatSignature(doclet) {
|
|
24
|
+
const parameters = (doclet.params || []).map(parameter => parameter.name).filter(Boolean);
|
|
25
|
+
return `(${parameters.join(", ")})`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getAnchor(doclet) {
|
|
29
|
+
if (doclet.kind !== "function" || !doclet.longname.includes("#")) {
|
|
30
|
+
return doclet.longname || doclet.name;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const parts = doclet.longname.split("#");
|
|
34
|
+
if (parts.length === 2) {
|
|
35
|
+
return `${parts[0]}+${parts[1]}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return `${parts.slice(0, -1).join(".")}+${parts.at(-1)}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getDisplayName(doclet) {
|
|
42
|
+
if (doclet.kind !== "function") {
|
|
43
|
+
return doclet.longname || doclet.name;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const signature = formatSignature(doclet);
|
|
47
|
+
const owner = doclet.memberof;
|
|
48
|
+
const name = doclet.name || doclet.longname;
|
|
49
|
+
|
|
50
|
+
if (!owner) {
|
|
51
|
+
return `${name}${signature}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (name.includes("#")) {
|
|
55
|
+
return `${owner}.${name}${signature}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (doclet.scope === "instance") {
|
|
59
|
+
return `${owner[0].toLowerCase()}${owner.slice(1)}.${name}${signature}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return `${doclet.longname}${signature}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function getTocLabel(doclet) {
|
|
66
|
+
if (doclet.kind === "function" && doclet.scope === "instance") {
|
|
67
|
+
return `.${doclet.name.split("#").at(-1)}${formatSignature(doclet)}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (doclet.kind === "member" && doclet.scope === "static") {
|
|
71
|
+
return `.${doclet.name}`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return getDisplayName(doclet);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function renderParameterTable(parameters) {
|
|
78
|
+
if (!parameters || parameters.length === 0) {
|
|
79
|
+
return "";
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const rows = parameters.map(parameter => {
|
|
83
|
+
const name = String(parameter.name || "").replace(/\|/g, "\\|");
|
|
84
|
+
const description = String(parameter.description || "").replace(/\|/g, "\\|");
|
|
85
|
+
return `| ${name} | ${formatType(parameter.type)} | ${description} |`;
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
return [
|
|
89
|
+
"| Param | Type | Description |",
|
|
90
|
+
"| --- | --- | --- |",
|
|
91
|
+
...rows
|
|
92
|
+
].join("\n");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function renderReturnValues(returns) {
|
|
96
|
+
return (returns || []).map(result => {
|
|
97
|
+
const type = formatType(result.type);
|
|
98
|
+
const description = result.description ? ` - ${result.description}` : "";
|
|
99
|
+
return `**Returns**: ${type}${description}`;
|
|
100
|
+
}).join("\n\n");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function renderExamples(examples) {
|
|
104
|
+
return (examples || []).map(example => `\`\`\`js\n${example}\n\`\`\``).join("\n\n");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function renderExceptions(exceptions) {
|
|
108
|
+
return (exceptions || []).map(exception => {
|
|
109
|
+
const type = formatType(exception.type);
|
|
110
|
+
const description = exception.description ? ` - ${exception.description}` : "";
|
|
111
|
+
return `- ${type}${description}`;
|
|
112
|
+
}).join("\n");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function renderKind(doclet, owner) {
|
|
116
|
+
if (!owner) {
|
|
117
|
+
return "";
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const target = `[<code>${owner}</code>](#${owner})`;
|
|
121
|
+
if (doclet.kind === "function" && doclet.scope === "instance") {
|
|
122
|
+
return `**Kind**: instance method of ${target}`;
|
|
123
|
+
}
|
|
124
|
+
if (doclet.kind === "function" && doclet.scope === "static") {
|
|
125
|
+
return `**Kind**: static method of ${target}`;
|
|
126
|
+
}
|
|
127
|
+
if (doclet.kind === "member" && doclet.scope === "static") {
|
|
128
|
+
return `**Kind**: static property of ${target}`;
|
|
129
|
+
}
|
|
130
|
+
return `**Kind**: ${doclet.kind} of ${target}`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function renderEntry(doclet, owner) {
|
|
134
|
+
const anchor = getAnchor(doclet);
|
|
135
|
+
const sections = [`<a id="${anchor}" name="${anchor}"></a>`, `### ${getDisplayName(doclet)}`];
|
|
136
|
+
|
|
137
|
+
if (doclet.description) {
|
|
138
|
+
sections.push(doclet.description);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const kind = renderKind(doclet, owner);
|
|
142
|
+
if (kind) {
|
|
143
|
+
sections.push(kind);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (doclet.type) {
|
|
147
|
+
sections.push(`**Type**: ${formatType(doclet.type)}`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (doclet.defaultvalue !== undefined) {
|
|
151
|
+
const value = typeof doclet.defaultvalue === "string"
|
|
152
|
+
? doclet.defaultvalue
|
|
153
|
+
: JSON.stringify(doclet.defaultvalue);
|
|
154
|
+
sections.push(`**Default**: <code>${escapeCode(value)}</code>`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (doclet.readonly) {
|
|
158
|
+
sections.push("**Read-only**: Yes");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const returns = renderReturnValues(doclet.returns);
|
|
162
|
+
if (returns) {
|
|
163
|
+
sections.push(returns);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const parameters = renderParameterTable(doclet.params);
|
|
167
|
+
if (parameters) {
|
|
168
|
+
sections.push(parameters);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (doclet.deprecated) {
|
|
172
|
+
sections.push(`**Deprecated**: ${typeof doclet.deprecated === "string" ? doclet.deprecated : "Yes"}`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (doclet.since) {
|
|
176
|
+
sections.push(`**Since**: ${doclet.since}`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const exceptions = renderExceptions(doclet.exceptions);
|
|
180
|
+
if (exceptions) {
|
|
181
|
+
sections.push(`**Throws**:\n\n${exceptions}`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const examples = renderExamples(doclet.examples);
|
|
185
|
+
if (examples) {
|
|
186
|
+
sections.push(`**Example**:\n\n${examples}`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (doclet.see && doclet.see.length) {
|
|
190
|
+
sections.push(`**See**: ${doclet.see.join(", ")}`);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return sections.join("\n\n");
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function renderNamespaceIndex(namespace, symbols) {
|
|
197
|
+
const instanceSymbols = symbols.filter(symbol => symbol.kind === "function" && symbol.scope === "instance");
|
|
198
|
+
const staticSymbols = symbols.filter(symbol => !instanceSymbols.includes(symbol));
|
|
199
|
+
const lines = [`- [${namespace.name}](#${namespace.name}) : <code>object</code>`];
|
|
200
|
+
|
|
201
|
+
if (instanceSymbols.length) {
|
|
202
|
+
lines.push(" - _instance_");
|
|
203
|
+
for (const symbol of instanceSymbols) {
|
|
204
|
+
lines.push(` - [${getTocLabel(symbol)}](#${getAnchor(symbol)})`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (staticSymbols.length) {
|
|
209
|
+
lines.push(" - _static_");
|
|
210
|
+
for (const symbol of staticSymbols) {
|
|
211
|
+
lines.push(` - [${getTocLabel(symbol)}](#${getAnchor(symbol)})`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return lines.join("\n");
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function renderNamespace(namespace, symbols) {
|
|
219
|
+
const sections = [
|
|
220
|
+
`<a id="${namespace.name}" name="${namespace.name}"></a>`,
|
|
221
|
+
`## ${namespace.name} : <code>object</code>`,
|
|
222
|
+
"**Kind**: global namespace"
|
|
223
|
+
];
|
|
224
|
+
|
|
225
|
+
if (namespace.description) {
|
|
226
|
+
sections.push(namespace.description);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const parameters = renderParameterTable(namespace.params);
|
|
230
|
+
if (parameters) {
|
|
231
|
+
sections.push(parameters);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
for (const symbol of symbols) {
|
|
235
|
+
sections.push(renderEntry(symbol, namespace.name));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return sections.join("\n\n");
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function getDoclets() {
|
|
242
|
+
const jsdocCliPath = path.join(rootDirectory, "node_modules", "jsdoc", "jsdoc.js");
|
|
243
|
+
const result = spawnSync(process.execPath, [jsdocCliPath, "--configure", "jsdoc.json", "--explain"], {
|
|
244
|
+
cwd: rootDirectory,
|
|
245
|
+
encoding: "utf8",
|
|
246
|
+
maxBuffer: 32 * 1024 * 1024
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
if (result.error) {
|
|
250
|
+
throw result.error;
|
|
251
|
+
}
|
|
252
|
+
if (result.status !== 0) {
|
|
253
|
+
throw new Error(result.stderr || `JSDoc exited with status ${result.status}`);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return JSON.parse(result.stdout);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function renderMarkdown(doclets) {
|
|
260
|
+
const namespaces = [];
|
|
261
|
+
const namespaceNames = new Set();
|
|
262
|
+
|
|
263
|
+
for (const doclet of doclets) {
|
|
264
|
+
if (doclet.kind === "namespace" && doclet.name && !namespaceNames.has(doclet.name)) {
|
|
265
|
+
namespaces.push(doclet);
|
|
266
|
+
namespaceNames.add(doclet.name);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const symbols = doclets.filter(doclet => {
|
|
271
|
+
const documented = doclet.comment && doclet.comment.trim() && !doclet.undocumented;
|
|
272
|
+
const supportedKind = namespaceKinds.has(doclet.kind);
|
|
273
|
+
const supportedOwner = namespaceNames.has(doclet.memberof) || (!doclet.memberof && doclet.kind === "function");
|
|
274
|
+
return documented && supportedKind && supportedOwner;
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
const symbolsByNamespace = new Map(namespaces.map(namespace => [namespace.name, []]));
|
|
278
|
+
const globalFunctions = [];
|
|
279
|
+
const uniqueSymbols = new Map();
|
|
280
|
+
|
|
281
|
+
for (const symbol of symbols) {
|
|
282
|
+
const key = symbol.longname || `${symbol.kind}:${symbol.name}`;
|
|
283
|
+
if (uniqueSymbols.has(key)) {
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
uniqueSymbols.set(key, symbol);
|
|
287
|
+
|
|
288
|
+
if (symbol.memberof) {
|
|
289
|
+
symbolsByNamespace.get(symbol.memberof).push(symbol);
|
|
290
|
+
} else {
|
|
291
|
+
globalFunctions.push(symbol);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const output = ["## Objects", namespaces.map(namespace =>
|
|
296
|
+
renderNamespaceIndex(namespace, symbolsByNamespace.get(namespace.name))
|
|
297
|
+
).join("\n")];
|
|
298
|
+
|
|
299
|
+
if (globalFunctions.length) {
|
|
300
|
+
output.push("## Functions", globalFunctions.map(symbol =>
|
|
301
|
+
`- [${getTocLabel(symbol)}](#${getAnchor(symbol)})`
|
|
302
|
+
).join("\n"));
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
for (const namespace of namespaces) {
|
|
306
|
+
output.push(renderNamespace(namespace, symbolsByNamespace.get(namespace.name)));
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
for (const symbol of globalFunctions) {
|
|
310
|
+
output.push(renderEntry(symbol));
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const markdown = `${output.join("\n\n")}\n`;
|
|
314
|
+
const anchors = [...markdown.matchAll(/<a id="([^"]+)" name="\1"><\/a>/g)].map(match => match[1]);
|
|
315
|
+
const anchorSet = new Set(anchors);
|
|
316
|
+
|
|
317
|
+
for (const symbol of uniqueSymbols.values()) {
|
|
318
|
+
if (!anchorSet.has(getAnchor(symbol))) {
|
|
319
|
+
throw new Error(`Missing Markdown anchor for ${symbol.longname}`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const linkTargets = [...markdown.matchAll(/\]\(#([^)]+)\)/g)].map(match => match[1]);
|
|
324
|
+
const missingTargets = linkTargets.filter(target => !anchorSet.has(target));
|
|
325
|
+
if (missingTargets.length) {
|
|
326
|
+
throw new Error(`Markdown link target has no ID anchor: ${missingTargets[0]}`);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (namespaceNames.size !== namespaces.length) {
|
|
330
|
+
throw new Error("Namespace deduplication failed");
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return { markdown, namespaceCount: namespaces.length, symbolCount: uniqueSymbols.size };
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
try {
|
|
337
|
+
const result = renderMarkdown(getDoclets());
|
|
338
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
339
|
+
fs.writeFileSync(outputPath, result.markdown, "utf8");
|
|
340
|
+
console.log(`Generated Markdown for ${result.namespaceCount} namespaces and ${result.symbolCount} API symbols.`);
|
|
341
|
+
} catch (error) {
|
|
342
|
+
console.error(error.message);
|
|
343
|
+
process.exitCode = 1;
|
|
344
|
+
}
|
package/vitest.config.mjs
CHANGED
|
@@ -1,16 +1,45 @@
|
|
|
1
1
|
import { defineConfig } from 'vitest/config';
|
|
2
|
+
import { playwright } from '@vitest/browser-playwright';
|
|
2
3
|
|
|
3
4
|
export default defineConfig({
|
|
4
5
|
test: {
|
|
5
|
-
include: ['test/**/*Tests.js'],
|
|
6
|
-
environment: 'jsdom',
|
|
7
6
|
globals: true,
|
|
7
|
+
api: {
|
|
8
|
+
host: '127.0.0.1',
|
|
9
|
+
port: 41315,
|
|
10
|
+
strictPort: false,
|
|
11
|
+
},
|
|
8
12
|
silent: 'passed-only',
|
|
9
13
|
restoreMocks: true,
|
|
10
14
|
setupFiles: ['./test/vitest.setup.js'],
|
|
11
15
|
coverage: {
|
|
16
|
+
provider: 'istanbul',
|
|
12
17
|
reporter: ['html', 'lcovonly', 'text-summary'],
|
|
13
18
|
reportsDirectory: 'coverage',
|
|
19
|
+
include: ['src/**/*.js'],
|
|
14
20
|
},
|
|
21
|
+
projects: [
|
|
22
|
+
{
|
|
23
|
+
extends: true,
|
|
24
|
+
test: {
|
|
25
|
+
name: 'unit',
|
|
26
|
+
environment: 'jsdom',
|
|
27
|
+
include: ['test/**/*Tests.js'],
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
extends: true,
|
|
32
|
+
test: {
|
|
33
|
+
name: 'browser',
|
|
34
|
+
include: ['test/**/*Tests.js'],
|
|
35
|
+
browser: {
|
|
36
|
+
enabled: true,
|
|
37
|
+
provider: playwright(),
|
|
38
|
+
headless: true,
|
|
39
|
+
instances: [{ browser: 'chromium' }, { browser: 'firefox' }],
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
],
|
|
15
44
|
},
|
|
16
45
|
});
|