@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,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pipeline step: tap — declarative Store Action Bridge.
|
|
3
|
+
*
|
|
4
|
+
* Generates a self-contained IIFE that:
|
|
5
|
+
* 1. Injects fetch + XHR dual interception proxy
|
|
6
|
+
* 2. Finds the Pinia/Vuex store and calls the action
|
|
7
|
+
* 3. Captures the response matching the URL pattern
|
|
8
|
+
* 4. Auto-cleans up interception in finally block
|
|
9
|
+
* 5. Returns the captured data (optionally sub-selected)
|
|
10
|
+
*/
|
|
11
|
+
import { render } from '../template.js';
|
|
12
|
+
import { generateTapInterceptorJs } from '../../interceptor.js';
|
|
13
|
+
import { CliError } from '../../errors.js';
|
|
14
|
+
export async function stepTap(page, params, data, args) {
|
|
15
|
+
const cfg = typeof params === 'object' && params !== null ? params : {};
|
|
16
|
+
const storeName = String(render(cfg.store ?? '', { args, data }));
|
|
17
|
+
const actionName = String(render(cfg.action ?? '', { args, data }));
|
|
18
|
+
const capturePattern = String(render(cfg.capture ?? '', { args, data }));
|
|
19
|
+
const timeout = cfg.timeout ?? 5;
|
|
20
|
+
const selectPath = cfg.select ? String(render(cfg.select, { args, data })) : null;
|
|
21
|
+
const framework = cfg.framework ?? null;
|
|
22
|
+
const actionArgs = cfg.args ?? [];
|
|
23
|
+
if (!storeName || !actionName)
|
|
24
|
+
throw new CliError('TAP_MISSING_PARAMS', 'tap: store and action are required');
|
|
25
|
+
// Build select chain for the captured response
|
|
26
|
+
const selectChain = selectPath
|
|
27
|
+
? selectPath.split('.').map((p) => `?.[${JSON.stringify(p)}]`).join('')
|
|
28
|
+
: '';
|
|
29
|
+
// Serialize action arguments
|
|
30
|
+
const actionArgsRendered = actionArgs.map((a) => {
|
|
31
|
+
const rendered = render(a, { args, data });
|
|
32
|
+
return JSON.stringify(rendered);
|
|
33
|
+
});
|
|
34
|
+
const actionCall = actionArgsRendered.length
|
|
35
|
+
? `store[${JSON.stringify(actionName)}](${actionArgsRendered.join(', ')})`
|
|
36
|
+
: `store[${JSON.stringify(actionName)}]()`;
|
|
37
|
+
// Use shared interceptor generator for fetch/XHR patching
|
|
38
|
+
const tap = generateTapInterceptorJs(JSON.stringify(capturePattern));
|
|
39
|
+
const js = `
|
|
40
|
+
async () => {
|
|
41
|
+
// ── 1. Setup capture proxy (fetch + XHR dual interception) ──
|
|
42
|
+
${tap.setupVar}
|
|
43
|
+
${tap.fetchPatch}
|
|
44
|
+
${tap.xhrPatch}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
// ── 2. Find store ──
|
|
48
|
+
let store = null;
|
|
49
|
+
const storeName = ${JSON.stringify(storeName)};
|
|
50
|
+
const fw = ${JSON.stringify(framework)};
|
|
51
|
+
|
|
52
|
+
const app = document.querySelector('#app');
|
|
53
|
+
if (!fw || fw === 'pinia') {
|
|
54
|
+
try {
|
|
55
|
+
const pinia = app?.__vue_app__?.config?.globalProperties?.$pinia;
|
|
56
|
+
if (pinia?._s) store = pinia._s.get(storeName);
|
|
57
|
+
} catch {}
|
|
58
|
+
}
|
|
59
|
+
if (!store && (!fw || fw === 'vuex')) {
|
|
60
|
+
try {
|
|
61
|
+
const vuexStore = app?.__vue_app__?.config?.globalProperties?.$store
|
|
62
|
+
?? app?.__vue__?.$store;
|
|
63
|
+
if (vuexStore) {
|
|
64
|
+
store = { [${JSON.stringify(actionName)}]: (...a) => vuexStore.dispatch(storeName + '/' + ${JSON.stringify(actionName)}, ...a) };
|
|
65
|
+
}
|
|
66
|
+
} catch {}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!store) return { error: 'Store not found: ' + storeName, hint: 'Page may not be fully loaded or store name may be incorrect' };
|
|
70
|
+
if (typeof store[${JSON.stringify(actionName)}] !== 'function') {
|
|
71
|
+
return { error: 'Action not found: ' + ${JSON.stringify(actionName)} + ' on store ' + storeName,
|
|
72
|
+
hint: 'Available: ' + Object.keys(store).filter(k => typeof store[k] === 'function' && !k.startsWith('$') && !k.startsWith('_')).join(', ') };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ── 3. Call store action ──
|
|
76
|
+
await ${actionCall};
|
|
77
|
+
|
|
78
|
+
// ── 4. Wait for network response ──
|
|
79
|
+
if (!${tap.capturedVar}) {
|
|
80
|
+
const timeoutPromise = new Promise(r => setTimeout(r, ${timeout} * 1000));
|
|
81
|
+
await Promise.race([${tap.promiseVar}, timeoutPromise]);
|
|
82
|
+
}
|
|
83
|
+
} finally {
|
|
84
|
+
// ── 5. Always restore originals ──
|
|
85
|
+
${tap.restorePatch}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!${tap.capturedVar}) return { error: 'No matching response captured for pattern: ' + capturePattern };
|
|
89
|
+
return ${tap.capturedVar}${selectChain} ?? ${tap.capturedVar};
|
|
90
|
+
}
|
|
91
|
+
`;
|
|
92
|
+
return page.evaluate(js);
|
|
93
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pipeline steps: data transforms — select, map, filter, sort, limit.
|
|
3
|
+
*/
|
|
4
|
+
import type { IPage } from '../../types.js';
|
|
5
|
+
export declare function stepSelect(_page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown>;
|
|
6
|
+
export declare function stepMap(_page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown>;
|
|
7
|
+
export declare function stepFilter(_page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown>;
|
|
8
|
+
export declare function stepSort(_page: IPage | null, params: unknown, data: unknown, _args: Record<string, unknown>): Promise<unknown>;
|
|
9
|
+
export declare function stepLimit(_page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pipeline steps: data transforms — select, map, filter, sort, limit.
|
|
3
|
+
*/
|
|
4
|
+
import { render, evalExpr } from '../template.js';
|
|
5
|
+
import { isRecord } from '../../utils.js';
|
|
6
|
+
export async function stepSelect(_page, params, data, args) {
|
|
7
|
+
const pathStr = String(render(params, { args, data }));
|
|
8
|
+
if (data && typeof data === 'object') {
|
|
9
|
+
let current = data;
|
|
10
|
+
for (const part of pathStr.split('.')) {
|
|
11
|
+
if (isRecord(current))
|
|
12
|
+
current = current[part];
|
|
13
|
+
else if (Array.isArray(current) && /^\d+$/.test(part))
|
|
14
|
+
current = current[parseInt(part, 10)];
|
|
15
|
+
else
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
return current;
|
|
19
|
+
}
|
|
20
|
+
return data;
|
|
21
|
+
}
|
|
22
|
+
export async function stepMap(_page, params, data, args) {
|
|
23
|
+
if (!data || typeof data !== 'object')
|
|
24
|
+
return data;
|
|
25
|
+
let source = data;
|
|
26
|
+
// Support inline select: { map: { select: 'path', key: '${{ item.x }}' } }
|
|
27
|
+
if (isRecord(params) && 'select' in params) {
|
|
28
|
+
source = await stepSelect(null, params.select, data, args);
|
|
29
|
+
}
|
|
30
|
+
if (!source || typeof source !== 'object')
|
|
31
|
+
return source;
|
|
32
|
+
let items = Array.isArray(source) ? source : [source];
|
|
33
|
+
if (isRecord(source) && Array.isArray(source.data))
|
|
34
|
+
items = source.data;
|
|
35
|
+
const result = [];
|
|
36
|
+
const templateParams = isRecord(params) ? params : {};
|
|
37
|
+
for (let i = 0; i < items.length; i++) {
|
|
38
|
+
const item = items[i];
|
|
39
|
+
const row = {};
|
|
40
|
+
for (const [key, template] of Object.entries(templateParams)) {
|
|
41
|
+
if (key === 'select')
|
|
42
|
+
continue;
|
|
43
|
+
row[key] = render(template, { args, data: source, root: data, item, index: i });
|
|
44
|
+
}
|
|
45
|
+
result.push(row);
|
|
46
|
+
}
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
export async function stepFilter(_page, params, data, args) {
|
|
50
|
+
if (!Array.isArray(data))
|
|
51
|
+
return data;
|
|
52
|
+
return data.filter((item, i) => evalExpr(String(params), { args, item, index: i }));
|
|
53
|
+
}
|
|
54
|
+
export async function stepSort(_page, params, data, _args) {
|
|
55
|
+
if (!Array.isArray(data))
|
|
56
|
+
return data;
|
|
57
|
+
const key = isRecord(params) ? String(params.by ?? '') : String(params);
|
|
58
|
+
const reverse = isRecord(params) ? params.order === 'desc' : false;
|
|
59
|
+
return [...data].sort((a, b) => {
|
|
60
|
+
const left = isRecord(a) ? a[key] : undefined;
|
|
61
|
+
const right = isRecord(b) ? b[key] : undefined;
|
|
62
|
+
const cmp = String(left ?? '').localeCompare(String(right ?? ''), undefined, { numeric: true });
|
|
63
|
+
return reverse ? -cmp : cmp;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
export async function stepLimit(_page, params, data, args) {
|
|
67
|
+
if (!Array.isArray(data))
|
|
68
|
+
return data;
|
|
69
|
+
return data.slice(0, Number(render(params, { args, data })));
|
|
70
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pipeline template engine: ${{ ... }} expression rendering.
|
|
3
|
+
*/
|
|
4
|
+
export interface RenderContext {
|
|
5
|
+
args?: Record<string, unknown>;
|
|
6
|
+
data?: unknown;
|
|
7
|
+
root?: unknown;
|
|
8
|
+
item?: unknown;
|
|
9
|
+
index?: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function render(template: unknown, ctx: RenderContext): unknown;
|
|
12
|
+
export declare function evalExpr(expr: string, ctx: RenderContext): unknown;
|
|
13
|
+
export declare function resolvePath(pathStr: string, ctx: RenderContext): unknown;
|
|
14
|
+
/**
|
|
15
|
+
* Normalize JavaScript source for browser evaluate() calls.
|
|
16
|
+
*/
|
|
17
|
+
export declare function normalizeEvaluateSource(source: string): string;
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pipeline template engine: ${{ ... }} expression rendering.
|
|
3
|
+
*/
|
|
4
|
+
import vm from 'node:vm';
|
|
5
|
+
import { isRecord } from '../utils.js';
|
|
6
|
+
import { log } from '../logger.js';
|
|
7
|
+
export function render(template, ctx) {
|
|
8
|
+
if (typeof template !== 'string')
|
|
9
|
+
return template;
|
|
10
|
+
const trimmed = template.trim();
|
|
11
|
+
// Full expression: entire string is a single ${{ ... }}
|
|
12
|
+
// Use [^}] to prevent matching across }} boundaries (e.g. "${{ a }}-${{ b }}")
|
|
13
|
+
const fullMatch = trimmed.match(/^\$\{\{\s*([^}]*(?:\}[^}][^}]*)*)\s*\}\}$/);
|
|
14
|
+
if (fullMatch && !trimmed.includes('}}-') && !trimmed.includes('}}${{'))
|
|
15
|
+
return evalExpr(fullMatch[1].trim(), ctx);
|
|
16
|
+
// Check if the entire string is a single expression (no other text around it)
|
|
17
|
+
const singleExpr = trimmed.match(/^\$\{\{\s*([\s\S]*?)\s*\}\}$/);
|
|
18
|
+
if (singleExpr) {
|
|
19
|
+
// Verify it's truly a single expression (no other ${{ inside)
|
|
20
|
+
const inner = singleExpr[1];
|
|
21
|
+
if (!inner.includes('${{'))
|
|
22
|
+
return evalExpr(inner.trim(), ctx);
|
|
23
|
+
}
|
|
24
|
+
return template.replace(/\$\{\{\s*(.*?)\s*\}\}/g, (_m, expr) => String(evalExpr(expr.trim(), ctx)));
|
|
25
|
+
}
|
|
26
|
+
export function evalExpr(expr, ctx) {
|
|
27
|
+
const args = ctx.args ?? {};
|
|
28
|
+
const item = ctx.item ?? {};
|
|
29
|
+
const data = ctx.data;
|
|
30
|
+
const root = ctx.root;
|
|
31
|
+
const index = ctx.index ?? 0;
|
|
32
|
+
// ── Pipe filters: expr | filter1(arg) | filter2 ──
|
|
33
|
+
// Split on single | (not ||) so "item.a || item.b | upper" works correctly.
|
|
34
|
+
const pipeSegments = expr.split(/(?<!\|)\|(?!\|)/).map(s => s.trim());
|
|
35
|
+
if (pipeSegments.length > 1) {
|
|
36
|
+
let result = evalExpr(pipeSegments[0], ctx);
|
|
37
|
+
for (let i = 1; i < pipeSegments.length; i++) {
|
|
38
|
+
result = applyFilter(pipeSegments[i], result);
|
|
39
|
+
}
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
// Fast path: quoted string literal — skip VM overhead
|
|
43
|
+
const strLit = expr.match(/^(['"])(.*)\1$/);
|
|
44
|
+
if (strLit)
|
|
45
|
+
return strLit[2];
|
|
46
|
+
// Fast path: numeric literal
|
|
47
|
+
if (/^\d+(\.\d+)?$/.test(expr))
|
|
48
|
+
return Number(expr);
|
|
49
|
+
// Try resolving as a simple dotted path (item.foo.bar, args.limit, index)
|
|
50
|
+
const resolved = resolvePath(expr, { args, item, data, root, index });
|
|
51
|
+
if (resolved !== null && resolved !== undefined)
|
|
52
|
+
return resolved;
|
|
53
|
+
// Fallback: evaluate as JS in a sandboxed VM.
|
|
54
|
+
// Handles ||, ??, arithmetic, ternary, method calls, etc. natively.
|
|
55
|
+
return evalJsExpr(expr, { args, item, data, root, index });
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Apply a named filter to a value.
|
|
59
|
+
* Supported filters:
|
|
60
|
+
* default(val), join(sep), upper, lower, truncate(n), trim,
|
|
61
|
+
* replace(old,new), keys, length, first, last, json
|
|
62
|
+
*/
|
|
63
|
+
function applyFilter(filterExpr, value) {
|
|
64
|
+
const match = filterExpr.match(/^(\w+)(?:\((.+)\))?$/);
|
|
65
|
+
if (!match)
|
|
66
|
+
return value;
|
|
67
|
+
const [, name, rawArgs] = match;
|
|
68
|
+
const filterArg = rawArgs?.replace(/^['"]|['"]$/g, '') ?? '';
|
|
69
|
+
switch (name) {
|
|
70
|
+
case 'default': {
|
|
71
|
+
if (value === null || value === undefined || value === '') {
|
|
72
|
+
const intVal = parseInt(filterArg, 10);
|
|
73
|
+
if (!Number.isNaN(intVal) && String(intVal) === filterArg.trim())
|
|
74
|
+
return intVal;
|
|
75
|
+
return filterArg;
|
|
76
|
+
}
|
|
77
|
+
return value;
|
|
78
|
+
}
|
|
79
|
+
case 'join':
|
|
80
|
+
return Array.isArray(value) ? value.join(filterArg || ', ') : value;
|
|
81
|
+
case 'upper':
|
|
82
|
+
return typeof value === 'string' ? value.toUpperCase() : value;
|
|
83
|
+
case 'lower':
|
|
84
|
+
return typeof value === 'string' ? value.toLowerCase() : value;
|
|
85
|
+
case 'trim':
|
|
86
|
+
return typeof value === 'string' ? value.trim() : value;
|
|
87
|
+
case 'truncate': {
|
|
88
|
+
const n = parseInt(filterArg, 10) || 50;
|
|
89
|
+
return typeof value === 'string' && value.length > n ? `${value.slice(0, n)}...` : value;
|
|
90
|
+
}
|
|
91
|
+
case 'replace': {
|
|
92
|
+
if (typeof value !== 'string')
|
|
93
|
+
return value;
|
|
94
|
+
const parts = rawArgs?.split(',').map(s => s.trim().replace(/^['"]|['"]$/g, '')) ?? [];
|
|
95
|
+
return parts.length >= 2 ? value.replaceAll(parts[0], parts[1]) : value;
|
|
96
|
+
}
|
|
97
|
+
case 'keys':
|
|
98
|
+
return value && typeof value === 'object' ? Object.keys(value) : value;
|
|
99
|
+
case 'length':
|
|
100
|
+
return Array.isArray(value) ? value.length : typeof value === 'string' ? value.length : value;
|
|
101
|
+
case 'first':
|
|
102
|
+
return Array.isArray(value) ? value[0] : value;
|
|
103
|
+
case 'last':
|
|
104
|
+
return Array.isArray(value) ? value[value.length - 1] : value;
|
|
105
|
+
case 'json':
|
|
106
|
+
return JSON.stringify(value ?? null);
|
|
107
|
+
case 'slugify':
|
|
108
|
+
// Convert to URL-safe slug
|
|
109
|
+
return typeof value === 'string'
|
|
110
|
+
? value
|
|
111
|
+
.toLowerCase()
|
|
112
|
+
.replace(/[^\p{L}\p{N}]+/gu, '-')
|
|
113
|
+
.replace(/^-|-$/g, '')
|
|
114
|
+
: value;
|
|
115
|
+
case 'sanitize':
|
|
116
|
+
// Remove invalid filename characters
|
|
117
|
+
return typeof value === 'string'
|
|
118
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: intentional - strips C0 control chars from filenames
|
|
119
|
+
? value.replace(/[<>:"/\\|?*\x00-\x1f]/g, '_')
|
|
120
|
+
: value;
|
|
121
|
+
case 'ext': {
|
|
122
|
+
// Extract file extension from URL or path
|
|
123
|
+
if (typeof value !== 'string')
|
|
124
|
+
return value;
|
|
125
|
+
const lastDot = value.lastIndexOf('.');
|
|
126
|
+
const lastSlash = Math.max(value.lastIndexOf('/'), value.lastIndexOf('\\'));
|
|
127
|
+
return lastDot > lastSlash ? value.slice(lastDot) : '';
|
|
128
|
+
}
|
|
129
|
+
case 'basename': {
|
|
130
|
+
// Extract filename from URL or path
|
|
131
|
+
if (typeof value !== 'string')
|
|
132
|
+
return value;
|
|
133
|
+
const parts = value.split(/[/\\]/);
|
|
134
|
+
return parts[parts.length - 1] || value;
|
|
135
|
+
}
|
|
136
|
+
case 'urlencode':
|
|
137
|
+
return typeof value === 'string' ? encodeURIComponent(value) : value;
|
|
138
|
+
case 'urldecode':
|
|
139
|
+
return typeof value === 'string' ? decodeURIComponent(value) : value;
|
|
140
|
+
default:
|
|
141
|
+
return value;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
export function resolvePath(pathStr, ctx) {
|
|
145
|
+
const args = ctx.args ?? {};
|
|
146
|
+
const item = ctx.item ?? {};
|
|
147
|
+
const data = ctx.data;
|
|
148
|
+
const root = ctx.root;
|
|
149
|
+
const index = ctx.index ?? 0;
|
|
150
|
+
const parts = pathStr.split('.');
|
|
151
|
+
const rootName = parts[0];
|
|
152
|
+
let obj;
|
|
153
|
+
let rest;
|
|
154
|
+
if (rootName === 'args') {
|
|
155
|
+
obj = args;
|
|
156
|
+
rest = parts.slice(1);
|
|
157
|
+
}
|
|
158
|
+
else if (rootName === 'item') {
|
|
159
|
+
obj = item;
|
|
160
|
+
rest = parts.slice(1);
|
|
161
|
+
}
|
|
162
|
+
else if (rootName === 'data') {
|
|
163
|
+
obj = data;
|
|
164
|
+
rest = parts.slice(1);
|
|
165
|
+
}
|
|
166
|
+
else if (rootName === 'root') {
|
|
167
|
+
obj = root;
|
|
168
|
+
rest = parts.slice(1);
|
|
169
|
+
}
|
|
170
|
+
else if (rootName === 'index')
|
|
171
|
+
return index;
|
|
172
|
+
else {
|
|
173
|
+
obj = item;
|
|
174
|
+
rest = parts;
|
|
175
|
+
}
|
|
176
|
+
for (const part of rest) {
|
|
177
|
+
if (isRecord(obj))
|
|
178
|
+
obj = obj[part];
|
|
179
|
+
else if (Array.isArray(obj) && /^\d+$/.test(part))
|
|
180
|
+
obj = obj[parseInt(part, 10)];
|
|
181
|
+
else
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
return obj;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Evaluate arbitrary JS expressions as a last-resort fallback.
|
|
188
|
+
* Runs inside a `node:vm` sandbox with dynamic code generation disabled.
|
|
189
|
+
*
|
|
190
|
+
* Compiled functions are cached by expression string to avoid re-creating
|
|
191
|
+
* VM contexts on every invocation — critical for loops where the same
|
|
192
|
+
* expression is evaluated hundreds of times.
|
|
193
|
+
*/
|
|
194
|
+
const FORBIDDEN_EXPR_PATTERNS = /\b(constructor|__proto__|prototype|globalThis|process|require|import|eval)\b/;
|
|
195
|
+
/**
|
|
196
|
+
* Deep-copy plain data to sever prototype chains, preventing sandbox escape
|
|
197
|
+
* via `args.constructor.constructor('return process')()` etc.
|
|
198
|
+
*
|
|
199
|
+
* Uses a WeakMap cache keyed by object reference: when the same object
|
|
200
|
+
* (e.g. `args` or `data`) is passed repeatedly across loop iterations,
|
|
201
|
+
* the expensive JSON round-trip is performed only once. The WeakMap
|
|
202
|
+
* lets entries be GC'd when the source object is no longer referenced.
|
|
203
|
+
*/
|
|
204
|
+
/**
|
|
205
|
+
* Cache serialized JSON strings (not parsed objects) by source reference.
|
|
206
|
+
* Caching the parsed object would be unsafe: the VM sandbox could mutate it,
|
|
207
|
+
* and the polluted version would leak to subsequent calls. By caching the
|
|
208
|
+
* string and returning a fresh JSON.parse() each time, every evaluation gets
|
|
209
|
+
* its own clean deep-copy while still avoiding redundant JSON.stringify()
|
|
210
|
+
* for the same unchanged source object across loop iterations.
|
|
211
|
+
*/
|
|
212
|
+
const _sanitizeCache = new WeakMap();
|
|
213
|
+
function sanitizeContext(obj) {
|
|
214
|
+
if (obj === null || obj === undefined)
|
|
215
|
+
return obj;
|
|
216
|
+
if (typeof obj !== 'object' && typeof obj !== 'function')
|
|
217
|
+
return obj;
|
|
218
|
+
const objRef = obj;
|
|
219
|
+
const cached = _sanitizeCache.get(objRef);
|
|
220
|
+
if (cached !== undefined)
|
|
221
|
+
return JSON.parse(cached);
|
|
222
|
+
try {
|
|
223
|
+
// BigInt is non-serializable by default but is the most common cause of
|
|
224
|
+
// sanitizeContext failures (e.g. GraphQL 64-bit IDs). Coerce to string
|
|
225
|
+
// so callers see the value instead of a silent {}.
|
|
226
|
+
const jsonStr = JSON.stringify(obj, (_key, value) => typeof value === 'bigint' ? value.toString() : value);
|
|
227
|
+
_sanitizeCache.set(objRef, jsonStr);
|
|
228
|
+
return JSON.parse(jsonStr);
|
|
229
|
+
}
|
|
230
|
+
catch (err) {
|
|
231
|
+
log.warn(`[pipeline/template] sanitizeContext failed: ${err instanceof Error ? err.message : String(err)}. ` +
|
|
232
|
+
`Returning {} for this branch. Likely cause: circular reference, Symbol, or other non-serializable value in pipeline context.`);
|
|
233
|
+
return {};
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/** LRU-bounded cache for compiled VM scripts — prevents unbounded memory growth. */
|
|
237
|
+
const MAX_VM_CACHE_SIZE = 256;
|
|
238
|
+
const _vmCache = new Map();
|
|
239
|
+
function getOrCompileScript(expr) {
|
|
240
|
+
let script = _vmCache.get(expr);
|
|
241
|
+
if (script)
|
|
242
|
+
return script;
|
|
243
|
+
// Evict oldest entry when cache is full
|
|
244
|
+
if (_vmCache.size >= MAX_VM_CACHE_SIZE) {
|
|
245
|
+
const firstKey = _vmCache.keys().next().value;
|
|
246
|
+
if (firstKey !== undefined)
|
|
247
|
+
_vmCache.delete(firstKey);
|
|
248
|
+
}
|
|
249
|
+
script = new vm.Script(`(${expr})`);
|
|
250
|
+
_vmCache.set(expr, script);
|
|
251
|
+
return script;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Reusable VM sandbox context.
|
|
255
|
+
*
|
|
256
|
+
* vm.createContext() is expensive (~0.3ms per call) because it creates a new
|
|
257
|
+
* V8 context with its own global object. In pipeline loops (map/filter over
|
|
258
|
+
* hundreds of items), this adds up to significant overhead.
|
|
259
|
+
*
|
|
260
|
+
* Instead, we create the context once and mutate the sandbox properties
|
|
261
|
+
* before each evaluation. This is safe because:
|
|
262
|
+
* 1. Sandbox properties are sanitized (deep-copied) before assignment
|
|
263
|
+
* 2. Scripts run with a 50ms timeout
|
|
264
|
+
* 3. codeGeneration is disabled (no eval/Function inside the sandbox)
|
|
265
|
+
*/
|
|
266
|
+
let _reusableSandbox = null;
|
|
267
|
+
let _reusableContext = null;
|
|
268
|
+
function getReusableContext() {
|
|
269
|
+
if (_reusableSandbox && _reusableContext) {
|
|
270
|
+
return { sandbox: _reusableSandbox, context: _reusableContext };
|
|
271
|
+
}
|
|
272
|
+
_reusableSandbox = {
|
|
273
|
+
args: {},
|
|
274
|
+
item: {},
|
|
275
|
+
data: null,
|
|
276
|
+
root: null,
|
|
277
|
+
index: 0,
|
|
278
|
+
encodeURIComponent,
|
|
279
|
+
decodeURIComponent,
|
|
280
|
+
JSON,
|
|
281
|
+
Math,
|
|
282
|
+
Number,
|
|
283
|
+
String,
|
|
284
|
+
Boolean,
|
|
285
|
+
Array,
|
|
286
|
+
Date,
|
|
287
|
+
};
|
|
288
|
+
_reusableContext = vm.createContext(_reusableSandbox, {
|
|
289
|
+
codeGeneration: { strings: false, wasm: false },
|
|
290
|
+
});
|
|
291
|
+
return { sandbox: _reusableSandbox, context: _reusableContext };
|
|
292
|
+
}
|
|
293
|
+
/** Properties that are part of the sandbox's initial shape and safe to keep. */
|
|
294
|
+
const SANDBOX_WHITELIST = new Set([
|
|
295
|
+
'args', 'item', 'data', 'root', 'index',
|
|
296
|
+
'encodeURIComponent', 'decodeURIComponent',
|
|
297
|
+
'JSON', 'Math', 'Number', 'String', 'Boolean', 'Array', 'Date',
|
|
298
|
+
]);
|
|
299
|
+
function evalJsExpr(expr, ctx) {
|
|
300
|
+
// Guard against absurdly long expressions that could indicate injection.
|
|
301
|
+
if (expr.length > 2000)
|
|
302
|
+
return undefined;
|
|
303
|
+
// Block obvious sandbox escape attempts.
|
|
304
|
+
if (FORBIDDEN_EXPR_PATTERNS.test(expr))
|
|
305
|
+
return undefined;
|
|
306
|
+
try {
|
|
307
|
+
const script = getOrCompileScript(expr);
|
|
308
|
+
const { sandbox, context } = getReusableContext();
|
|
309
|
+
// Clean non-whitelisted properties that a previous script may have added.
|
|
310
|
+
// Without this, `${{ x = 42 }}` would leak `x` into subsequent evaluations.
|
|
311
|
+
for (const key of Object.keys(sandbox)) {
|
|
312
|
+
if (!SANDBOX_WHITELIST.has(key)) {
|
|
313
|
+
delete sandbox[key];
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
// Update mutable sandbox properties — sanitizeContext severs prototype chains.
|
|
317
|
+
sandbox.args = sanitizeContext(ctx.args ?? {});
|
|
318
|
+
sandbox.item = sanitizeContext(ctx.item ?? {});
|
|
319
|
+
sandbox.data = sanitizeContext(ctx.data);
|
|
320
|
+
sandbox.root = sanitizeContext(ctx.root);
|
|
321
|
+
sandbox.index = ctx.index ?? 0;
|
|
322
|
+
return script.runInContext(context, { timeout: 50 });
|
|
323
|
+
}
|
|
324
|
+
catch {
|
|
325
|
+
return undefined;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Normalize JavaScript source for browser evaluate() calls.
|
|
330
|
+
*/
|
|
331
|
+
export function normalizeEvaluateSource(source) {
|
|
332
|
+
const stripped = source.trim();
|
|
333
|
+
if (!stripped)
|
|
334
|
+
return '() => undefined';
|
|
335
|
+
if (stripped.startsWith('(') && stripped.endsWith(')()'))
|
|
336
|
+
return `() => (${stripped})`;
|
|
337
|
+
if (/^(async\s+)?\([^)]*\)\s*=>/.test(stripped))
|
|
338
|
+
return stripped;
|
|
339
|
+
if (/^(async\s+)?[A-Za-z_][A-Za-z0-9_]*\s*=>/.test(stripped))
|
|
340
|
+
return stripped;
|
|
341
|
+
if (stripped.startsWith('function ') || stripped.startsWith('async function '))
|
|
342
|
+
return stripped;
|
|
343
|
+
return `() => (${stripped})`;
|
|
344
|
+
}
|