@aryacode/dsh-engineering 0.0.0-stage → 0.3.0
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/CHANGELOG.md +115 -0
- package/LICENSE +21 -0
- package/README.md +228 -2
- package/cordis.patch.yml +122 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +77 -3
- package/vendor/ftview/README.md +55 -0
- package/vendor/ftview/SKILL.md +74 -0
- package/vendor/ftview/lib/checks.d.ts +27 -0
- package/vendor/ftview/lib/checks.js +39 -0
- package/vendor/ftview/lib/core.d.ts +208 -0
- package/vendor/ftview/lib/core.js +160 -0
- package/vendor/ftview/lib/edit/drafts.d.ts +49 -0
- package/vendor/ftview/lib/edit/drafts.js +84 -0
- package/vendor/ftview/lib/edit/operations.d.ts +62 -0
- package/vendor/ftview/lib/edit/operations.js +126 -0
- package/vendor/ftview/lib/edit/prepare.d.ts +38 -0
- package/vendor/ftview/lib/edit/prepare.js +133 -0
- package/vendor/ftview/lib/edit/tools.d.ts +25 -0
- package/vendor/ftview/lib/edit/tools.js +198 -0
- package/vendor/ftview/lib/edit.d.ts +30 -0
- package/vendor/ftview/lib/edit.js +38 -0
- package/vendor/ftview/lib/index.d.ts +31 -0
- package/vendor/ftview/lib/index.js +36 -0
- package/vendor/ftview/lib/model.d.ts +80 -0
- package/vendor/ftview/lib/model.js +267 -0
- package/vendor/ftview/lib/session.d.ts +48 -0
- package/vendor/ftview/lib/session.js +98 -0
- package/vendor/ftview/lib/skill.d.ts +29 -0
- package/vendor/ftview/lib/skill.js +43 -0
- package/vendor/ftview/lib/studio/discover.d.ts +36 -0
- package/vendor/ftview/lib/studio/discover.js +87 -0
- package/vendor/ftview/lib/studio/preflight.d.ts +51 -0
- package/vendor/ftview/lib/studio/preflight.js +64 -0
- package/vendor/ftview/lib/studio/tools.d.ts +15 -0
- package/vendor/ftview/lib/studio/tools.js +83 -0
- package/vendor/ftview/lib/studio.d.ts +30 -0
- package/vendor/ftview/lib/studio.js +37 -0
- package/vendor/ftview/lib/tools.d.ts +14 -0
- package/vendor/ftview/lib/tools.js +202 -0
- package/vendor/l5x/README.md +62 -0
- package/vendor/l5x/SKILL.md +81 -0
- package/vendor/l5x/lib/approvals.d.ts +50 -0
- package/vendor/l5x/lib/approvals.js +93 -0
- package/vendor/l5x/lib/client.js +412 -0
- package/vendor/l5x/lib/client.js.map +1 -0
- package/vendor/l5x/lib/core/errors.d.ts +38 -0
- package/vendor/l5x/lib/core/errors.js +48 -0
- package/vendor/l5x/lib/core/params.d.ts +49 -0
- package/vendor/l5x/lib/core/params.js +39 -0
- package/vendor/l5x/lib/core/result.d.ts +46 -0
- package/vendor/l5x/lib/core/result.js +21 -0
- package/vendor/l5x/lib/core/tool.d.ts +102 -0
- package/vendor/l5x/lib/core/tool.js +19 -0
- package/vendor/l5x/lib/drafts.d.ts +66 -0
- package/vendor/l5x/lib/drafts.js +98 -0
- package/vendor/l5x/lib/editing.d.ts +30 -0
- package/vendor/l5x/lib/editing.js +42 -0
- package/vendor/l5x/lib/engine/acd-edit.d.ts +13 -0
- package/vendor/l5x/lib/engine/acd-edit.js +115 -0
- package/vendor/l5x/lib/engine/analysis.d.ts +22 -0
- package/vendor/l5x/lib/engine/analysis.js +258 -0
- package/vendor/l5x/lib/engine/capabilities.d.ts +9 -0
- package/vendor/l5x/lib/engine/capabilities.js +40 -0
- package/vendor/l5x/lib/engine/change-set.d.ts +122 -0
- package/vendor/l5x/lib/engine/change-set.js +446 -0
- package/vendor/l5x/lib/engine/configuration.d.ts +21 -0
- package/vendor/l5x/lib/engine/configuration.js +42 -0
- package/vendor/l5x/lib/engine/details.d.ts +47 -0
- package/vendor/l5x/lib/engine/details.js +173 -0
- package/vendor/l5x/lib/engine/direct-polling.d.ts +91 -0
- package/vendor/l5x/lib/engine/direct-polling.js +193 -0
- package/vendor/l5x/lib/engine/download-review.d.ts +15 -0
- package/vendor/l5x/lib/engine/download-review.js +63 -0
- package/vendor/l5x/lib/engine/logic-syntax.d.ts +60 -0
- package/vendor/l5x/lib/engine/logic-syntax.js +237 -0
- package/vendor/l5x/lib/engine/observations.d.ts +41 -0
- package/vendor/l5x/lib/engine/observations.js +67 -0
- package/vendor/l5x/lib/engine/project-analysis.d.ts +131 -0
- package/vendor/l5x/lib/engine/project-analysis.js +314 -0
- package/vendor/l5x/lib/engine/review-evidence.d.ts +8 -0
- package/vendor/l5x/lib/engine/review-evidence.js +49 -0
- package/vendor/l5x/lib/engine/review.d.ts +18 -0
- package/vendor/l5x/lib/engine/review.js +123 -0
- package/vendor/l5x/lib/engine/studio-sdk.d.ts +101 -0
- package/vendor/l5x/lib/engine/studio-sdk.js +571 -0
- package/vendor/l5x/lib/engine/tag-resolution.d.ts +20 -0
- package/vendor/l5x/lib/engine/tag-resolution.js +169 -0
- package/vendor/l5x/lib/engine/usage.d.ts +16 -0
- package/vendor/l5x/lib/engine/usage.js +92 -0
- package/vendor/l5x/lib/engine/xml-regions.d.ts +23 -0
- package/vendor/l5x/lib/engine/xml-regions.js +77 -0
- package/vendor/l5x/lib/fs.d.ts +17 -0
- package/vendor/l5x/lib/fs.js +36 -0
- package/vendor/l5x/lib/index.d.ts +36 -0
- package/vendor/l5x/lib/index.js +42 -0
- package/vendor/l5x/lib/live.d.ts +48 -0
- package/vendor/l5x/lib/live.js +48 -0
- package/vendor/l5x/lib/model/aois.d.ts +39 -0
- package/vendor/l5x/lib/model/aois.js +112 -0
- package/vendor/l5x/lib/model/errors.d.ts +15 -0
- package/vendor/l5x/lib/model/errors.js +18 -0
- package/vendor/l5x/lib/model/index.d.ts +20 -0
- package/vendor/l5x/lib/model/index.js +19 -0
- package/vendor/l5x/lib/model/module.d.ts +37 -0
- package/vendor/l5x/lib/model/module.js +54 -0
- package/vendor/l5x/lib/model/modules.d.ts +24 -0
- package/vendor/l5x/lib/model/modules.js +55 -0
- package/vendor/l5x/lib/model/parse.d.ts +32 -0
- package/vendor/l5x/lib/model/parse.js +116 -0
- package/vendor/l5x/lib/model/project.d.ts +15 -0
- package/vendor/l5x/lib/model/project.js +31 -0
- package/vendor/l5x/lib/model/routines.d.ts +71 -0
- package/vendor/l5x/lib/model/routines.js +183 -0
- package/vendor/l5x/lib/model/tags.d.ts +51 -0
- package/vendor/l5x/lib/model/tags.js +119 -0
- package/vendor/l5x/lib/model/types.d.ts +304 -0
- package/vendor/l5x/lib/model/types.js +12 -0
- package/vendor/l5x/lib/model/udts.d.ts +34 -0
- package/vendor/l5x/lib/model/udts.js +81 -0
- package/vendor/l5x/lib/model/xml.d.ts +139 -0
- package/vendor/l5x/lib/model/xml.js +291 -0
- package/vendor/l5x/lib/model/xref.d.ts +28 -0
- package/vendor/l5x/lib/model/xref.js +154 -0
- package/vendor/l5x/lib/session.d.ts +119 -0
- package/vendor/l5x/lib/session.js +236 -0
- package/vendor/l5x/lib/skill.d.ts +30 -0
- package/vendor/l5x/lib/skill.js +44 -0
- package/vendor/l5x/lib/studio-state.d.ts +77 -0
- package/vendor/l5x/lib/studio-state.js +79 -0
- package/vendor/l5x/lib/studio.d.ts +29 -0
- package/vendor/l5x/lib/studio.js +40 -0
- package/vendor/l5x/lib/tools/checks.d.ts +19 -0
- package/vendor/l5x/lib/tools/checks.js +140 -0
- package/vendor/l5x/lib/tools/common.d.ts +41 -0
- package/vendor/l5x/lib/tools/common.js +84 -0
- package/vendor/l5x/lib/tools/controller.d.ts +21 -0
- package/vendor/l5x/lib/tools/controller.js +170 -0
- package/vendor/l5x/lib/tools/drafts.d.ts +23 -0
- package/vendor/l5x/lib/tools/drafts.js +270 -0
- package/vendor/l5x/lib/tools/live.d.ts +21 -0
- package/vendor/l5x/lib/tools/live.js +175 -0
- package/vendor/l5x/lib/tools/read.d.ts +19 -0
- package/vendor/l5x/lib/tools/read.js +245 -0
- package/vendor/l5x/lib/tools/references.d.ts +20 -0
- package/vendor/l5x/lib/tools/references.js +269 -0
- package/vendor/l5x/lib/tools/studio.d.ts +21 -0
- package/vendor/l5x/lib/tools/studio.js +193 -0
- package/vendor/l5x/poll-worker.mjs +77 -0
- package/vendor/l5x/studio-bridge/NuGet.Config +1 -0
- package/vendor/l5x/studio-bridge/Program.cs +304 -0
- package/vendor/l5x/studio-bridge/StudioBridge.csproj +11 -0
- package/vendor/mwi/README.md +60 -0
- package/vendor/mwi/SKILL.md +87 -0
- package/vendor/mwi/bridge/README.md +8 -0
- package/vendor/mwi/bridge/mw_bridge.ps1 +2763 -0
- package/vendor/mwi/bridge/sdk-interop-importer.cs +30 -0
- package/vendor/mwi/bridge/sdk-variable-reader.cs +22 -0
- package/vendor/mwi/bridge/start_bridge.cmd +21 -0
- package/vendor/mwi/bridge/watch_dialogs.cmd +13 -0
- package/vendor/mwi/bridge/watch_dialogs.ps1 +153 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__init__.py +14 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/__init__.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/block_interfaces.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/cfb.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/datatypes.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/declarations.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/errors.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/graphical_listing.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/grid.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/knowledge.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/mwt_bind.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/navigation.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/program_checks.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/project.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/staging.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/structure.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/task_bindings.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/tasks.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/tree.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/validation.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/variable_edit.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/variables.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/workflow.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/block_interfaces.py +275 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/build.py +339 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/capture.py +448 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/cfb.py +765 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/com.py +268 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/datatypes.py +204 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/declarations.py +399 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/descriptions.py +30 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/eip.py +292 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/errors.py +36 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/graphical_listing.py +208 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/grid.py +227 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/ide.py +317 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/iec.py +317 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/installed_help.py +151 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/knowledge.py +218 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/libraries.py +250 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/manuals.py +520 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/mwt_bind.py +133 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/navigation.py +44 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/pou_writer.py +1547 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/program_checks.py +360 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/project.py +704 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/reference_data/catalog.json +672 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/reference_data/patterns.json +86 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/restore.py +208 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/server.py +1913 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/snapshot.py +138 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/st_comments.py +25 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/staging.py +179 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/stlint.py +676 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/structure.py +98 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/task_bindings.py +68 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/tasks.py +162 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/transaction.py +157 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/tree.py +563 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/tree_writer.py +790 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/uia.py +465 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/validation.py +41 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/variable_edit.py +183 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/variables.py +472 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/workflow.py +130 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/writer.py +666 -0
- package/vendor/mwi/code/mw_code.py +1258 -0
- package/vendor/mwi/legacy/edit-session.js +50 -0
- package/vendor/mwi/legacy/fb-insertion.js +74 -0
- package/vendor/mwi/legacy/native-code.js +83 -0
- package/vendor/mwi/legacy/native-groups.js +75 -0
- package/vendor/mwi/legacy/native-structure.js +143 -0
- package/vendor/mwi/legacy/native-variables.js +77 -0
- package/vendor/mwi/legacy/pou-conversion.js +47 -0
- package/vendor/mwi/legacy/pou-package.js +66 -0
- package/vendor/mwi/lib/core/bridge.d.ts +71 -0
- package/vendor/mwi/lib/core/bridge.js +53 -0
- package/vendor/mwi/lib/core/code-engine.d.ts +47 -0
- package/vendor/mwi/lib/core/code-engine.js +109 -0
- package/vendor/mwi/lib/core/errors.d.ts +37 -0
- package/vendor/mwi/lib/core/errors.js +47 -0
- package/vendor/mwi/lib/core/params.d.ts +52 -0
- package/vendor/mwi/lib/core/params.js +46 -0
- package/vendor/mwi/lib/core/ps-bridge.d.ts +62 -0
- package/vendor/mwi/lib/core/ps-bridge.js +127 -0
- package/vendor/mwi/lib/core/result.d.ts +45 -0
- package/vendor/mwi/lib/core/result.js +20 -0
- package/vendor/mwi/lib/core/tool.d.ts +80 -0
- package/vendor/mwi/lib/core/tool.js +55 -0
- package/vendor/mwi/lib/edit/compare.d.ts +33 -0
- package/vendor/mwi/lib/edit/compare.js +70 -0
- package/vendor/mwi/lib/edit.d.ts +20 -0
- package/vendor/mwi/lib/edit.js +32 -0
- package/vendor/mwi/lib/index.d.ts +21 -0
- package/vendor/mwi/lib/index.js +33 -0
- package/vendor/mwi/lib/read/service.d.ts +117 -0
- package/vendor/mwi/lib/read/service.js +229 -0
- package/vendor/mwi/lib/read.d.ts +21 -0
- package/vendor/mwi/lib/read.js +33 -0
- package/vendor/mwi/lib/session/project.d.ts +34 -0
- package/vendor/mwi/lib/session/project.js +54 -0
- package/vendor/mwi/lib/session/service.d.ts +167 -0
- package/vendor/mwi/lib/session/service.js +566 -0
- package/vendor/mwi/lib/session/stage.d.ts +84 -0
- package/vendor/mwi/lib/session/stage.js +187 -0
- package/vendor/mwi/lib/session/workspace.d.ts +52 -0
- package/vendor/mwi/lib/session/workspace.js +90 -0
- package/vendor/mwi/lib/skill.d.ts +36 -0
- package/vendor/mwi/lib/skill.js +56 -0
- package/vendor/mwi/lib/tools/edit.d.ts +20 -0
- package/vendor/mwi/lib/tools/edit.js +286 -0
- package/vendor/mwi/lib/tools/read.d.ts +19 -0
- package/vendor/mwi/lib/tools/read.js +161 -0
- package/vendor/mwi/lib/tools/session.d.ts +20 -0
- package/vendor/mwi/lib/tools/session.js +166 -0
- package/vendor/mwi/lib/tools/verify.d.ts +19 -0
- package/vendor/mwi/lib/tools/verify.js +85 -0
- package/vendor/mwi/lib/verify.d.ts +17 -0
- package/vendor/mwi/lib/verify.js +29 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded discovery of FactoryTalk View Studio ME tooling.
|
|
3
|
+
*
|
|
4
|
+
* Reads directories through the filesystem provider, never executes anything.
|
|
5
|
+
* A missing install is reported as such; absence of a utility is not an error,
|
|
6
|
+
* because the manual Studio workflow does not need it.
|
|
7
|
+
*
|
|
8
|
+
* @module dsh-engineering-ftview/studio/discover
|
|
9
|
+
*/
|
|
10
|
+
import { createHash } from 'node:crypto';
|
|
11
|
+
/** Directory depth the discovery walks below each root. */
|
|
12
|
+
const MAX_DEPTH = 4;
|
|
13
|
+
/** Directory entries the discovery visits per root before it stops. */
|
|
14
|
+
const MAX_ENTRIES_PER_ROOT = 4000;
|
|
15
|
+
/** Known install roots for FactoryTalk View on Windows. */
|
|
16
|
+
export const DEFAULT_SEARCH_ROOTS = [
|
|
17
|
+
'C:/Program Files (x86)/Rockwell Software',
|
|
18
|
+
'C:/Program Files/Rockwell Software',
|
|
19
|
+
];
|
|
20
|
+
/** Walk one root and collect products, schemas, utilities and help files. */
|
|
21
|
+
async function walkRoot(fs, root, signal, found) {
|
|
22
|
+
const queue = [{ path: root, depth: 0 }];
|
|
23
|
+
let visited = 0;
|
|
24
|
+
while (queue.length > 0) {
|
|
25
|
+
signal?.throwIfAborted();
|
|
26
|
+
const current = queue.shift();
|
|
27
|
+
let entries;
|
|
28
|
+
try {
|
|
29
|
+
entries = fs.listDir === undefined ? [] : await fs.listDir(current.path, signal);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
for (const entry of entries) {
|
|
35
|
+
visited += 1;
|
|
36
|
+
if (visited > MAX_ENTRIES_PER_ROOT) {
|
|
37
|
+
found.truncated = true;
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const path = `${current.path}/${entry.name}`;
|
|
41
|
+
if (entry.type === 'directory') {
|
|
42
|
+
if (/FactoryTalk View/i.test(entry.name))
|
|
43
|
+
found.products.push(path);
|
|
44
|
+
if (current.depth < MAX_DEPTH)
|
|
45
|
+
queue.push({ path, depth: current.depth + 1 });
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (/^SEAddComponent\.exe$/iu.test(entry.name)) {
|
|
49
|
+
try {
|
|
50
|
+
const bytes = await fs.readBytes(path, signal, 64 * 1024 * 1024);
|
|
51
|
+
found.utilities.push({ path, bytes: bytes.length, sha256: createHash('sha256').update(bytes).digest('hex') });
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
found.utilities.push({ path, bytes: 0, sha256: '' });
|
|
55
|
+
}
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (/^Gfx-ME.*\.xsd$/iu.test(entry.name))
|
|
59
|
+
found.schemas.push(path);
|
|
60
|
+
if (/\.chm$/iu.test(entry.name))
|
|
61
|
+
found.help.push(path);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Discover View Studio tooling under the configured roots.
|
|
67
|
+
*
|
|
68
|
+
* @param fs - Filesystem provider.
|
|
69
|
+
* @param roots - Absolute roots to search.
|
|
70
|
+
* @param signal - Cancellation.
|
|
71
|
+
* @returns What was found, plus the roots that were searched.
|
|
72
|
+
*/
|
|
73
|
+
export async function discoverStudio(fs, roots, signal) {
|
|
74
|
+
const found = { products: [], schemas: [], utilities: [], help: [], truncated: false };
|
|
75
|
+
const searched = [];
|
|
76
|
+
for (const root of roots) {
|
|
77
|
+
const resolved = await fs.resolve(root, {
|
|
78
|
+
...(signal === undefined ? {} : { signal }),
|
|
79
|
+
});
|
|
80
|
+
const info = await fs.stat(resolved, signal);
|
|
81
|
+
searched.push(resolved);
|
|
82
|
+
if (info === undefined || info.type !== 'directory')
|
|
83
|
+
continue;
|
|
84
|
+
await walkRoot(fs, resolved, signal, found);
|
|
85
|
+
}
|
|
86
|
+
return { searched, ...found };
|
|
87
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preflight and handoff for a manual FactoryTalk View Studio import.
|
|
3
|
+
*
|
|
4
|
+
* This entry never runs a vendor utility. Preflight reports what a Studio
|
|
5
|
+
* import would touch and which prerequisites are missing; handoff returns the
|
|
6
|
+
* ordered manual steps. Automation through `SEAddComponent.exe` is deliberately
|
|
7
|
+
* absent until it can be validated in a licensed environment.
|
|
8
|
+
*
|
|
9
|
+
* @module dsh-engineering-ftview/studio/preflight
|
|
10
|
+
*/
|
|
11
|
+
import type { Application } from '../model.ts';
|
|
12
|
+
import type { StudioDiscovery } from './discover.ts';
|
|
13
|
+
/** The result of checking one change set for Studio. */
|
|
14
|
+
export interface StudioCheck {
|
|
15
|
+
readonly changed: readonly string[];
|
|
16
|
+
readonly blockers: readonly string[];
|
|
17
|
+
readonly warnings: readonly string[];
|
|
18
|
+
readonly ready: boolean;
|
|
19
|
+
readonly utility_available: boolean;
|
|
20
|
+
}
|
|
21
|
+
/** One manual step. */
|
|
22
|
+
export interface HandoffStep {
|
|
23
|
+
readonly step: number;
|
|
24
|
+
readonly action: string;
|
|
25
|
+
readonly files?: readonly string[];
|
|
26
|
+
}
|
|
27
|
+
/** The manual plan. */
|
|
28
|
+
export interface HandoffPlan {
|
|
29
|
+
readonly application: string;
|
|
30
|
+
readonly changed: readonly string[];
|
|
31
|
+
readonly steps: readonly HandoffStep[];
|
|
32
|
+
readonly utility_available: boolean;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Compare two exports and check whether the changed displays can be imported.
|
|
36
|
+
*
|
|
37
|
+
* @param before - The earlier export.
|
|
38
|
+
* @param after - The edited export.
|
|
39
|
+
* @param discovery - Studio tooling that was found.
|
|
40
|
+
* @returns Changed files, blockers, warnings and readiness.
|
|
41
|
+
*/
|
|
42
|
+
export declare function studioPreflight(before: Application, after: Application, discovery: StudioDiscovery): StudioCheck;
|
|
43
|
+
/**
|
|
44
|
+
* Produce the ordered manual import plan.
|
|
45
|
+
*
|
|
46
|
+
* @param before - The earlier export.
|
|
47
|
+
* @param after - The edited export.
|
|
48
|
+
* @param discovery - Studio tooling that was found.
|
|
49
|
+
* @returns The plan.
|
|
50
|
+
*/
|
|
51
|
+
export declare function handoffPlan(before: Application, after: Application, discovery: StudioDiscovery): HandoffPlan;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preflight and handoff for a manual FactoryTalk View Studio import.
|
|
3
|
+
*
|
|
4
|
+
* This entry never runs a vendor utility. Preflight reports what a Studio
|
|
5
|
+
* import would touch and which prerequisites are missing; handoff returns the
|
|
6
|
+
* ordered manual steps. Automation through `SEAddComponent.exe` is deliberately
|
|
7
|
+
* absent until it can be validated in a licensed environment.
|
|
8
|
+
*
|
|
9
|
+
* @module dsh-engineering-ftview/studio/preflight
|
|
10
|
+
*/
|
|
11
|
+
/** File names the Add Component utility refuses (vendor restriction). */
|
|
12
|
+
const UNSAFE_NAME = /[%.":?*<>#{}\[\]\\/;=&+\-\u2019]/u;
|
|
13
|
+
/**
|
|
14
|
+
* Compare two exports and check whether the changed displays can be imported.
|
|
15
|
+
*
|
|
16
|
+
* @param before - The earlier export.
|
|
17
|
+
* @param after - The edited export.
|
|
18
|
+
* @param discovery - Studio tooling that was found.
|
|
19
|
+
* @returns Changed files, blockers, warnings and readiness.
|
|
20
|
+
*/
|
|
21
|
+
export function studioPreflight(before, after, discovery) {
|
|
22
|
+
const beforeHashes = new Map(before.files.map(file => [file.file, file.sha256]));
|
|
23
|
+
const changed = after.files.filter(file => beforeHashes.get(file.file) !== file.sha256).map(file => file.file);
|
|
24
|
+
const blockers = [];
|
|
25
|
+
const warnings = [];
|
|
26
|
+
if (changed.length === 0)
|
|
27
|
+
blockers.push('No file differs between the two exports, so there is nothing to import.');
|
|
28
|
+
for (const display of after.displays) {
|
|
29
|
+
if (!changed.includes(display.file))
|
|
30
|
+
continue;
|
|
31
|
+
const stem = display.file.replace(/\\/gu, '/').split('/').pop()?.replace(/\.xml$/iu, '') ?? display.file;
|
|
32
|
+
if (UNSAFE_NAME.test(stem))
|
|
33
|
+
blockers.push(`Display file ${JSON.stringify(display.file)} has a name Studio's import refuses; rename it in Studio first.`);
|
|
34
|
+
if (display.parseError !== undefined)
|
|
35
|
+
blockers.push(`Display ${JSON.stringify(display.name)} does not parse: ${display.parseError}`);
|
|
36
|
+
}
|
|
37
|
+
if (discovery.utilities.length === 0)
|
|
38
|
+
warnings.push('No SEAddComponent utility was discovered: only the manual Studio import is available.');
|
|
39
|
+
if (discovery.schemas.length === 0)
|
|
40
|
+
warnings.push('No ME XSD was discovered: schema validation is unavailable.');
|
|
41
|
+
return { changed, blockers, warnings, ready: blockers.length === 0, utility_available: discovery.utilities.length > 0 };
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Produce the ordered manual import plan.
|
|
45
|
+
*
|
|
46
|
+
* @param before - The earlier export.
|
|
47
|
+
* @param after - The edited export.
|
|
48
|
+
* @param discovery - Studio tooling that was found.
|
|
49
|
+
* @returns The plan.
|
|
50
|
+
*/
|
|
51
|
+
export function handoffPlan(before, after, discovery) {
|
|
52
|
+
const changed = studioPreflight(before, after, discovery).changed;
|
|
53
|
+
return {
|
|
54
|
+
application: after.name,
|
|
55
|
+
changed,
|
|
56
|
+
utility_available: discovery.utilities.length > 0,
|
|
57
|
+
steps: [
|
|
58
|
+
{ step: 1, action: `Open the ME application for ${after.name} in FactoryTalk View Studio.` },
|
|
59
|
+
{ step: 2, action: 'Import only the changed display components listed here, using the Graphics editor or Add Component.', files: changed },
|
|
60
|
+
{ step: 3, action: 'Save the application and export a fresh full export folder.' },
|
|
61
|
+
{ step: 4, action: 'Reopen both exports with ftview_open and run ftview_compare to confirm only the intended files changed.' },
|
|
62
|
+
],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The FTView Studio entry: discovery, preflight and the manual handoff plan.
|
|
3
|
+
*
|
|
4
|
+
* @module dsh-engineering-ftview/studio/tools
|
|
5
|
+
*/
|
|
6
|
+
import { type ToolDefinition } from '../core.ts';
|
|
7
|
+
import type { ViewSession } from '../session.ts';
|
|
8
|
+
/**
|
|
9
|
+
* The Studio tools.
|
|
10
|
+
*
|
|
11
|
+
* @param session - View handles from the read entry.
|
|
12
|
+
* @param roots - Install roots to search.
|
|
13
|
+
* @returns Tool definitions in registration order.
|
|
14
|
+
*/
|
|
15
|
+
export declare function studioTools(session: ViewSession, roots: readonly string[]): readonly ToolDefinition[];
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The FTView Studio entry: discovery, preflight and the manual handoff plan.
|
|
3
|
+
*
|
|
4
|
+
* @module dsh-engineering-ftview/studio/tools
|
|
5
|
+
*/
|
|
6
|
+
import { object, optionalString, requiredString, string, success, tool } from "../core.js";
|
|
7
|
+
import { discoverStudio } from "./discover.js";
|
|
8
|
+
import { handoffPlan, studioPreflight } from "./preflight.js";
|
|
9
|
+
/** Nothing here proves what Studio will do. */
|
|
10
|
+
const COVERAGE = {
|
|
11
|
+
complete: false,
|
|
12
|
+
unverified: [
|
|
13
|
+
'FactoryTalk View Studio import behavior',
|
|
14
|
+
'vendor XSD validation',
|
|
15
|
+
'runtime behavior on the terminal',
|
|
16
|
+
'automation through SEAddComponent (not executed)',
|
|
17
|
+
],
|
|
18
|
+
};
|
|
19
|
+
/** A compact view of the discovery for tool output. */
|
|
20
|
+
function discoveryData(discovery) {
|
|
21
|
+
return {
|
|
22
|
+
searched: discovery.searched,
|
|
23
|
+
products: discovery.products,
|
|
24
|
+
schemas: discovery.schemas,
|
|
25
|
+
utilities: discovery.utilities,
|
|
26
|
+
help: discovery.help,
|
|
27
|
+
truncated: discovery.truncated,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The Studio tools.
|
|
32
|
+
*
|
|
33
|
+
* @param session - View handles from the read entry.
|
|
34
|
+
* @param roots - Install roots to search.
|
|
35
|
+
* @returns Tool definitions in registration order.
|
|
36
|
+
*/
|
|
37
|
+
export function studioTools(session, roots) {
|
|
38
|
+
return [
|
|
39
|
+
tool({
|
|
40
|
+
name: 'ftview_studio_status',
|
|
41
|
+
description: 'Discover installed FactoryTalk View ME tooling: product folders, ME XSD schemas, help files and the Add Component utility. Read-only; does not start Studio or prove licensing.',
|
|
42
|
+
parameters: object({}),
|
|
43
|
+
async run(_args, exec) {
|
|
44
|
+
const discovery = await discoverStudio(session.fs, roots, exec.signal);
|
|
45
|
+
const products = discovery.products.length;
|
|
46
|
+
const utilities = discovery.utilities.length;
|
|
47
|
+
return success(`Searched ${discovery.searched.length} root(s): ${products} product folder(s), ${utilities} Add Component utility, ${discovery.schemas.length} schema(s).`, discoveryData(discovery), utilities === 0 ? [] : [{ tool: 'ftview_studio_preflight', why: 'Check a change set before importing it.' }], COVERAGE);
|
|
48
|
+
},
|
|
49
|
+
}),
|
|
50
|
+
tool({
|
|
51
|
+
name: 'ftview_studio_preflight',
|
|
52
|
+
description: 'Check the files that differ between two opened exports for a manual Studio import: parse state, name restrictions, discovered utility and schemas. Writes nothing and starts no Studio.',
|
|
53
|
+
parameters: object({
|
|
54
|
+
before_id: string('View id of the export before the edit.'),
|
|
55
|
+
after_id: string('View id of the edited export.'),
|
|
56
|
+
}, ['before_id', 'after_id']),
|
|
57
|
+
async run(args, exec) {
|
|
58
|
+
const before = session.require(requiredString(args, 'before_id'), exec).app;
|
|
59
|
+
const after = session.require(requiredString(args, 'after_id'), exec).app;
|
|
60
|
+
const discovery = await discoverStudio(session.fs, roots, exec.signal);
|
|
61
|
+
const check = studioPreflight(before, after, discovery);
|
|
62
|
+
return success(`${check.changed.length} file(s) differ; ${check.blockers.length} blocker(s).`, { ...check, discovery: discoveryData(discovery) }, check.ready ? [{ tool: 'ftview_studio_handoff', args: { before_id: requiredString(args, 'before_id'), after_id: requiredString(args, 'after_id') }, why: 'Get the ordered manual import steps.' }] : [], COVERAGE);
|
|
63
|
+
},
|
|
64
|
+
}),
|
|
65
|
+
tool({
|
|
66
|
+
name: 'ftview_studio_handoff',
|
|
67
|
+
description: 'Return the ordered manual FactoryTalk View Studio import plan for the changed displays between two opened exports. No vendor utility runs and no file is written.',
|
|
68
|
+
parameters: object({
|
|
69
|
+
before_id: string('View id of the export before the edit.'),
|
|
70
|
+
after_id: string('View id of the edited export.'),
|
|
71
|
+
application: string('ME application name the human opens in Studio. Example: "Line".'),
|
|
72
|
+
}, ['before_id', 'after_id', 'application']),
|
|
73
|
+
async run(args, exec) {
|
|
74
|
+
const before = session.require(requiredString(args, 'before_id'), exec).app;
|
|
75
|
+
const after = session.require(requiredString(args, 'after_id'), exec).app;
|
|
76
|
+
const discovery = await discoverStudio(session.fs, roots, exec.signal);
|
|
77
|
+
const plan = handoffPlan(before, after, discovery);
|
|
78
|
+
const application = optionalString(args, 'application') ?? plan.application;
|
|
79
|
+
return success(`${plan.changed.length} changed file(s) to import into ${application} by hand.`, { ...plan, application }, [], COVERAGE);
|
|
80
|
+
},
|
|
81
|
+
}),
|
|
82
|
+
];
|
|
83
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The FTView Studio entry: discovery, preflight and manual handoff.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately does not execute vendor utilities. The old plugin's
|
|
5
|
+
* `ftview_studio_apply` ran `SEAddComponent.exe` behind a capability probe that
|
|
6
|
+
* only a licensed Studio install can produce; that path is out of scope until
|
|
7
|
+
* it can be validated, so this entry stops at a verifiable handoff.
|
|
8
|
+
*
|
|
9
|
+
* @module dsh-engineering-ftview/studio
|
|
10
|
+
*/
|
|
11
|
+
import type { PluginContext } from './core.ts';
|
|
12
|
+
/** Plugin name in a composition. */
|
|
13
|
+
export declare const name = "ftview-studio";
|
|
14
|
+
/** The read entry's view service must already be mounted. */
|
|
15
|
+
export declare const inject: readonly ["tools", "fs", "ftviewViews"];
|
|
16
|
+
/** Configuration for the Studio entry. */
|
|
17
|
+
export interface Config {
|
|
18
|
+
/** Install roots to search. Defaults to the two Rockwell Programs folders. */
|
|
19
|
+
searchRoots?: string[];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Register the Studio tools.
|
|
23
|
+
*
|
|
24
|
+
* @param ctx - Plugin context carrying the read entry's view service.
|
|
25
|
+
* @param config - Deployment limits.
|
|
26
|
+
*/
|
|
27
|
+
export declare function apply(ctx: PluginContext, config?: Config): void;
|
|
28
|
+
export { discoverStudio, DEFAULT_SEARCH_ROOTS } from './studio/discover.ts';
|
|
29
|
+
export { handoffPlan, studioPreflight } from './studio/preflight.ts';
|
|
30
|
+
export { studioTools } from './studio/tools.ts';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The FTView Studio entry: discovery, preflight and manual handoff.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately does not execute vendor utilities. The old plugin's
|
|
5
|
+
* `ftview_studio_apply` ran `SEAddComponent.exe` behind a capability probe that
|
|
6
|
+
* only a licensed Studio install can produce; that path is out of scope until
|
|
7
|
+
* it can be validated, so this entry stops at a verifiable handoff.
|
|
8
|
+
*
|
|
9
|
+
* @module dsh-engineering-ftview/studio
|
|
10
|
+
*/
|
|
11
|
+
import { DEFAULT_SEARCH_ROOTS } from "./studio/discover.js";
|
|
12
|
+
import { studioTools } from "./studio/tools.js";
|
|
13
|
+
/** Plugin name in a composition. */
|
|
14
|
+
export const name = 'ftview-studio';
|
|
15
|
+
/** The read entry's view service must already be mounted. */
|
|
16
|
+
export const inject = ['tools', 'fs', 'ftviewViews'];
|
|
17
|
+
/**
|
|
18
|
+
* Register the Studio tools.
|
|
19
|
+
*
|
|
20
|
+
* @param ctx - Plugin context carrying the read entry's view service.
|
|
21
|
+
* @param config - Deployment limits.
|
|
22
|
+
*/
|
|
23
|
+
export function apply(ctx, config = {}) {
|
|
24
|
+
const session = ctx.get?.('ftviewViews');
|
|
25
|
+
if (session === undefined) {
|
|
26
|
+
throw new Error('ftview-studio: the ftviewViews service is unavailable; mount the FTView read entry before this one');
|
|
27
|
+
}
|
|
28
|
+
const roots = config.searchRoots ?? [...DEFAULT_SEARCH_ROOTS];
|
|
29
|
+
if (!Array.isArray(roots) || roots.some(root => typeof root !== 'string' || root === '')) {
|
|
30
|
+
throw new Error('ftview-studio: searchRoots must be an array of non-empty absolute paths');
|
|
31
|
+
}
|
|
32
|
+
for (const definition of studioTools(session, roots))
|
|
33
|
+
ctx.tools.register(definition);
|
|
34
|
+
}
|
|
35
|
+
export { discoverStudio, DEFAULT_SEARCH_ROOTS } from "./studio/discover.js";
|
|
36
|
+
export { handoffPlan, studioPreflight } from "./studio/preflight.js";
|
|
37
|
+
export { studioTools } from "./studio/tools.js";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The FTView read surface: six intents over one opened export folder.
|
|
3
|
+
*
|
|
4
|
+
* @module dsh-engineering-ftview/tools
|
|
5
|
+
*/
|
|
6
|
+
import { type ToolDefinition } from './core.ts';
|
|
7
|
+
import type { ViewSession } from './session.ts';
|
|
8
|
+
/**
|
|
9
|
+
* The read tools, bound to one chat's view store.
|
|
10
|
+
*
|
|
11
|
+
* @param session - View handles for the calling chat.
|
|
12
|
+
* @returns Tool definitions in registration order.
|
|
13
|
+
*/
|
|
14
|
+
export declare function ftviewTools(session: ViewSession): readonly ToolDefinition[];
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The FTView read surface: six intents over one opened export folder.
|
|
3
|
+
*
|
|
4
|
+
* @module dsh-engineering-ftview/tools
|
|
5
|
+
*/
|
|
6
|
+
import { EngineeringError, choice, integer, object, optionalInteger, optionalString, requiredString, string, success, tool } from "./core.js";
|
|
7
|
+
import { findingsOf } from "./checks.js";
|
|
8
|
+
/** What this surface cannot prove. */
|
|
9
|
+
const COVERAGE = {
|
|
10
|
+
complete: false,
|
|
11
|
+
unverified: [
|
|
12
|
+
'vendor XSD schema validation',
|
|
13
|
+
'PLC binding and runtime behavior',
|
|
14
|
+
'visual rendering',
|
|
15
|
+
'displays or tags absent from the supplied export',
|
|
16
|
+
],
|
|
17
|
+
};
|
|
18
|
+
/** The view handle parameter, described once. */
|
|
19
|
+
const VIEW_ID = string('View handle returned by ftview_open. Omit when exactly one export is open in this chat.');
|
|
20
|
+
/** Page parameters shared by the list-shaped tools. */
|
|
21
|
+
const PAGE = {
|
|
22
|
+
offset: integer('Number of rows to skip. Default 0.', { minimum: 0, default: 0 }),
|
|
23
|
+
limit: integer('Most rows to return. Default 50, maximum 200.', { minimum: 1, maximum: 200, default: 50 }),
|
|
24
|
+
};
|
|
25
|
+
/** Slice one page. */
|
|
26
|
+
function page(rows, offset, limit) {
|
|
27
|
+
return { rows: rows.slice(offset, offset + limit), offset, limit, total: rows.length, truncated: offset + limit < rows.length };
|
|
28
|
+
}
|
|
29
|
+
/** Display rows shared by list and compare. */
|
|
30
|
+
function displayRows(displays) {
|
|
31
|
+
return displays.map(display => ({ name: display.name, file: display.file, objects: display.objects.length }));
|
|
32
|
+
}
|
|
33
|
+
/** Tag rows shared by list and compare. */
|
|
34
|
+
function tagRows(tags) {
|
|
35
|
+
return tags.map(tag => ({ name: tag.name, type: tag.type, description: tag.description }));
|
|
36
|
+
}
|
|
37
|
+
/** The closest names for a not-found failure. */
|
|
38
|
+
function nearest(names, wanted) {
|
|
39
|
+
const lower = wanted.toLowerCase();
|
|
40
|
+
return names.filter(name => name.toLowerCase().includes(lower)).slice(0, 5);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The read tools, bound to one chat's view store.
|
|
44
|
+
*
|
|
45
|
+
* @param session - View handles for the calling chat.
|
|
46
|
+
* @returns Tool definitions in registration order.
|
|
47
|
+
*/
|
|
48
|
+
export function ftviewTools(session) {
|
|
49
|
+
return [
|
|
50
|
+
tool({
|
|
51
|
+
name: 'ftview_open',
|
|
52
|
+
description: 'Open one FactoryTalk View ME export folder and return its application summary: displays, tag rows, structural diagnostics and coverage. Read-only; does not decode MER, contact a terminal, or change files.',
|
|
53
|
+
parameters: object({
|
|
54
|
+
root: string('Export folder containing the display XML files and the HMI tag CSV. Example: "C:/Projects/Line/ME Export".'),
|
|
55
|
+
}, ['root']),
|
|
56
|
+
async run(args, exec) {
|
|
57
|
+
const view = await session.open(requiredString(args, 'root'), exec);
|
|
58
|
+
const { app } = view;
|
|
59
|
+
return success(`${app.name}: ${app.displays.length} displays, ${app.tags.length} tags, ${app.diagnostics.length + app.tagErrors.length} diagnostic(s).`, {
|
|
60
|
+
view_id: view.id,
|
|
61
|
+
root: app.root,
|
|
62
|
+
name: app.name,
|
|
63
|
+
displays: app.displays.length,
|
|
64
|
+
tags: app.tags.length,
|
|
65
|
+
tag_files: app.tagFiles,
|
|
66
|
+
diagnostics: app.diagnostics,
|
|
67
|
+
tag_errors: app.tagErrors,
|
|
68
|
+
files: app.files.length,
|
|
69
|
+
}, [
|
|
70
|
+
{ tool: 'ftview_list', args: { view_id: view.id, kind: 'display' }, why: 'List the displays you can read.' },
|
|
71
|
+
{ tool: 'ftview_check', args: { view_id: view.id }, why: 'See the structural findings before reporting.' },
|
|
72
|
+
], COVERAGE);
|
|
73
|
+
},
|
|
74
|
+
}),
|
|
75
|
+
tool({
|
|
76
|
+
name: 'ftview_list',
|
|
77
|
+
description: 'List the displays or the HMI tags in one opened export, with a name filter and paging.',
|
|
78
|
+
parameters: object({
|
|
79
|
+
view_id: VIEW_ID,
|
|
80
|
+
kind: choice('What to list: the display files or the HMI tag rows.', ['display', 'tag']),
|
|
81
|
+
filter: string('Optional case-insensitive substring for the display or tag name.'),
|
|
82
|
+
...PAGE,
|
|
83
|
+
}, ['kind']),
|
|
84
|
+
async run(args, exec) {
|
|
85
|
+
const { app } = session.require(optionalString(args, 'view_id'), exec);
|
|
86
|
+
const kind = requiredString(args, 'kind');
|
|
87
|
+
const filter = optionalString(args, 'filter')?.toLowerCase();
|
|
88
|
+
const rows = kind === 'tag'
|
|
89
|
+
? tagRows(filter === undefined ? app.tags : app.tags.filter(tag => tag.name.toLowerCase().includes(filter) || tag.description.toLowerCase().includes(filter)))
|
|
90
|
+
: displayRows(filter === undefined ? app.displays : app.displays.filter(display => display.name.toLowerCase().includes(filter) || display.file.toLowerCase().includes(filter)));
|
|
91
|
+
const sliced = page(rows, optionalInteger(args, 'offset') ?? 0, optionalInteger(args, 'limit') ?? 50);
|
|
92
|
+
return success(`${sliced.total} ${kind === 'tag' ? 'tags' : 'displays'}${sliced.truncated ? `; showing ${sliced.offset + 1}-${sliced.offset + sliced.rows.length}` : ''}.`, { kind, ...sliced }, [{ tool: 'ftview_get', args: { view_id: optionalString(args, 'view_id'), kind, name: 'NAME' }, why: 'Read one row in full.' }], COVERAGE);
|
|
93
|
+
},
|
|
94
|
+
}),
|
|
95
|
+
tool({
|
|
96
|
+
name: 'ftview_get',
|
|
97
|
+
description: 'Read one display or one HMI tag in full: a display exposes its named objects; a tag exposes its CSV row.',
|
|
98
|
+
parameters: object({
|
|
99
|
+
view_id: VIEW_ID,
|
|
100
|
+
kind: choice('What to read: one display file or one HMI tag row.', ['display', 'tag']),
|
|
101
|
+
name: string('Exact display name or tag name. Example: "Main" or "StartPB".'),
|
|
102
|
+
}, ['kind', 'name']),
|
|
103
|
+
async run(args, exec) {
|
|
104
|
+
const { app } = session.require(optionalString(args, 'view_id'), exec);
|
|
105
|
+
const kind = requiredString(args, 'kind');
|
|
106
|
+
const name = requiredString(args, 'name');
|
|
107
|
+
if (kind === 'tag') {
|
|
108
|
+
const tag = app.tags.find(candidate => candidate.name === name);
|
|
109
|
+
if (tag === undefined) {
|
|
110
|
+
const candidates = nearest(app.tags.map(candidate => candidate.name), name);
|
|
111
|
+
throw new EngineeringError('COMPONENT_NOT_FOUND', `No tag is named ${JSON.stringify(name)} in this export.`, candidates.length > 0 ? `Pass one of: ${candidates.join(', ')}.` : 'Call ftview_list with kind "tag" to see the exported tag names.', candidates);
|
|
112
|
+
}
|
|
113
|
+
return success(`Tag ${tag.name}.`, tag, [], COVERAGE);
|
|
114
|
+
}
|
|
115
|
+
const display = app.displays.find(candidate => candidate.name === name);
|
|
116
|
+
if (display === undefined) {
|
|
117
|
+
const candidates = nearest(app.displays.map(candidate => candidate.name), name);
|
|
118
|
+
throw new EngineeringError('COMPONENT_NOT_FOUND', `No display is named ${JSON.stringify(name)} in this export.`, candidates.length > 0 ? `Pass one of: ${candidates.join(', ')}.` : 'Call ftview_list with kind "display" to see the exported display names.', candidates);
|
|
119
|
+
}
|
|
120
|
+
return success(`Display ${display.name}: ${display.objects.length} objects.`, { name: display.name, file: display.file, objects: display.objects.slice(0, 200), object_count: display.objects.length }, [], COVERAGE);
|
|
121
|
+
},
|
|
122
|
+
}),
|
|
123
|
+
tool({
|
|
124
|
+
name: 'ftview_find',
|
|
125
|
+
description: 'Search one opened export by name or text across displays, display objects and HMI tags.',
|
|
126
|
+
parameters: object({
|
|
127
|
+
view_id: VIEW_ID,
|
|
128
|
+
query: string('Text to search for, case-insensitive. Example: "StartPB".'),
|
|
129
|
+
limit: integer('Most matches to return. Default 50, maximum 200.', { minimum: 1, maximum: 200, default: 50 }),
|
|
130
|
+
}, ['query']),
|
|
131
|
+
async run(args, exec) {
|
|
132
|
+
const { app } = session.require(optionalString(args, 'view_id'), exec);
|
|
133
|
+
const query = requiredString(args, 'query').toLowerCase();
|
|
134
|
+
const matches = [];
|
|
135
|
+
for (const display of app.displays) {
|
|
136
|
+
if (display.name.toLowerCase().includes(query) || display.file.toLowerCase().includes(query)) {
|
|
137
|
+
matches.push({ kind: 'display', where: display.file, text: display.name });
|
|
138
|
+
}
|
|
139
|
+
for (const object of display.objects) {
|
|
140
|
+
const named = object.name ?? '';
|
|
141
|
+
const values = Object.values(object.attributes);
|
|
142
|
+
const hit = named.toLowerCase().includes(query) || values.some(value => value.toLowerCase().includes(query));
|
|
143
|
+
if (hit)
|
|
144
|
+
matches.push({ kind: 'object', where: display.file, text: named === '' ? object.type : `${object.type} ${named}` });
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
for (const tag of app.tags) {
|
|
148
|
+
if (tag.name.toLowerCase().includes(query) || tag.description.toLowerCase().includes(query)) {
|
|
149
|
+
matches.push({ kind: 'tag', where: 'tag CSV', text: `${tag.name}${tag.description === '' ? '' : `  ${tag.description}`}` });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const limit = optionalInteger(args, 'limit') ?? 50;
|
|
153
|
+
return success(`${matches.length} match(es) for ${JSON.stringify(requiredString(args, 'query'))}${matches.length > limit ? `; showing ${limit}` : ''}.`, { query: requiredString(args, 'query'), total: matches.length, matches: matches.slice(0, limit) }, [], COVERAGE);
|
|
154
|
+
},
|
|
155
|
+
}),
|
|
156
|
+
tool({
|
|
157
|
+
name: 'ftview_check',
|
|
158
|
+
description: 'Run structural checks over one opened export: tag CSV integrity, duplicate names within displays and the tag file, and files that did not parse. Vendor XSD validation and PLC binding are not performed.',
|
|
159
|
+
parameters: object({ view_id: VIEW_ID }, []),
|
|
160
|
+
async run(args, exec) {
|
|
161
|
+
const { app } = session.require(optionalString(args, 'view_id'), exec);
|
|
162
|
+
const findings = findingsOf(app);
|
|
163
|
+
const byRule = {};
|
|
164
|
+
for (const finding of findings)
|
|
165
|
+
byRule[finding.rule] = (byRule[finding.rule] ?? 0) + 1;
|
|
166
|
+
return success(`${findings.length} finding(s) in ${app.name}.`, { total: findings.length, by_rule: byRule, findings, coverage: { schema: 'not run', plc_binding: 'not run' } }, [], COVERAGE);
|
|
167
|
+
},
|
|
168
|
+
}),
|
|
169
|
+
tool({
|
|
170
|
+
name: 'ftview_compare',
|
|
171
|
+
description: 'Compare two opened exports by display name, tag name and file content hash.',
|
|
172
|
+
parameters: object({
|
|
173
|
+
before_id: string('View id of the earlier export.'),
|
|
174
|
+
after_id: string('View id of the later export.'),
|
|
175
|
+
...PAGE,
|
|
176
|
+
}, ['before_id', 'after_id']),
|
|
177
|
+
async run(args, exec) {
|
|
178
|
+
const before = session.require(requiredString(args, 'before_id'), exec).app;
|
|
179
|
+
const after = session.require(requiredString(args, 'after_id'), exec).app;
|
|
180
|
+
const names = (app) => ({
|
|
181
|
+
displays: new Map(app.displays.map(display => [display.name, display.file])),
|
|
182
|
+
tags: new Map(app.tags.map(tag => [tag.name, tag.type])),
|
|
183
|
+
});
|
|
184
|
+
const oldNames = names(before);
|
|
185
|
+
const newNames = names(after);
|
|
186
|
+
const diff = (left, right) => ({
|
|
187
|
+
added: [...right.keys()].filter(name => !left.has(name)),
|
|
188
|
+
removed: [...left.keys()].filter(name => !right.has(name)),
|
|
189
|
+
});
|
|
190
|
+
const hashOf = new Map(before.files.map(file => [file.file, file.sha256]));
|
|
191
|
+
const changedFiles = after.files.filter(file => hashOf.get(file.file) !== file.sha256).map(file => file.file);
|
|
192
|
+
const limit = optionalInteger(args, 'limit') ?? 50;
|
|
193
|
+
return success(`${diff(oldNames.displays, newNames.displays).added.length + diff(oldNames.displays, newNames.displays).removed.length} display change(s), ${diff(oldNames.tags, newNames.tags).added.length + diff(oldNames.tags, newNames.tags).removed.length} tag change(s), ${changedFiles.length} changed file(s).`, {
|
|
194
|
+
displays: diff(oldNames.displays, newNames.displays),
|
|
195
|
+
tags: diff(oldNames.tags, newNames.tags),
|
|
196
|
+
files_changed: changedFiles.slice(0, limit),
|
|
197
|
+
truncated: changedFiles.length > limit,
|
|
198
|
+
}, [], COVERAGE);
|
|
199
|
+
},
|
|
200
|
+
}),
|
|
201
|
+
];
|
|
202
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# dsh-engineering-l5x
|
|
2
|
+
|
|
3
|
+
Studio 5000 L5X tools for the `dsh-engineering` bundle. Four loader entries share one package:
|
|
4
|
+
|
|
5
|
+
| Entry | Tools | Needs |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| `dsh-engineering-l5x` | 11 offline read and reference tools | Node |
|
|
8
|
+
| `dsh-engineering-l5x/editing` | 9 `draft_*` tools | the read entry |
|
|
9
|
+
| `dsh-engineering-l5x/studio` | 6 Logix Designer SDK tools and 7 supervised controller tools | Studio 5000, Logix Designer SDK, FactoryTalk Linx |
|
|
10
|
+
| `dsh-engineering-l5x/live` | `poll_live_tags`, `start_plc_trend` | a reachable controller |
|
|
11
|
+
|
|
12
|
+
The `rockwell-l5x` guidance ships as the separate `dsh-engineering-l5x/skill` entry, and the PLC
|
|
13
|
+
trend card ships as the client face (`dsh.client`).
|
|
14
|
+
|
|
15
|
+
## Read surface
|
|
16
|
+
|
|
17
|
+
`open_project` opens one export and returns a `project_id`. `project_overview`, `list_components` and
|
|
18
|
+
`get_component` walk the structure; `search_logic`, `find_references`, `trace_dependencies`,
|
|
19
|
+
`resolve_tag` and `usage_map` answer where a symbol is used; `run_checks` and `compare_projects`
|
|
20
|
+
produce review evidence. Every result carries `summary`, `data`, `coverage` and `next`.
|
|
21
|
+
|
|
22
|
+
Reading accepts L5X revision 19 or later and never needs Studio. A changed export is refused with
|
|
23
|
+
`STALE_EXPORT`; reopen it before continuing.
|
|
24
|
+
|
|
25
|
+
## Editing
|
|
26
|
+
|
|
27
|
+
`draft_rung`, `draft_st`, `draft_routine`, `draft_tag`, `draft_tag_description`, `draft_udt_member` and
|
|
28
|
+
`draft_udt_description` append one operation per call; `draft_validate` prepares the draft and
|
|
29
|
+
`draft_apply` writes one new L5X beside the source. Nothing overwrites, imports, connects or changes
|
|
30
|
+
a controller.
|
|
31
|
+
|
|
32
|
+
## Studio and controller
|
|
33
|
+
|
|
34
|
+
`studio_sdk_status`, `studio_capabilities`, `export_acd_to_l5x`, `verify_offline_l5x`,
|
|
35
|
+
`build_acd_from_draft` and `studio_review_evidence` cover offline SDK work. The controller tools
|
|
36
|
+
(`capture_controller_snapshot`, `go_online`, `read_controller_mode`, `read_live_tag`,
|
|
37
|
+
`change_controller_mode`, `download_project`, `controller_approval_status`) are read-only unless the
|
|
38
|
+
operation is an upload, mode change or download; those require per-call human approval and a
|
|
39
|
+
`verification_id` from a passed build.
|
|
40
|
+
|
|
41
|
+
## Live
|
|
42
|
+
|
|
43
|
+
`poll_live_tags` and `start_plc_trend` read declared scalar tags over EtherNet/IP without Studio,
|
|
44
|
+
Linx, an ACD or the SDK. They are read-only and report that live logic matching the export is
|
|
45
|
+
unverified.
|
|
46
|
+
|
|
47
|
+
## Configuration
|
|
48
|
+
|
|
49
|
+
| Entry | Option | Default |
|
|
50
|
+
|---|---|---|
|
|
51
|
+
| read | `maxFileBytes` | 64 MiB |
|
|
52
|
+
| editing | `maxFileBytes`, `maxDraftOperations` | 64 MiB, 200 |
|
|
53
|
+
| studio | `maxFileBytes` | 64 MiB |
|
|
54
|
+
| live | `enabled`, `minIntervalMs`, `defaultIntervalMs`, `maxTags`, `maxSamples` | true, 50 ms, 200 ms, 16, 1200 |
|
|
55
|
+
|
|
56
|
+
## Develop
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
pnpm install
|
|
60
|
+
pnpm run build
|
|
61
|
+
pnpm test
|
|
62
|
+
```
|