@agentrhq/webcmd 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +317 -24
- package/dist/src/adapter-shadow.d.ts +11 -0
- package/dist/src/adapter-shadow.js +72 -0
- package/dist/src/adapter-shadow.test.d.ts +1 -0
- package/dist/src/adapter-shadow.test.js +49 -0
- package/dist/src/adapter-source.d.ts +11 -0
- package/dist/src/adapter-source.js +24 -0
- package/dist/src/adapter-source.test.d.ts +1 -0
- package/dist/src/adapter-source.test.js +29 -0
- package/dist/src/brand.d.ts +9 -0
- package/dist/src/brand.js +9 -0
- package/dist/src/brand.test.d.ts +1 -0
- package/dist/src/brand.test.js +15 -0
- package/dist/src/browser/analyze.d.ts +118 -0
- package/dist/src/browser/analyze.js +405 -0
- package/dist/src/browser/analyze.test.d.ts +1 -0
- package/dist/src/browser/analyze.test.js +209 -0
- package/dist/src/browser/article-extract.d.ts +57 -0
- package/dist/src/browser/article-extract.e2e.test.d.ts +1 -0
- package/dist/src/browser/article-extract.e2e.test.js +105 -0
- package/dist/src/browser/article-extract.js +169 -0
- package/dist/src/browser/article-extract.test.d.ts +1 -0
- package/dist/src/browser/article-extract.test.js +94 -0
- package/dist/src/browser/ax-snapshot.d.ts +37 -0
- package/dist/src/browser/ax-snapshot.js +217 -0
- package/dist/src/browser/ax-snapshot.test.d.ts +1 -0
- package/dist/src/browser/ax-snapshot.test.js +91 -0
- package/dist/src/browser/base-page.d.ts +151 -0
- package/dist/src/browser/base-page.js +1060 -0
- package/dist/src/browser/base-page.test.d.ts +1 -0
- package/dist/src/browser/base-page.test.js +803 -0
- package/dist/src/browser/bridge-readiness.d.ts +14 -0
- package/dist/src/browser/bridge-readiness.js +24 -0
- package/dist/src/browser/bridge-readiness.test.d.ts +1 -0
- package/dist/src/browser/bridge-readiness.test.js +58 -0
- package/dist/src/browser/bridge.d.ts +26 -0
- package/dist/src/browser/bridge.js +58 -0
- package/dist/src/browser/cdp-click-fixture.test.d.ts +1 -0
- package/dist/src/browser/cdp-click-fixture.test.js +87 -0
- package/dist/src/browser/cdp.d.ts +46 -0
- package/dist/src/browser/cdp.js +536 -0
- package/dist/src/browser/cdp.test.d.ts +1 -0
- package/dist/src/browser/cdp.test.js +79 -0
- package/dist/src/browser/compound.d.ts +59 -0
- package/dist/src/browser/compound.js +112 -0
- package/dist/src/browser/compound.test.d.ts +1 -0
- package/dist/src/browser/compound.test.js +175 -0
- package/dist/src/browser/config.d.ts +2 -0
- package/dist/src/browser/config.js +14 -0
- package/dist/src/browser/daemon-client.d.ts +32 -0
- package/dist/src/browser/daemon-client.js +135 -0
- package/dist/src/browser/daemon-client.test.d.ts +1 -0
- package/dist/src/browser/daemon-client.test.js +326 -0
- package/dist/src/browser/daemon-lifecycle.d.ts +37 -0
- package/dist/src/browser/daemon-lifecycle.js +147 -0
- package/dist/src/browser/daemon-transport.d.ts +54 -0
- package/dist/src/browser/daemon-transport.js +68 -0
- package/dist/src/browser/daemon-version.d.ts +4 -0
- package/dist/src/browser/daemon-version.js +12 -0
- package/dist/src/browser/dom-helpers.d.ts +41 -0
- package/dist/src/browser/dom-helpers.js +239 -0
- package/dist/src/browser/dom-helpers.test.d.ts +1 -0
- package/dist/src/browser/dom-helpers.test.js +106 -0
- package/dist/src/browser/dom-snapshot.d.ts +93 -0
- package/dist/src/browser/dom-snapshot.js +894 -0
- package/dist/src/browser/dom-snapshot.test.d.ts +11 -0
- package/dist/src/browser/dom-snapshot.test.js +313 -0
- package/dist/src/browser/errors.d.ts +38 -0
- package/dist/src/browser/errors.js +79 -0
- package/dist/src/browser/errors.test.d.ts +1 -0
- package/dist/src/browser/errors.test.js +60 -0
- package/dist/src/browser/extract.d.ts +69 -0
- package/dist/src/browser/extract.js +132 -0
- package/dist/src/browser/extract.test.d.ts +1 -0
- package/dist/src/browser/extract.test.js +129 -0
- package/dist/src/browser/find.d.ts +84 -0
- package/dist/src/browser/find.js +398 -0
- package/dist/src/browser/find.test.d.ts +1 -0
- package/dist/src/browser/find.test.js +180 -0
- package/dist/src/browser/html-tree.d.ts +75 -0
- package/dist/src/browser/html-tree.js +112 -0
- package/dist/src/browser/html-tree.test.d.ts +1 -0
- package/dist/src/browser/html-tree.test.js +181 -0
- package/dist/src/browser/index.d.ts +14 -0
- package/dist/src/browser/index.js +12 -0
- package/dist/src/browser/network-cache.d.ts +49 -0
- package/dist/src/browser/network-cache.js +78 -0
- package/dist/src/browser/network-cache.test.d.ts +1 -0
- package/dist/src/browser/network-cache.test.js +75 -0
- package/dist/src/browser/network-interceptor.d.ts +11 -0
- package/dist/src/browser/network-interceptor.js +11 -0
- package/dist/src/browser/network-key.d.ts +22 -0
- package/dist/src/browser/network-key.js +66 -0
- package/dist/src/browser/network-key.test.d.ts +1 -0
- package/dist/src/browser/network-key.test.js +49 -0
- package/dist/src/browser/page.d.ts +83 -0
- package/dist/src/browser/page.js +453 -0
- package/dist/src/browser/page.test.d.ts +1 -0
- package/dist/src/browser/page.test.js +406 -0
- package/dist/src/browser/profile.d.ts +14 -0
- package/dist/src/browser/profile.js +86 -0
- package/dist/src/browser/protocol.d.ts +63 -0
- package/dist/src/browser/protocol.js +1 -0
- package/dist/src/browser/runtime/local-cloak/actions.d.ts +3 -0
- package/dist/src/browser/runtime/local-cloak/actions.js +240 -0
- package/dist/src/browser/runtime/local-cloak/downloads.d.ts +3 -0
- package/dist/src/browser/runtime/local-cloak/downloads.js +28 -0
- package/dist/src/browser/runtime/local-cloak/network.d.ts +28 -0
- package/dist/src/browser/runtime/local-cloak/network.js +120 -0
- package/dist/src/browser/runtime/local-cloak/network.test.d.ts +1 -0
- package/dist/src/browser/runtime/local-cloak/network.test.js +124 -0
- package/dist/src/browser/runtime/local-cloak/profiles.d.ts +6 -0
- package/dist/src/browser/runtime/local-cloak/profiles.js +17 -0
- package/dist/src/browser/runtime/local-cloak/profiles.test.d.ts +1 -0
- package/dist/src/browser/runtime/local-cloak/profiles.test.js +19 -0
- package/dist/src/browser/runtime/local-cloak/provider.d.ts +15 -0
- package/dist/src/browser/runtime/local-cloak/provider.js +27 -0
- package/dist/src/browser/runtime/local-cloak/provider.test.d.ts +1 -0
- package/dist/src/browser/runtime/local-cloak/provider.test.js +244 -0
- package/dist/src/browser/runtime/local-cloak/session-manager.d.ts +77 -0
- package/dist/src/browser/runtime/local-cloak/session-manager.js +280 -0
- package/dist/src/browser/runtime/local-cloak/session-manager.test.d.ts +1 -0
- package/dist/src/browser/runtime/local-cloak/session-manager.test.js +92 -0
- package/dist/src/browser/runtime/provider.d.ts +9 -0
- package/dist/src/browser/runtime/provider.js +1 -0
- package/dist/src/browser/runtime/provider.test.d.ts +2 -0
- package/dist/src/browser/runtime/provider.test.js +49 -0
- package/dist/src/browser/shape-filter.d.ts +52 -0
- package/dist/src/browser/shape-filter.js +101 -0
- package/dist/src/browser/shape-filter.test.d.ts +1 -0
- package/dist/src/browser/shape-filter.test.js +101 -0
- package/dist/src/browser/shape.d.ts +23 -0
- package/dist/src/browser/shape.js +95 -0
- package/dist/src/browser/shape.test.d.ts +1 -0
- package/dist/src/browser/shape.test.js +82 -0
- package/dist/src/browser/stealth.d.ts +11 -0
- package/dist/src/browser/stealth.js +359 -0
- package/dist/src/browser/stealth.test.d.ts +1 -0
- package/dist/src/browser/stealth.test.js +134 -0
- package/dist/src/browser/tabs.d.ts +13 -0
- package/dist/src/browser/tabs.js +70 -0
- package/dist/src/browser/target-errors.d.ts +39 -0
- package/dist/src/browser/target-errors.js +45 -0
- package/dist/src/browser/target-errors.test.d.ts +1 -0
- package/dist/src/browser/target-errors.test.js +94 -0
- package/dist/src/browser/target-resolver.d.ts +158 -0
- package/dist/src/browser/target-resolver.js +570 -0
- package/dist/src/browser/target-resolver.test.d.ts +1 -0
- package/dist/src/browser/target-resolver.test.js +118 -0
- package/dist/src/browser/utils.d.ts +20 -0
- package/dist/src/browser/utils.js +64 -0
- package/dist/src/browser/utils.test.d.ts +1 -0
- package/dist/src/browser/utils.test.js +29 -0
- package/dist/src/browser/verify-fixture.d.ts +65 -0
- package/dist/src/browser/verify-fixture.js +318 -0
- package/dist/src/browser/verify-fixture.test.d.ts +1 -0
- package/dist/src/browser/verify-fixture.test.js +219 -0
- package/dist/src/browser/visual-refs.d.ts +11 -0
- package/dist/src/browser/visual-refs.js +108 -0
- package/dist/src/browser.test.d.ts +1 -0
- package/dist/src/browser.test.js +297 -0
- package/dist/src/build-manifest.d.ts +103 -0
- package/dist/src/build-manifest.js +363 -0
- package/dist/src/build-manifest.test.d.ts +1 -0
- package/dist/src/build-manifest.test.js +341 -0
- package/dist/src/capabilityRouting.d.ts +19 -0
- package/dist/src/capabilityRouting.js +56 -0
- package/dist/src/capabilityRouting.test.d.ts +1 -0
- package/dist/src/capabilityRouting.test.js +63 -0
- package/dist/src/cli-argv-preprocess.d.ts +63 -0
- package/dist/src/cli-argv-preprocess.js +303 -0
- package/dist/src/cli-argv-preprocess.test.d.ts +1 -0
- package/dist/src/cli-argv-preprocess.test.js +304 -0
- package/dist/src/cli.d.ts +79 -0
- package/dist/src/cli.js +3350 -0
- package/dist/src/cli.test.d.ts +1 -0
- package/dist/src/cli.test.js +3041 -0
- package/dist/src/commanderAdapter.d.ts +21 -0
- package/dist/src/commanderAdapter.js +208 -0
- package/dist/src/commanderAdapter.test.d.ts +1 -0
- package/dist/src/commanderAdapter.test.js +352 -0
- package/dist/src/commands/auth.d.ts +40 -0
- package/dist/src/commands/auth.js +434 -0
- package/dist/src/commands/auth.test.d.ts +1 -0
- package/dist/src/commands/auth.test.js +252 -0
- package/dist/src/commands/daemon.d.ts +9 -0
- package/dist/src/commands/daemon.js +91 -0
- package/dist/src/commands/daemon.test.d.ts +1 -0
- package/dist/src/commands/daemon.test.js +293 -0
- package/dist/src/completion-fast.d.ts +22 -0
- package/dist/src/completion-fast.js +88 -0
- package/dist/src/completion-shared.d.ts +13 -0
- package/dist/src/completion-shared.js +61 -0
- package/dist/src/completion.d.ts +21 -0
- package/dist/src/completion.js +68 -0
- package/dist/src/completion.test.d.ts +1 -0
- package/dist/src/completion.test.js +24 -0
- package/dist/src/constants.d.ts +18 -0
- package/dist/src/constants.js +41 -0
- package/dist/src/convention-audit.d.ts +50 -0
- package/dist/src/convention-audit.js +553 -0
- package/dist/src/convention-audit.test.d.ts +1 -0
- package/dist/src/convention-audit.test.js +255 -0
- package/dist/src/daemon/server.d.ts +13 -0
- package/dist/src/daemon/server.js +185 -0
- package/dist/src/daemon/server.test.d.ts +1 -0
- package/dist/src/daemon/server.test.js +132 -0
- package/dist/src/daemon-utils.d.ts +18 -0
- package/dist/src/daemon-utils.js +37 -0
- package/dist/src/daemon.d.ts +1 -0
- package/dist/src/daemon.js +27 -0
- package/dist/src/daemon.test.d.ts +1 -0
- package/dist/src/daemon.test.js +60 -0
- package/dist/src/discovery.d.ts +46 -0
- package/dist/src/discovery.js +341 -0
- package/dist/src/doctor.d.ts +39 -0
- package/dist/src/doctor.js +168 -0
- package/dist/src/doctor.test.d.ts +1 -0
- package/dist/src/doctor.test.js +302 -0
- package/dist/src/download/article-download.d.ts +72 -0
- package/dist/src/download/article-download.js +305 -0
- package/dist/src/download/article-download.test.d.ts +1 -0
- package/dist/src/download/article-download.test.js +235 -0
- package/dist/src/download/index.d.ts +71 -0
- package/dist/src/download/index.js +363 -0
- package/dist/src/download/index.test.d.ts +1 -0
- package/dist/src/download/index.test.js +174 -0
- package/dist/src/download/media-download.d.ts +49 -0
- package/dist/src/download/media-download.js +127 -0
- package/dist/src/download/media-download.test.d.ts +1 -0
- package/dist/src/download/media-download.test.js +112 -0
- package/dist/src/download/progress.d.ts +36 -0
- package/dist/src/download/progress.js +120 -0
- package/dist/src/download/progress.test.d.ts +1 -0
- package/dist/src/download/progress.test.js +36 -0
- package/dist/src/electron-apps.d.ts +31 -0
- package/dist/src/electron-apps.js +91 -0
- package/dist/src/electron-apps.test.d.ts +1 -0
- package/dist/src/electron-apps.test.js +76 -0
- package/dist/src/engine.test.d.ts +1 -0
- package/dist/src/engine.test.js +312 -0
- package/dist/src/errors.d.ts +114 -0
- package/dist/src/errors.js +174 -0
- package/dist/src/errors.test.d.ts +1 -0
- package/dist/src/errors.test.js +109 -0
- package/dist/src/execution.d.ts +24 -0
- package/dist/src/execution.js +509 -0
- package/dist/src/execution.test.d.ts +1 -0
- package/dist/src/execution.test.js +645 -0
- package/dist/src/external.d.ts +49 -0
- package/dist/src/external.js +211 -0
- package/dist/src/external.test.d.ts +1 -0
- package/dist/src/external.test.js +110 -0
- package/dist/src/help.d.ts +88 -0
- package/dist/src/help.js +556 -0
- package/dist/src/help.test.d.ts +1 -0
- package/dist/src/help.test.js +58 -0
- package/dist/src/hooks.d.ts +46 -0
- package/dist/src/hooks.js +58 -0
- package/dist/src/hooks.test.d.ts +4 -0
- package/dist/src/hooks.test.js +92 -0
- package/dist/src/interceptor.d.ts +44 -0
- package/dist/src/interceptor.js +183 -0
- package/dist/src/interceptor.test.d.ts +4 -0
- package/dist/src/interceptor.test.js +81 -0
- package/dist/src/launcher.d.ts +41 -0
- package/dist/src/launcher.js +226 -0
- package/dist/src/launcher.test.d.ts +1 -0
- package/dist/src/launcher.test.js +153 -0
- package/dist/src/logger.d.ts +26 -0
- package/dist/src/logger.js +49 -0
- package/dist/src/main.d.ts +5 -0
- package/dist/src/main.js +177 -0
- package/dist/src/manifest-types.d.ts +43 -0
- package/dist/src/manifest-types.js +9 -0
- package/dist/src/node-network.d.ts +10 -0
- package/dist/src/node-network.js +174 -0
- package/dist/src/node-network.test.d.ts +1 -0
- package/dist/src/node-network.test.js +55 -0
- package/dist/src/observation/artifact.d.ts +16 -0
- package/dist/src/observation/artifact.js +260 -0
- package/dist/src/observation/artifact.test.d.ts +1 -0
- package/dist/src/observation/artifact.test.js +121 -0
- package/dist/src/observation/events.d.ts +89 -0
- package/dist/src/observation/events.js +1 -0
- package/dist/src/observation/index.d.ts +7 -0
- package/dist/src/observation/index.js +7 -0
- package/dist/src/observation/manager.d.ts +9 -0
- package/dist/src/observation/manager.js +27 -0
- package/dist/src/observation/manager.test.d.ts +1 -0
- package/dist/src/observation/manager.test.js +13 -0
- package/dist/src/observation/redaction.d.ts +11 -0
- package/dist/src/observation/redaction.js +81 -0
- package/dist/src/observation/redaction.test.d.ts +1 -0
- package/dist/src/observation/redaction.test.js +32 -0
- package/dist/src/observation/retention.d.ts +32 -0
- package/dist/src/observation/retention.js +160 -0
- package/dist/src/observation/retention.test.d.ts +1 -0
- package/dist/src/observation/retention.test.js +118 -0
- package/dist/src/observation/ring-buffer.d.ts +22 -0
- package/dist/src/observation/ring-buffer.js +45 -0
- package/dist/src/observation/ring-buffer.test.d.ts +1 -0
- package/dist/src/observation/ring-buffer.test.js +22 -0
- package/dist/src/observation/session.d.ts +25 -0
- package/dist/src/observation/session.js +50 -0
- package/dist/src/output.d.ts +14 -0
- package/dist/src/output.js +152 -0
- package/dist/src/output.test.d.ts +1 -0
- package/dist/src/output.test.js +53 -0
- package/dist/src/package-exports.test.d.ts +1 -0
- package/dist/src/package-exports.test.js +112 -0
- package/dist/src/package-paths.d.ts +8 -0
- package/dist/src/package-paths.js +41 -0
- package/dist/src/pipeline/executor.d.ts +11 -0
- package/dist/src/pipeline/executor.js +93 -0
- package/dist/src/pipeline/executor.test.d.ts +4 -0
- package/dist/src/pipeline/executor.test.js +182 -0
- package/dist/src/pipeline/index.d.ts +5 -0
- package/dist/src/pipeline/index.js +5 -0
- package/dist/src/pipeline/registry.d.ts +28 -0
- package/dist/src/pipeline/registry.js +55 -0
- package/dist/src/pipeline/steps/browser.d.ts +13 -0
- package/dist/src/pipeline/steps/browser.js +86 -0
- package/dist/src/pipeline/steps/download.d.ts +18 -0
- package/dist/src/pipeline/steps/download.js +252 -0
- package/dist/src/pipeline/steps/download.test.d.ts +1 -0
- package/dist/src/pipeline/steps/download.test.js +102 -0
- package/dist/src/pipeline/steps/fetch.d.ts +5 -0
- package/dist/src/pipeline/steps/fetch.js +122 -0
- package/dist/src/pipeline/steps/fetch.test.d.ts +1 -0
- package/dist/src/pipeline/steps/fetch.test.js +117 -0
- package/dist/src/pipeline/steps/intercept.d.ts +5 -0
- package/dist/src/pipeline/steps/intercept.js +50 -0
- package/dist/src/pipeline/steps/tap.d.ts +12 -0
- package/dist/src/pipeline/steps/tap.js +93 -0
- package/dist/src/pipeline/steps/transform.d.ts +9 -0
- package/dist/src/pipeline/steps/transform.js +70 -0
- package/dist/src/pipeline/template.d.ts +17 -0
- package/dist/src/pipeline/template.js +344 -0
- package/dist/src/pipeline/template.test.d.ts +4 -0
- package/dist/src/pipeline/template.test.js +197 -0
- package/dist/src/pipeline/transform.test.d.ts +4 -0
- package/dist/src/pipeline/transform.test.js +133 -0
- package/dist/src/plugin-manifest.d.ts +70 -0
- package/dist/src/plugin-manifest.js +160 -0
- package/dist/src/plugin-manifest.test.d.ts +4 -0
- package/dist/src/plugin-manifest.test.js +179 -0
- package/dist/src/plugin-scaffold.d.ts +28 -0
- package/dist/src/plugin-scaffold.js +143 -0
- package/dist/src/plugin-scaffold.test.d.ts +4 -0
- package/dist/src/plugin-scaffold.test.js +83 -0
- package/dist/src/plugin.d.ts +146 -0
- package/dist/src/plugin.js +1303 -0
- package/dist/src/plugin.test.d.ts +4 -0
- package/dist/src/plugin.test.js +1363 -0
- package/dist/src/registry-api.d.ts +13 -0
- package/dist/src/registry-api.js +10 -0
- package/dist/src/registry.d.ts +123 -0
- package/dist/src/registry.js +125 -0
- package/dist/src/registry.test.d.ts +4 -0
- package/dist/src/registry.test.js +209 -0
- package/dist/src/runtime-copy.test.d.ts +1 -0
- package/dist/src/runtime-copy.test.js +29 -0
- package/dist/src/runtime-detect.d.ts +31 -0
- package/dist/src/runtime-detect.js +47 -0
- package/dist/src/runtime-detect.test.d.ts +1 -0
- package/dist/src/runtime-detect.test.js +39 -0
- package/dist/src/runtime-identity.test.d.ts +1 -0
- package/dist/src/runtime-identity.test.js +18 -0
- package/dist/src/runtime.d.ts +47 -0
- package/dist/src/runtime.js +54 -0
- package/dist/src/scripts/framework.d.ts +7 -0
- package/dist/src/scripts/framework.js +25 -0
- package/dist/src/scripts/interact.d.ts +4 -0
- package/dist/src/scripts/interact.js +20 -0
- package/dist/src/scripts/store.d.ts +13 -0
- package/dist/src/scripts/store.js +48 -0
- package/dist/src/serialization.d.ts +42 -0
- package/dist/src/serialization.js +103 -0
- package/dist/src/serialization.test.d.ts +1 -0
- package/dist/src/serialization.test.js +78 -0
- package/dist/src/skills.d.ts +14 -0
- package/dist/src/skills.js +127 -0
- package/dist/src/skills.test.d.ts +1 -0
- package/dist/src/skills.test.js +71 -0
- package/dist/src/snapshotFormatter.d.ts +11 -0
- package/dist/src/snapshotFormatter.js +338 -0
- package/dist/src/snapshotFormatter.test.d.ts +7 -0
- package/dist/src/snapshotFormatter.test.js +521 -0
- package/dist/src/tui.d.ts +28 -0
- package/dist/src/tui.js +179 -0
- package/dist/src/tui.test.d.ts +1 -0
- package/dist/src/tui.test.js +19 -0
- package/dist/src/types.d.ts +236 -0
- package/dist/src/types.js +7 -0
- package/dist/src/update-check.d.ts +66 -0
- package/dist/src/update-check.js +206 -0
- package/dist/src/update-check.test.d.ts +1 -0
- package/dist/src/update-check.test.js +117 -0
- package/dist/src/utils.d.ts +57 -0
- package/dist/src/utils.js +152 -0
- package/dist/src/utils.test.d.ts +1 -0
- package/dist/src/utils.test.js +155 -0
- package/dist/src/validate.d.ts +21 -0
- package/dist/src/validate.js +130 -0
- package/dist/src/validate.test.d.ts +9 -0
- package/dist/src/validate.test.js +90 -0
- package/dist/src/verify.d.ts +26 -0
- package/dist/src/verify.js +67 -0
- package/dist/src/version.d.ts +4 -0
- package/dist/src/version.js +22 -0
- package/dist/src/weixin-download.test.d.ts +1 -0
- package/dist/src/weixin-download.test.js +45 -0
- package/package.json +4 -3
- package/scripts/check-package-bin.mjs +95 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the pipeline template engine: render, evalExpr, resolvePath.
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect } from 'vitest';
|
|
5
|
+
import { render, evalExpr, resolvePath, normalizeEvaluateSource } from './template.js';
|
|
6
|
+
describe('resolvePath', () => {
|
|
7
|
+
it('resolves args path', () => {
|
|
8
|
+
expect(resolvePath('args.limit', { args: { limit: 20 } })).toBe(20);
|
|
9
|
+
});
|
|
10
|
+
it('resolves nested args path', () => {
|
|
11
|
+
expect(resolvePath('args.query.keyword', { args: { query: { keyword: 'test' } } })).toBe('test');
|
|
12
|
+
});
|
|
13
|
+
it('resolves item path', () => {
|
|
14
|
+
expect(resolvePath('item.title', { item: { title: 'Hello' } })).toBe('Hello');
|
|
15
|
+
});
|
|
16
|
+
it('resolves implicit item path (no prefix)', () => {
|
|
17
|
+
expect(resolvePath('title', { item: { title: 'World' } })).toBe('World');
|
|
18
|
+
});
|
|
19
|
+
it('resolves index', () => {
|
|
20
|
+
expect(resolvePath('index', { index: 5 })).toBe(5);
|
|
21
|
+
});
|
|
22
|
+
it('resolves data path', () => {
|
|
23
|
+
expect(resolvePath('data.items', { data: { items: [1, 2, 3] } })).toEqual([1, 2, 3]);
|
|
24
|
+
});
|
|
25
|
+
it('resolves root path', () => {
|
|
26
|
+
expect(resolvePath('root.items', { root: { items: [1, 2, 3] } })).toEqual([1, 2, 3]);
|
|
27
|
+
});
|
|
28
|
+
it('returns null for missing path', () => {
|
|
29
|
+
expect(resolvePath('args.missing', { args: {} })).toBeUndefined();
|
|
30
|
+
});
|
|
31
|
+
it('resolves array index', () => {
|
|
32
|
+
expect(resolvePath('data.0', { data: ['a', 'b'] })).toBe('a');
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
describe('evalExpr', () => {
|
|
36
|
+
it('evaluates default filter', () => {
|
|
37
|
+
expect(evalExpr('args.limit | default(20)', { args: {} })).toBe(20);
|
|
38
|
+
});
|
|
39
|
+
it('uses actual value over default', () => {
|
|
40
|
+
expect(evalExpr('args.limit | default(20)', { args: { limit: 10 } })).toBe(10);
|
|
41
|
+
});
|
|
42
|
+
it('evaluates string default', () => {
|
|
43
|
+
expect(evalExpr("args.name | default('unknown')", { args: {} })).toBe('unknown');
|
|
44
|
+
});
|
|
45
|
+
it('evaluates arithmetic: index + 1', () => {
|
|
46
|
+
expect(evalExpr('index + 1', { index: 0 })).toBe(1);
|
|
47
|
+
});
|
|
48
|
+
it('evaluates arithmetic: index * 2', () => {
|
|
49
|
+
expect(evalExpr('index * 2', { index: 5 })).toBe(10);
|
|
50
|
+
});
|
|
51
|
+
it('evaluates || fallback', () => {
|
|
52
|
+
expect(evalExpr("item.name || 'N/A'", { item: {} })).toBe('N/A');
|
|
53
|
+
});
|
|
54
|
+
it('evaluates || with truthy left', () => {
|
|
55
|
+
expect(evalExpr("item.name || 'N/A'", { item: { name: 'Alice' } })).toBe('Alice');
|
|
56
|
+
});
|
|
57
|
+
it('evaluates chained || fallback (issue #303)', () => {
|
|
58
|
+
// When first two are falsy, should evaluate through to the string literal
|
|
59
|
+
expect(evalExpr("item.a || item.b || 'default'", { item: {} })).toBe('default');
|
|
60
|
+
});
|
|
61
|
+
it('evaluates chained || with middle value truthy', () => {
|
|
62
|
+
expect(evalExpr("item.a || item.b || 'default'", { item: { b: 'middle' } })).toBe('middle');
|
|
63
|
+
});
|
|
64
|
+
it('evaluates chained || with first value truthy', () => {
|
|
65
|
+
expect(evalExpr("item.a || item.b || 'default'", { item: { a: 'first', b: 'middle' } })).toBe('first');
|
|
66
|
+
});
|
|
67
|
+
it('evaluates || with 0 as falsy left (JS semantics)', () => {
|
|
68
|
+
expect(evalExpr("item.count || 'N/A'", { item: { count: 0 } })).toBe('N/A');
|
|
69
|
+
});
|
|
70
|
+
it('evaluates || with empty string as falsy left', () => {
|
|
71
|
+
expect(evalExpr("item.name || 'unknown'", { item: { name: '' } })).toBe('unknown');
|
|
72
|
+
});
|
|
73
|
+
it('evaluates || with numeric fallback returning number type', () => {
|
|
74
|
+
expect(evalExpr('item.a || 42', { item: {} })).toBe(42);
|
|
75
|
+
});
|
|
76
|
+
it('evaluates 4-way chained ||', () => {
|
|
77
|
+
expect(evalExpr("item.a || item.b || item.c || 'last'", { item: { c: 'third' } })).toBe('third');
|
|
78
|
+
});
|
|
79
|
+
it('handles || combined with pipe filter', () => {
|
|
80
|
+
expect(evalExpr("item.a || item.b | upper", { item: { b: 'hello' } })).toBe('HELLO');
|
|
81
|
+
});
|
|
82
|
+
it('resolves simple path', () => {
|
|
83
|
+
expect(evalExpr('item.title', { item: { title: 'Test' } })).toBe('Test');
|
|
84
|
+
});
|
|
85
|
+
it('evaluates JS helper expressions', () => {
|
|
86
|
+
expect(evalExpr('encodeURIComponent(args.keyword)', { args: { keyword: 'hello world' } })).toBe('hello%20world');
|
|
87
|
+
});
|
|
88
|
+
it('evaluates ternary expressions', () => {
|
|
89
|
+
expect(evalExpr("args.kind === 'tech' ? 'technology' : args.kind", { args: { kind: 'tech' } })).toBe('technology');
|
|
90
|
+
});
|
|
91
|
+
it('evaluates method calls on values', () => {
|
|
92
|
+
expect(evalExpr("args.username.startsWith('@') ? args.username : '@' + args.username", { args: { username: 'alice' } })).toBe('@alice');
|
|
93
|
+
});
|
|
94
|
+
it('rejects constructor-based sandbox escapes', () => {
|
|
95
|
+
expect(evalExpr("args['cons' + 'tructor']['constructor']('return process')()", { args: {} })).toBeUndefined();
|
|
96
|
+
});
|
|
97
|
+
it('applies join filter', () => {
|
|
98
|
+
expect(evalExpr('item.tags | join(,)', { item: { tags: ['a', 'b', 'c'] } })).toBe('a,b,c');
|
|
99
|
+
});
|
|
100
|
+
it('applies upper filter', () => {
|
|
101
|
+
expect(evalExpr('item.name | upper', { item: { name: 'hello' } })).toBe('HELLO');
|
|
102
|
+
});
|
|
103
|
+
it('applies lower filter', () => {
|
|
104
|
+
expect(evalExpr('item.name | lower', { item: { name: 'HELLO' } })).toBe('hello');
|
|
105
|
+
});
|
|
106
|
+
it('applies truncate filter', () => {
|
|
107
|
+
expect(evalExpr('item.text | truncate(5)', { item: { text: 'Hello World!' } })).toBe('Hello...');
|
|
108
|
+
});
|
|
109
|
+
it('chains filters', () => {
|
|
110
|
+
expect(evalExpr('item.name | upper | truncate(3)', { item: { name: 'hello' } })).toBe('HEL...');
|
|
111
|
+
});
|
|
112
|
+
it('applies length filter', () => {
|
|
113
|
+
expect(evalExpr('item.items | length', { item: { items: [1, 2, 3] } })).toBe(3);
|
|
114
|
+
});
|
|
115
|
+
it('applies json filter to strings with quotes', () => {
|
|
116
|
+
expect(evalExpr('args.keyword | json', { args: { keyword: "O'Reilly" } })).toBe('"O\'Reilly"');
|
|
117
|
+
});
|
|
118
|
+
it('applies json filter to nullish values', () => {
|
|
119
|
+
expect(evalExpr('args.keyword | json', { args: {} })).toBe('null');
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
describe('render', () => {
|
|
123
|
+
it('renders full expression', () => {
|
|
124
|
+
expect(render('${{ args.limit }}', { args: { limit: 30 } })).toBe(30);
|
|
125
|
+
});
|
|
126
|
+
it('renders inline expression in string', () => {
|
|
127
|
+
expect(render('Hello ${{ item.name }}!', { item: { name: 'World' } })).toBe('Hello World!');
|
|
128
|
+
});
|
|
129
|
+
it('renders multiple inline expressions', () => {
|
|
130
|
+
expect(render('${{ item.first }}-${{ item.second }}', { item: { first: 'X', second: 'Y' } })).toBe('X-Y');
|
|
131
|
+
});
|
|
132
|
+
it('returns non-string values as-is', () => {
|
|
133
|
+
expect(render(42, {})).toBe(42);
|
|
134
|
+
expect(render(null, {})).toBeNull();
|
|
135
|
+
expect(render(undefined, {})).toBeUndefined();
|
|
136
|
+
});
|
|
137
|
+
it('returns full expression result as native type', () => {
|
|
138
|
+
expect(render('${{ args.list }}', { args: { list: [1, 2, 3] } })).toEqual([1, 2, 3]);
|
|
139
|
+
});
|
|
140
|
+
it('renders URL template', () => {
|
|
141
|
+
expect(render('https://api.example.com/search?q=${{ args.keyword }}', { args: { keyword: 'test' } })).toBe('https://api.example.com/search?q=test');
|
|
142
|
+
});
|
|
143
|
+
it('renders inline helper expressions', () => {
|
|
144
|
+
expect(render('https://example.com/search?q=${{ encodeURIComponent(args.keyword) }}', { args: { keyword: 'hello world' } })).toBe('https://example.com/search?q=hello%20world');
|
|
145
|
+
});
|
|
146
|
+
it('renders full multiline expressions', () => {
|
|
147
|
+
expect(render("${{\n args.topic ? `https://medium.com/tag/${args.topic}` : 'https://medium.com/tag/technology'\n}}", { args: { topic: 'ai' } })).toBe('https://medium.com/tag/ai');
|
|
148
|
+
});
|
|
149
|
+
it('renders block expressions with surrounding whitespace', () => {
|
|
150
|
+
expect(render("\n ${{ args.kind === 'tech' ? 'technology' : args.kind }}\n", { args: { kind: 'tech' } })).toBe('technology');
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
describe('normalizeEvaluateSource', () => {
|
|
154
|
+
it('wraps bare expression', () => {
|
|
155
|
+
expect(normalizeEvaluateSource('document.title')).toBe('() => (document.title)');
|
|
156
|
+
});
|
|
157
|
+
it('passes through arrow function', () => {
|
|
158
|
+
expect(normalizeEvaluateSource('() => 42')).toBe('() => 42');
|
|
159
|
+
});
|
|
160
|
+
it('passes through async arrow function', () => {
|
|
161
|
+
const src = 'async () => { return 1; }';
|
|
162
|
+
expect(normalizeEvaluateSource(src)).toBe(src);
|
|
163
|
+
});
|
|
164
|
+
it('passes through named function', () => {
|
|
165
|
+
const src = 'function foo() { return 1; }';
|
|
166
|
+
expect(normalizeEvaluateSource(src)).toBe(src);
|
|
167
|
+
});
|
|
168
|
+
it('wraps IIFE pattern', () => {
|
|
169
|
+
const src = '(async () => { return 1; })()';
|
|
170
|
+
expect(normalizeEvaluateSource(src)).toBe(`() => (${src})`);
|
|
171
|
+
});
|
|
172
|
+
it('handles empty string', () => {
|
|
173
|
+
expect(normalizeEvaluateSource('')).toBe('() => undefined');
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
describe('sanitizeContext (via VM sandbox path)', () => {
|
|
177
|
+
it('coerces BigInt fields to string instead of silently dropping the branch', () => {
|
|
178
|
+
// Previously a BigInt anywhere in args would make JSON.stringify throw and
|
|
179
|
+
// sanitizeContext returned {}, so any reference to args.id in the JS
|
|
180
|
+
// sandbox would resolve to undefined. We now serialize BigInt as string
|
|
181
|
+
// so the value survives the sandbox copy.
|
|
182
|
+
const id = BigInt('9007199254740993'); // > Number.MAX_SAFE_INTEGER
|
|
183
|
+
const ctx = { args: { id } };
|
|
184
|
+
// Use an expression that forces evalJsExpr (not the resolvePath fast path)
|
|
185
|
+
// so it goes through sanitizeContext + VM sandbox.
|
|
186
|
+
expect(evalExpr('String(args.id)', ctx)).toBe('9007199254740993');
|
|
187
|
+
});
|
|
188
|
+
it('does not crash on circular references (returns {} after warning)', () => {
|
|
189
|
+
// Circular refs still can't be safely round-tripped; verify the
|
|
190
|
+
// fallback is the empty-object path, not a crash.
|
|
191
|
+
const args = { keep: 'me' };
|
|
192
|
+
args.self = args;
|
|
193
|
+
const ctx = { args };
|
|
194
|
+
// The expression must not throw even if the args object has a cycle.
|
|
195
|
+
expect(() => evalExpr('args.keep || "fallback"', ctx)).not.toThrow();
|
|
196
|
+
});
|
|
197
|
+
});
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for pipeline transform steps: select, map, filter, sort, limit.
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect } from 'vitest';
|
|
5
|
+
import { stepSelect, stepMap, stepFilter, stepSort, stepLimit } from './steps/transform.js';
|
|
6
|
+
const SAMPLE_DATA = [
|
|
7
|
+
{ title: 'Alpha', score: 10, author: 'Alice' },
|
|
8
|
+
{ title: 'Beta', score: 30, author: 'Bob' },
|
|
9
|
+
{ title: 'Gamma', score: 20, author: 'Charlie' },
|
|
10
|
+
];
|
|
11
|
+
describe('stepSelect', () => {
|
|
12
|
+
it('selects nested path', async () => {
|
|
13
|
+
const data = { result: { items: [1, 2, 3] } };
|
|
14
|
+
const result = await stepSelect(null, 'result.items', data, {});
|
|
15
|
+
expect(result).toEqual([1, 2, 3]);
|
|
16
|
+
});
|
|
17
|
+
it('selects array by index', async () => {
|
|
18
|
+
const data = { list: ['a', 'b', 'c'] };
|
|
19
|
+
const result = await stepSelect(null, 'list.1', data, {});
|
|
20
|
+
expect(result).toBe('b');
|
|
21
|
+
});
|
|
22
|
+
it('returns null for missing path', async () => {
|
|
23
|
+
const result = await stepSelect(null, 'missing.path', { foo: 1 }, {});
|
|
24
|
+
expect(result).toBeNull();
|
|
25
|
+
});
|
|
26
|
+
it('returns data as-is for non-object', async () => {
|
|
27
|
+
const result = await stepSelect(null, 'foo', 'string-data', {});
|
|
28
|
+
expect(result).toBe('string-data');
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
describe('stepMap', () => {
|
|
32
|
+
it('maps array items', async () => {
|
|
33
|
+
const result = await stepMap(null, {
|
|
34
|
+
name: '${{ item.title }}',
|
|
35
|
+
rank: '${{ index + 1 }}',
|
|
36
|
+
}, SAMPLE_DATA, {});
|
|
37
|
+
expect(result).toEqual([
|
|
38
|
+
{ name: 'Alpha', rank: 1 },
|
|
39
|
+
{ name: 'Beta', rank: 2 },
|
|
40
|
+
{ name: 'Gamma', rank: 3 },
|
|
41
|
+
]);
|
|
42
|
+
});
|
|
43
|
+
it('handles single object', async () => {
|
|
44
|
+
const result = await stepMap(null, {
|
|
45
|
+
name: '${{ item.title }}',
|
|
46
|
+
}, { title: 'Solo' }, {});
|
|
47
|
+
expect(result).toEqual([{ name: 'Solo' }]);
|
|
48
|
+
});
|
|
49
|
+
it('returns null/undefined as-is', async () => {
|
|
50
|
+
expect(await stepMap(null, { x: '${{ item.x }}' }, null, {})).toBeNull();
|
|
51
|
+
});
|
|
52
|
+
it('supports inline select before mapping', async () => {
|
|
53
|
+
const result = await stepMap(null, {
|
|
54
|
+
select: 'posts',
|
|
55
|
+
title: '${{ item.title }}',
|
|
56
|
+
rank: '${{ index + 1 }}',
|
|
57
|
+
}, { posts: [{ title: 'One' }, { title: 'Two' }] }, {});
|
|
58
|
+
expect(result).toEqual([
|
|
59
|
+
{ title: 'One', rank: 1 },
|
|
60
|
+
{ title: 'Two', rank: 2 },
|
|
61
|
+
]);
|
|
62
|
+
});
|
|
63
|
+
it('keeps data bound to the selected source and exposes root separately', async () => {
|
|
64
|
+
const result = await stepMap(null, {
|
|
65
|
+
select: 'bids',
|
|
66
|
+
bid_price: '${{ data[index][0] }}',
|
|
67
|
+
ask_price: '${{ root.asks[index][0] }}',
|
|
68
|
+
}, {
|
|
69
|
+
bids: [['100', '2'], ['99', '3']],
|
|
70
|
+
asks: [['101', '1'], ['102', '4']],
|
|
71
|
+
}, {});
|
|
72
|
+
expect(result).toEqual([
|
|
73
|
+
{ bid_price: '100', ask_price: '101' },
|
|
74
|
+
{ bid_price: '99', ask_price: '102' },
|
|
75
|
+
]);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
describe('stepFilter', () => {
|
|
79
|
+
it('filters by expression', async () => {
|
|
80
|
+
const result = await stepFilter(null, 'item.score', SAMPLE_DATA, {});
|
|
81
|
+
expect(result).toHaveLength(3); // all truthy
|
|
82
|
+
});
|
|
83
|
+
it('returns non-array as-is', async () => {
|
|
84
|
+
const result = await stepFilter(null, 'item.x', 'not-array', {});
|
|
85
|
+
expect(result).toBe('not-array');
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
describe('stepSort', () => {
|
|
89
|
+
it('sorts ascending by key', async () => {
|
|
90
|
+
const result = await stepSort(null, 'score', SAMPLE_DATA, {});
|
|
91
|
+
expect(result.map((r) => r.title)).toEqual(['Alpha', 'Gamma', 'Beta']);
|
|
92
|
+
});
|
|
93
|
+
it('sorts descending', async () => {
|
|
94
|
+
const result = await stepSort(null, { by: 'score', order: 'desc' }, SAMPLE_DATA, {});
|
|
95
|
+
expect(result.map((r) => r.title)).toEqual(['Beta', 'Gamma', 'Alpha']);
|
|
96
|
+
});
|
|
97
|
+
it('does not mutate original', async () => {
|
|
98
|
+
const original = [...SAMPLE_DATA];
|
|
99
|
+
await stepSort(null, 'score', SAMPLE_DATA, {});
|
|
100
|
+
expect(SAMPLE_DATA).toEqual(original);
|
|
101
|
+
});
|
|
102
|
+
it('sorts string-encoded numbers naturally by default', async () => {
|
|
103
|
+
const data = [
|
|
104
|
+
{ name: 'A', volume: '99' },
|
|
105
|
+
{ name: 'B', volume: '1000' },
|
|
106
|
+
{ name: 'C', volume: '250' },
|
|
107
|
+
];
|
|
108
|
+
const result = await stepSort(null, { by: 'volume', order: 'desc' }, data, {});
|
|
109
|
+
expect(result.map((r) => r.name)).toEqual(['B', 'C', 'A']);
|
|
110
|
+
});
|
|
111
|
+
it('handles missing fields gracefully', async () => {
|
|
112
|
+
const data = [
|
|
113
|
+
{ name: 'A', value: '10' },
|
|
114
|
+
{ name: 'B' },
|
|
115
|
+
{ name: 'C', value: '5' },
|
|
116
|
+
];
|
|
117
|
+
const result = await stepSort(null, { by: 'value', order: 'asc' }, data, {});
|
|
118
|
+
expect(result.map((r) => r.name)).toEqual(['B', 'C', 'A']);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
describe('stepLimit', () => {
|
|
122
|
+
it('limits array to N items', async () => {
|
|
123
|
+
const result = await stepLimit(null, '2', SAMPLE_DATA, {});
|
|
124
|
+
expect(result).toHaveLength(2);
|
|
125
|
+
});
|
|
126
|
+
it('limits using template expression', async () => {
|
|
127
|
+
const result = await stepLimit(null, '${{ args.limit }}', SAMPLE_DATA, { limit: 1 });
|
|
128
|
+
expect(result).toHaveLength(1);
|
|
129
|
+
});
|
|
130
|
+
it('returns non-array as-is', async () => {
|
|
131
|
+
expect(await stepLimit(null, '5', 'string', {})).toBe('string');
|
|
132
|
+
});
|
|
133
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin manifest: reads and validates webcmd-plugin.json files.
|
|
3
|
+
*
|
|
4
|
+
* Supports two modes:
|
|
5
|
+
* 1. Single plugin: repo root IS the plugin directory.
|
|
6
|
+
* 2. Monorepo: repo contains multiple plugins declared in `plugins` field.
|
|
7
|
+
*/
|
|
8
|
+
export interface SubPluginEntry {
|
|
9
|
+
/** Relative path from repo root to the sub-plugin directory. */
|
|
10
|
+
path: string;
|
|
11
|
+
version?: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
/** Semver range for webcmd compatibility (overrides top-level). */
|
|
14
|
+
webcmd?: string;
|
|
15
|
+
/** When true, this sub-plugin is skipped during install. */
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface PluginManifest {
|
|
19
|
+
/** Plugin name (single-plugin mode). */
|
|
20
|
+
name?: string;
|
|
21
|
+
/** Semantic version of the plugin (single-plugin mode). */
|
|
22
|
+
version?: string;
|
|
23
|
+
/** Semver range for webcmd compatibility, e.g. ">=1.0.0". */
|
|
24
|
+
webcmd?: string;
|
|
25
|
+
/** Human-readable description. */
|
|
26
|
+
description?: string;
|
|
27
|
+
/** Monorepo sub-plugins. Key = logical plugin name. */
|
|
28
|
+
plugins?: Record<string, SubPluginEntry>;
|
|
29
|
+
}
|
|
30
|
+
export declare const MANIFEST_FILENAME = "webcmd-plugin.json";
|
|
31
|
+
/**
|
|
32
|
+
* Read and parse webcmd-plugin.json from a directory.
|
|
33
|
+
* Returns null if the file does not exist or is unparseable.
|
|
34
|
+
*/
|
|
35
|
+
export declare function readPluginManifest(dir: string): PluginManifest | null;
|
|
36
|
+
/** Returns true when the manifest declares a monorepo (has `plugins` field). */
|
|
37
|
+
export declare function isMonorepo(manifest: PluginManifest): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Get the list of enabled sub-plugins from a monorepo manifest.
|
|
40
|
+
* Returns entries sorted by key name.
|
|
41
|
+
*/
|
|
42
|
+
export declare function getEnabledPlugins(manifest: PluginManifest): Array<{
|
|
43
|
+
name: string;
|
|
44
|
+
entry: SubPluginEntry;
|
|
45
|
+
}>;
|
|
46
|
+
/**
|
|
47
|
+
* Check if the current webcmd version satisfies a semver range string.
|
|
48
|
+
*
|
|
49
|
+
* Supports a simplified subset of semver ranges:
|
|
50
|
+
* ">=1.0.0" – greater than or equal
|
|
51
|
+
* "<=1.5.0" – less than or equal
|
|
52
|
+
* ">1.0.0" – strictly greater
|
|
53
|
+
* "<2.0.0" – strictly less
|
|
54
|
+
* "^1.2.0" – compatible (>=1.2.0 and <2.0.0)
|
|
55
|
+
* "~1.2.0" – patch-level (>=1.2.0 and <1.3.0)
|
|
56
|
+
* "1.2.0" – exact match
|
|
57
|
+
* ">=1.0.0 <2.0.0" – multiple constraints (space-separated, all must match)
|
|
58
|
+
*
|
|
59
|
+
* Returns true if compatible, false if not, and true for empty/undefined
|
|
60
|
+
* ranges (no constraint = always compatible).
|
|
61
|
+
*/
|
|
62
|
+
export declare function checkCompatibility(range: string | undefined): boolean;
|
|
63
|
+
/** Parse a version string ("1.2.3") into [major, minor, patch]. */
|
|
64
|
+
export declare function parseVersion(version: string): [number, number, number] | null;
|
|
65
|
+
/**
|
|
66
|
+
* Check if a version string satisfies a range expression.
|
|
67
|
+
* Space-separated constraints are ANDed together.
|
|
68
|
+
*/
|
|
69
|
+
export declare function satisfiesRange(versionStr: string, range: string): boolean;
|
|
70
|
+
export { readPluginManifest as _readPluginManifest, isMonorepo as _isMonorepo, getEnabledPlugins as _getEnabledPlugins, checkCompatibility as _checkCompatibility, parseVersion as _parseVersion, satisfiesRange as _satisfiesRange, };
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin manifest: reads and validates webcmd-plugin.json files.
|
|
3
|
+
*
|
|
4
|
+
* Supports two modes:
|
|
5
|
+
* 1. Single plugin: repo root IS the plugin directory.
|
|
6
|
+
* 2. Monorepo: repo contains multiple plugins declared in `plugins` field.
|
|
7
|
+
*/
|
|
8
|
+
import * as fs from 'node:fs';
|
|
9
|
+
import * as path from 'node:path';
|
|
10
|
+
import { PKG_VERSION } from './version.js';
|
|
11
|
+
export const MANIFEST_FILENAME = 'webcmd-plugin.json';
|
|
12
|
+
// ── Read / Validate ─────────────────────────────────────────────────────────
|
|
13
|
+
/**
|
|
14
|
+
* Read and parse webcmd-plugin.json from a directory.
|
|
15
|
+
* Returns null if the file does not exist or is unparseable.
|
|
16
|
+
*/
|
|
17
|
+
export function readPluginManifest(dir) {
|
|
18
|
+
const manifestPath = path.join(dir, MANIFEST_FILENAME);
|
|
19
|
+
try {
|
|
20
|
+
const raw = fs.readFileSync(manifestPath, 'utf-8');
|
|
21
|
+
const parsed = JSON.parse(raw);
|
|
22
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
return parsed;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** Returns true when the manifest declares a monorepo (has `plugins` field). */
|
|
32
|
+
export function isMonorepo(manifest) {
|
|
33
|
+
return (manifest.plugins !== undefined &&
|
|
34
|
+
manifest.plugins !== null &&
|
|
35
|
+
typeof manifest.plugins === 'object' &&
|
|
36
|
+
Object.keys(manifest.plugins).length > 0);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Get the list of enabled sub-plugins from a monorepo manifest.
|
|
40
|
+
* Returns entries sorted by key name.
|
|
41
|
+
*/
|
|
42
|
+
export function getEnabledPlugins(manifest) {
|
|
43
|
+
if (!manifest.plugins)
|
|
44
|
+
return [];
|
|
45
|
+
return Object.entries(manifest.plugins)
|
|
46
|
+
.filter(([, entry]) => !entry.disabled)
|
|
47
|
+
.map(([name, entry]) => ({ name, entry }))
|
|
48
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
49
|
+
}
|
|
50
|
+
// ── Version compatibility ───────────────────────────────────────────────────
|
|
51
|
+
/**
|
|
52
|
+
* Check if the current webcmd version satisfies a semver range string.
|
|
53
|
+
*
|
|
54
|
+
* Supports a simplified subset of semver ranges:
|
|
55
|
+
* ">=1.0.0" – greater than or equal
|
|
56
|
+
* "<=1.5.0" – less than or equal
|
|
57
|
+
* ">1.0.0" – strictly greater
|
|
58
|
+
* "<2.0.0" – strictly less
|
|
59
|
+
* "^1.2.0" – compatible (>=1.2.0 and <2.0.0)
|
|
60
|
+
* "~1.2.0" – patch-level (>=1.2.0 and <1.3.0)
|
|
61
|
+
* "1.2.0" – exact match
|
|
62
|
+
* ">=1.0.0 <2.0.0" – multiple constraints (space-separated, all must match)
|
|
63
|
+
*
|
|
64
|
+
* Returns true if compatible, false if not, and true for empty/undefined
|
|
65
|
+
* ranges (no constraint = always compatible).
|
|
66
|
+
*/
|
|
67
|
+
export function checkCompatibility(range) {
|
|
68
|
+
if (!range || range.trim() === '')
|
|
69
|
+
return true;
|
|
70
|
+
return satisfiesRange(PKG_VERSION, range);
|
|
71
|
+
}
|
|
72
|
+
/** Parse a version string ("1.2.3") into [major, minor, patch]. */
|
|
73
|
+
export function parseVersion(version) {
|
|
74
|
+
const match = version.trim().match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
75
|
+
if (!match)
|
|
76
|
+
return null;
|
|
77
|
+
return [parseInt(match[1], 10), parseInt(match[2], 10), parseInt(match[3], 10)];
|
|
78
|
+
}
|
|
79
|
+
/** Compare two version tuples: -1 if a<b, 0 if equal, 1 if a>b. */
|
|
80
|
+
function compareVersions(a, b) {
|
|
81
|
+
for (let i = 0; i < 3; i++) {
|
|
82
|
+
if (a[i] < b[i])
|
|
83
|
+
return -1;
|
|
84
|
+
if (a[i] > b[i])
|
|
85
|
+
return 1;
|
|
86
|
+
}
|
|
87
|
+
return 0;
|
|
88
|
+
}
|
|
89
|
+
/** Check if a version satisfies a single constraint like ">=1.2.0". */
|
|
90
|
+
function satisfiesSingleConstraint(version, constraint) {
|
|
91
|
+
const trimmed = constraint.trim();
|
|
92
|
+
if (!trimmed)
|
|
93
|
+
return true;
|
|
94
|
+
// ^1.2.0 → >=1.2.0 <2.0.0
|
|
95
|
+
if (trimmed.startsWith('^')) {
|
|
96
|
+
const target = parseVersion(trimmed.slice(1));
|
|
97
|
+
if (!target)
|
|
98
|
+
return true;
|
|
99
|
+
const upper = [target[0] + 1, 0, 0];
|
|
100
|
+
return compareVersions(version, target) >= 0 && compareVersions(version, upper) < 0;
|
|
101
|
+
}
|
|
102
|
+
// ~1.2.0 → >=1.2.0 <1.3.0
|
|
103
|
+
if (trimmed.startsWith('~')) {
|
|
104
|
+
const target = parseVersion(trimmed.slice(1));
|
|
105
|
+
if (!target)
|
|
106
|
+
return true;
|
|
107
|
+
const upper = [target[0], target[1] + 1, 0];
|
|
108
|
+
return compareVersions(version, target) >= 0 && compareVersions(version, upper) < 0;
|
|
109
|
+
}
|
|
110
|
+
// >=, <=, >, <, =
|
|
111
|
+
if (trimmed.startsWith('>=')) {
|
|
112
|
+
const target = parseVersion(trimmed.slice(2));
|
|
113
|
+
if (!target)
|
|
114
|
+
return true;
|
|
115
|
+
return compareVersions(version, target) >= 0;
|
|
116
|
+
}
|
|
117
|
+
if (trimmed.startsWith('<=')) {
|
|
118
|
+
const target = parseVersion(trimmed.slice(2));
|
|
119
|
+
if (!target)
|
|
120
|
+
return true;
|
|
121
|
+
return compareVersions(version, target) <= 0;
|
|
122
|
+
}
|
|
123
|
+
if (trimmed.startsWith('>')) {
|
|
124
|
+
const target = parseVersion(trimmed.slice(1));
|
|
125
|
+
if (!target)
|
|
126
|
+
return true;
|
|
127
|
+
return compareVersions(version, target) > 0;
|
|
128
|
+
}
|
|
129
|
+
if (trimmed.startsWith('<')) {
|
|
130
|
+
const target = parseVersion(trimmed.slice(1));
|
|
131
|
+
if (!target)
|
|
132
|
+
return true;
|
|
133
|
+
return compareVersions(version, target) < 0;
|
|
134
|
+
}
|
|
135
|
+
if (trimmed.startsWith('=')) {
|
|
136
|
+
const target = parseVersion(trimmed.slice(1));
|
|
137
|
+
if (!target)
|
|
138
|
+
return true;
|
|
139
|
+
return compareVersions(version, target) === 0;
|
|
140
|
+
}
|
|
141
|
+
// Exact match
|
|
142
|
+
const target = parseVersion(trimmed);
|
|
143
|
+
if (!target)
|
|
144
|
+
return true;
|
|
145
|
+
return compareVersions(version, target) === 0;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Check if a version string satisfies a range expression.
|
|
149
|
+
* Space-separated constraints are ANDed together.
|
|
150
|
+
*/
|
|
151
|
+
export function satisfiesRange(versionStr, range) {
|
|
152
|
+
const version = parseVersion(versionStr);
|
|
153
|
+
if (!version)
|
|
154
|
+
return true; // Can't parse our own version → assume ok
|
|
155
|
+
// Split on whitespace for multi-constraint ranges (e.g. ">=1.0.0 <2.0.0")
|
|
156
|
+
const constraints = range.trim().split(/\s+/);
|
|
157
|
+
return constraints.every((c) => satisfiesSingleConstraint(version, c));
|
|
158
|
+
}
|
|
159
|
+
// ── Exports for testing ─────────────────────────────────────────────────────
|
|
160
|
+
export { readPluginManifest as _readPluginManifest, isMonorepo as _isMonorepo, getEnabledPlugins as _getEnabledPlugins, checkCompatibility as _checkCompatibility, parseVersion as _parseVersion, satisfiesRange as _satisfiesRange, };
|