@pi-harness/pi-harness 0.1.21 → 0.1.23
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 +1 -1
- package/apps/web/dist/assets/index-Bo9CNI_U.js +22 -0
- package/apps/web/dist/index.html +1 -1
- package/apps/web/package.json +6 -6
- package/apps/web/server-dist/bin.js +18 -5
- package/apps/web/server-dist/bin.js.map +1 -1
- package/apps/web/src/server/bin.ts +18 -5
- package/apps/web/test/bin.test.ts +90 -0
- package/node_modules/@pi-harness/api-gateway/dist/index.d.ts.map +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/index.js +204 -66
- package/node_modules/@pi-harness/api-gateway/dist/index.js.map +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/agent-teams.json +3 -12
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/anchored-standard.json +3 -10
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/annotation.json +3 -8
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/archify.json +3 -12
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/at-file.json +2 -6
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/auto-mode.json +3 -10
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/better-sidebar.json +3 -13
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/browser-fetch.json +2 -5
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/browser-session.json +2 -6
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/canvas-draw.json +3 -9
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/change-verifier.json +3 -11
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/cleaner.json +2 -6
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/cli-notifier.json +3 -11
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/code2skill.json +3 -10
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/colleague-skill.json +3 -10
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/context-doctor.json +2 -8
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/context.json +2 -7
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/cost-meter.json +3 -12
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/dependency-checker.json +2 -6
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/docker-sandbox.json +2 -6
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/fail-logger.json +2 -7
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/genui.json +3 -11
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/git-time-capsule.json +2 -6
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/graph-memory.json +3 -12
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/history-compressor.json +3 -11
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/hol-guard.json +4 -11
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/i18n-pair.json +2 -6
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/image-compressor.json +3 -10
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/llm-verifier.json +3 -13
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/mcp-client.json +2 -6
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/mcp-panel.json +3 -10
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/memory.json +3 -9
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/mirage-bridge.json +3 -11
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/mock-server.json +3 -10
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/modlens.json +3 -9
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/module-search.json +3 -11
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/obsidian-sync.json +3 -10
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/openpets.json +2 -7
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/plan-execute.json +3 -10
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/plugin-check.json +3 -10
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/plugin-dev.json +3 -12
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/plugin-finder.json +3 -11
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/plugin-radar.json +3 -9
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/plugin-stars.json +2 -6
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/prompt-guard.json +3 -9
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/prompt-library.json +3 -11
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/readme-gen.json +2 -7
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/recall-unread.json +2 -7
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/reverse-skill.json +3 -11
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/reviewer-bot.json +3 -10
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/runtime-doctor.json +3 -13
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/secure-audit.json +3 -11
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/session-bookmarks.json +3 -11
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/session-bridge.json +2 -7
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/session-compare.json +3 -11
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/session-export.json +3 -11
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/session-insights.json +2 -7
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/session-search.json +3 -11
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/skill-catalog.json +3 -12
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/skill-guard.json +2 -6
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/sql-lens.json +2 -6
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/synapse.json +3 -9
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/tab-manager.json +3 -10
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/taskboard.json +3 -10
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/telemetry-blocker.json +3 -10
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/test-harness.json +2 -6
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/theme-studio.json +3 -11
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/token-guard.json +2 -7
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/turn-rewind.json +2 -7
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/undo-savepoint.json +3 -9
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/vision-toolkit.json +2 -6
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/web-research.json +3 -10
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/workspace-navigator.json +3 -13
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/workspace-search.json +3 -10
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/yaml-validator.json +2 -6
- package/node_modules/@pi-harness/api-gateway/package.json +1 -1
- package/node_modules/@pi-harness/cli/dist/node-stdio.d.ts.map +1 -1
- package/node_modules/@pi-harness/cli/dist/node-stdio.js +3 -1
- package/node_modules/@pi-harness/cli/dist/node-stdio.js.map +1 -1
- package/node_modules/@pi-harness/cli/dist/relaunch.d.ts.map +1 -1
- package/node_modules/@pi-harness/cli/dist/relaunch.js +6 -1
- package/node_modules/@pi-harness/cli/dist/relaunch.js.map +1 -1
- package/node_modules/@pi-harness/cli/package.json +1 -1
- package/node_modules/@pi-harness/host-webserver/dist/index.d.ts.map +1 -1
- package/node_modules/@pi-harness/host-webserver/dist/index.js +52 -3
- package/node_modules/@pi-harness/host-webserver/dist/index.js.map +1 -1
- package/node_modules/@pi-harness/host-webserver/package.json +1 -1
- package/node_modules/@pi-harness/web-app/dist/index.d.ts +3 -0
- package/node_modules/@pi-harness/web-app/dist/index.d.ts.map +1 -1
- package/node_modules/@pi-harness/web-app/dist/index.js +9 -5
- package/node_modules/@pi-harness/web-app/dist/index.js.map +1 -1
- package/node_modules/@pi-harness/web-app/package.json +1 -1
- package/package.json +7 -7
- package/packages/api-gateway/dist/index.d.ts.map +1 -1
- package/packages/api-gateway/dist/index.js +204 -66
- package/packages/api-gateway/dist/index.js.map +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/agent-teams.json +3 -12
- package/packages/api-gateway/dist/marketplace-entries/official/anchored-standard.json +3 -10
- package/packages/api-gateway/dist/marketplace-entries/official/annotation.json +3 -8
- package/packages/api-gateway/dist/marketplace-entries/official/archify.json +3 -12
- package/packages/api-gateway/dist/marketplace-entries/official/at-file.json +2 -6
- package/packages/api-gateway/dist/marketplace-entries/official/auto-mode.json +3 -10
- package/packages/api-gateway/dist/marketplace-entries/official/better-sidebar.json +3 -13
- package/packages/api-gateway/dist/marketplace-entries/official/browser-fetch.json +2 -5
- package/packages/api-gateway/dist/marketplace-entries/official/browser-session.json +2 -6
- package/packages/api-gateway/dist/marketplace-entries/official/canvas-draw.json +3 -9
- package/packages/api-gateway/dist/marketplace-entries/official/change-verifier.json +3 -11
- package/packages/api-gateway/dist/marketplace-entries/official/cleaner.json +2 -6
- package/packages/api-gateway/dist/marketplace-entries/official/cli-notifier.json +3 -11
- package/packages/api-gateway/dist/marketplace-entries/official/code2skill.json +3 -10
- package/packages/api-gateway/dist/marketplace-entries/official/colleague-skill.json +3 -10
- package/packages/api-gateway/dist/marketplace-entries/official/context-doctor.json +2 -8
- package/packages/api-gateway/dist/marketplace-entries/official/context.json +2 -7
- package/packages/api-gateway/dist/marketplace-entries/official/cost-meter.json +3 -12
- package/packages/api-gateway/dist/marketplace-entries/official/dependency-checker.json +2 -6
- package/packages/api-gateway/dist/marketplace-entries/official/docker-sandbox.json +2 -6
- package/packages/api-gateway/dist/marketplace-entries/official/fail-logger.json +2 -7
- package/packages/api-gateway/dist/marketplace-entries/official/genui.json +3 -11
- package/packages/api-gateway/dist/marketplace-entries/official/git-time-capsule.json +2 -6
- package/packages/api-gateway/dist/marketplace-entries/official/graph-memory.json +3 -12
- package/packages/api-gateway/dist/marketplace-entries/official/history-compressor.json +3 -11
- package/packages/api-gateway/dist/marketplace-entries/official/hol-guard.json +4 -11
- package/packages/api-gateway/dist/marketplace-entries/official/i18n-pair.json +2 -6
- package/packages/api-gateway/dist/marketplace-entries/official/image-compressor.json +3 -10
- package/packages/api-gateway/dist/marketplace-entries/official/llm-verifier.json +3 -13
- package/packages/api-gateway/dist/marketplace-entries/official/mcp-client.json +2 -6
- package/packages/api-gateway/dist/marketplace-entries/official/mcp-panel.json +3 -10
- package/packages/api-gateway/dist/marketplace-entries/official/memory.json +3 -9
- package/packages/api-gateway/dist/marketplace-entries/official/mirage-bridge.json +3 -11
- package/packages/api-gateway/dist/marketplace-entries/official/mock-server.json +3 -10
- package/packages/api-gateway/dist/marketplace-entries/official/modlens.json +3 -9
- package/packages/api-gateway/dist/marketplace-entries/official/module-search.json +3 -11
- package/packages/api-gateway/dist/marketplace-entries/official/obsidian-sync.json +3 -10
- package/packages/api-gateway/dist/marketplace-entries/official/openpets.json +2 -7
- package/packages/api-gateway/dist/marketplace-entries/official/plan-execute.json +3 -10
- package/packages/api-gateway/dist/marketplace-entries/official/plugin-check.json +3 -10
- package/packages/api-gateway/dist/marketplace-entries/official/plugin-dev.json +3 -12
- package/packages/api-gateway/dist/marketplace-entries/official/plugin-finder.json +3 -11
- package/packages/api-gateway/dist/marketplace-entries/official/plugin-radar.json +3 -9
- package/packages/api-gateway/dist/marketplace-entries/official/plugin-stars.json +2 -6
- package/packages/api-gateway/dist/marketplace-entries/official/prompt-guard.json +3 -9
- package/packages/api-gateway/dist/marketplace-entries/official/prompt-library.json +3 -11
- package/packages/api-gateway/dist/marketplace-entries/official/readme-gen.json +2 -7
- package/packages/api-gateway/dist/marketplace-entries/official/recall-unread.json +2 -7
- package/packages/api-gateway/dist/marketplace-entries/official/reverse-skill.json +3 -11
- package/packages/api-gateway/dist/marketplace-entries/official/reviewer-bot.json +3 -10
- package/packages/api-gateway/dist/marketplace-entries/official/runtime-doctor.json +3 -13
- package/packages/api-gateway/dist/marketplace-entries/official/secure-audit.json +3 -11
- package/packages/api-gateway/dist/marketplace-entries/official/session-bookmarks.json +3 -11
- package/packages/api-gateway/dist/marketplace-entries/official/session-bridge.json +2 -7
- package/packages/api-gateway/dist/marketplace-entries/official/session-compare.json +3 -11
- package/packages/api-gateway/dist/marketplace-entries/official/session-export.json +3 -11
- package/packages/api-gateway/dist/marketplace-entries/official/session-insights.json +2 -7
- package/packages/api-gateway/dist/marketplace-entries/official/session-search.json +3 -11
- package/packages/api-gateway/dist/marketplace-entries/official/skill-catalog.json +3 -12
- package/packages/api-gateway/dist/marketplace-entries/official/skill-guard.json +2 -6
- package/packages/api-gateway/dist/marketplace-entries/official/sql-lens.json +2 -6
- package/packages/api-gateway/dist/marketplace-entries/official/synapse.json +3 -9
- package/packages/api-gateway/dist/marketplace-entries/official/tab-manager.json +3 -10
- package/packages/api-gateway/dist/marketplace-entries/official/taskboard.json +3 -10
- package/packages/api-gateway/dist/marketplace-entries/official/telemetry-blocker.json +3 -10
- package/packages/api-gateway/dist/marketplace-entries/official/test-harness.json +2 -6
- package/packages/api-gateway/dist/marketplace-entries/official/theme-studio.json +3 -11
- package/packages/api-gateway/dist/marketplace-entries/official/token-guard.json +2 -7
- package/packages/api-gateway/dist/marketplace-entries/official/turn-rewind.json +2 -7
- package/packages/api-gateway/dist/marketplace-entries/official/undo-savepoint.json +3 -9
- package/packages/api-gateway/dist/marketplace-entries/official/vision-toolkit.json +2 -6
- package/packages/api-gateway/dist/marketplace-entries/official/web-research.json +3 -10
- package/packages/api-gateway/dist/marketplace-entries/official/workspace-navigator.json +3 -13
- package/packages/api-gateway/dist/marketplace-entries/official/workspace-search.json +3 -10
- package/packages/api-gateway/dist/marketplace-entries/official/yaml-validator.json +2 -6
- package/packages/api-gateway/package.json +1 -1
- package/packages/api-gateway/src/index.ts +227 -68
- package/packages/api-gateway/src/marketplace-entries/official/agent-teams.json +3 -12
- package/packages/api-gateway/src/marketplace-entries/official/anchored-standard.json +3 -10
- package/packages/api-gateway/src/marketplace-entries/official/annotation.json +3 -8
- package/packages/api-gateway/src/marketplace-entries/official/archify.json +3 -12
- package/packages/api-gateway/src/marketplace-entries/official/at-file.json +2 -6
- package/packages/api-gateway/src/marketplace-entries/official/auto-mode.json +3 -10
- package/packages/api-gateway/src/marketplace-entries/official/better-sidebar.json +3 -13
- package/packages/api-gateway/src/marketplace-entries/official/browser-fetch.json +2 -5
- package/packages/api-gateway/src/marketplace-entries/official/browser-session.json +2 -6
- package/packages/api-gateway/src/marketplace-entries/official/canvas-draw.json +3 -9
- package/packages/api-gateway/src/marketplace-entries/official/change-verifier.json +3 -11
- package/packages/api-gateway/src/marketplace-entries/official/cleaner.json +2 -6
- package/packages/api-gateway/src/marketplace-entries/official/cli-notifier.json +3 -11
- package/packages/api-gateway/src/marketplace-entries/official/code2skill.json +3 -10
- package/packages/api-gateway/src/marketplace-entries/official/colleague-skill.json +3 -10
- package/packages/api-gateway/src/marketplace-entries/official/context-doctor.json +2 -8
- package/packages/api-gateway/src/marketplace-entries/official/context.json +2 -7
- package/packages/api-gateway/src/marketplace-entries/official/cost-meter.json +3 -12
- package/packages/api-gateway/src/marketplace-entries/official/dependency-checker.json +2 -6
- package/packages/api-gateway/src/marketplace-entries/official/docker-sandbox.json +2 -6
- package/packages/api-gateway/src/marketplace-entries/official/fail-logger.json +2 -7
- package/packages/api-gateway/src/marketplace-entries/official/genui.json +3 -11
- package/packages/api-gateway/src/marketplace-entries/official/git-time-capsule.json +2 -6
- package/packages/api-gateway/src/marketplace-entries/official/graph-memory.json +3 -12
- package/packages/api-gateway/src/marketplace-entries/official/history-compressor.json +3 -11
- package/packages/api-gateway/src/marketplace-entries/official/hol-guard.json +4 -11
- package/packages/api-gateway/src/marketplace-entries/official/i18n-pair.json +2 -6
- package/packages/api-gateway/src/marketplace-entries/official/image-compressor.json +3 -10
- package/packages/api-gateway/src/marketplace-entries/official/llm-verifier.json +3 -13
- package/packages/api-gateway/src/marketplace-entries/official/mcp-client.json +2 -6
- package/packages/api-gateway/src/marketplace-entries/official/mcp-panel.json +3 -10
- package/packages/api-gateway/src/marketplace-entries/official/memory.json +3 -9
- package/packages/api-gateway/src/marketplace-entries/official/mirage-bridge.json +3 -11
- package/packages/api-gateway/src/marketplace-entries/official/mock-server.json +3 -10
- package/packages/api-gateway/src/marketplace-entries/official/modlens.json +3 -9
- package/packages/api-gateway/src/marketplace-entries/official/module-search.json +3 -11
- package/packages/api-gateway/src/marketplace-entries/official/obsidian-sync.json +3 -10
- package/packages/api-gateway/src/marketplace-entries/official/openpets.json +2 -7
- package/packages/api-gateway/src/marketplace-entries/official/plan-execute.json +3 -10
- package/packages/api-gateway/src/marketplace-entries/official/plugin-check.json +3 -10
- package/packages/api-gateway/src/marketplace-entries/official/plugin-dev.json +3 -12
- package/packages/api-gateway/src/marketplace-entries/official/plugin-finder.json +3 -11
- package/packages/api-gateway/src/marketplace-entries/official/plugin-radar.json +3 -9
- package/packages/api-gateway/src/marketplace-entries/official/plugin-stars.json +2 -6
- package/packages/api-gateway/src/marketplace-entries/official/prompt-guard.json +3 -9
- package/packages/api-gateway/src/marketplace-entries/official/prompt-library.json +3 -11
- package/packages/api-gateway/src/marketplace-entries/official/readme-gen.json +2 -7
- package/packages/api-gateway/src/marketplace-entries/official/recall-unread.json +2 -7
- package/packages/api-gateway/src/marketplace-entries/official/reverse-skill.json +3 -11
- package/packages/api-gateway/src/marketplace-entries/official/reviewer-bot.json +3 -10
- package/packages/api-gateway/src/marketplace-entries/official/runtime-doctor.json +3 -13
- package/packages/api-gateway/src/marketplace-entries/official/secure-audit.json +3 -11
- package/packages/api-gateway/src/marketplace-entries/official/session-bookmarks.json +3 -11
- package/packages/api-gateway/src/marketplace-entries/official/session-bridge.json +2 -7
- package/packages/api-gateway/src/marketplace-entries/official/session-compare.json +3 -11
- package/packages/api-gateway/src/marketplace-entries/official/session-export.json +3 -11
- package/packages/api-gateway/src/marketplace-entries/official/session-insights.json +2 -7
- package/packages/api-gateway/src/marketplace-entries/official/session-search.json +3 -11
- package/packages/api-gateway/src/marketplace-entries/official/skill-catalog.json +3 -12
- package/packages/api-gateway/src/marketplace-entries/official/skill-guard.json +2 -6
- package/packages/api-gateway/src/marketplace-entries/official/sql-lens.json +2 -6
- package/packages/api-gateway/src/marketplace-entries/official/synapse.json +3 -9
- package/packages/api-gateway/src/marketplace-entries/official/tab-manager.json +3 -10
- package/packages/api-gateway/src/marketplace-entries/official/taskboard.json +3 -10
- package/packages/api-gateway/src/marketplace-entries/official/telemetry-blocker.json +3 -10
- package/packages/api-gateway/src/marketplace-entries/official/test-harness.json +2 -6
- package/packages/api-gateway/src/marketplace-entries/official/theme-studio.json +3 -11
- package/packages/api-gateway/src/marketplace-entries/official/token-guard.json +2 -7
- package/packages/api-gateway/src/marketplace-entries/official/turn-rewind.json +2 -7
- package/packages/api-gateway/src/marketplace-entries/official/undo-savepoint.json +3 -9
- package/packages/api-gateway/src/marketplace-entries/official/vision-toolkit.json +2 -6
- package/packages/api-gateway/src/marketplace-entries/official/web-research.json +3 -10
- package/packages/api-gateway/src/marketplace-entries/official/workspace-navigator.json +3 -13
- package/packages/api-gateway/src/marketplace-entries/official/workspace-search.json +3 -10
- package/packages/api-gateway/src/marketplace-entries/official/yaml-validator.json +2 -6
- package/packages/api-gateway/test/index.test.ts +363 -1
- package/packages/api-gateway/test/marketplace-schema.test.ts +139 -0
- package/packages/api-gateway/test/release-contract.test.ts +77 -0
- package/packages/api-gateway/test/release-package.test.ts +1 -1
- package/packages/api-gateway/test/release-version.test.ts +6 -4
- package/packages/bundle-web-app/dist/index.d.ts +3 -0
- package/packages/bundle-web-app/dist/index.d.ts.map +1 -1
- package/packages/bundle-web-app/dist/index.js +9 -5
- package/packages/bundle-web-app/dist/index.js.map +1 -1
- package/packages/bundle-web-app/package.json +1 -1
- package/packages/bundle-web-app/src/index.ts +12 -5
- package/packages/bundle-web-app/test/index.test.ts +33 -2
- package/packages/cli/dist/node-stdio.d.ts.map +1 -1
- package/packages/cli/dist/node-stdio.js +3 -1
- package/packages/cli/dist/node-stdio.js.map +1 -1
- package/packages/cli/dist/relaunch.d.ts.map +1 -1
- package/packages/cli/dist/relaunch.js +6 -1
- package/packages/cli/dist/relaunch.js.map +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/cli/src/node-stdio.ts +6 -3
- package/packages/cli/src/relaunch.ts +5 -1
- package/packages/cli/test/node-stdio.test.ts +43 -0
- package/packages/cli/test/relaunch.test.ts +35 -0
- package/packages/host-webserver/dist/index.d.ts.map +1 -1
- package/packages/host-webserver/dist/index.js +52 -3
- package/packages/host-webserver/dist/index.js.map +1 -1
- package/packages/host-webserver/package.json +1 -1
- package/packages/host-webserver/src/index.ts +57 -3
- package/packages/host-webserver/test/index.test.ts +129 -1
- package/apps/web/dist/assets/index-e92yZuUr.js +0 -22
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAA4C,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC;AAExF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAEnD,OAAO,4BAA4B,CAAC;AAEpC,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAeD,KAAK,eAAe,GAAG,IAAI,CAAC,YAAY,EAAE,YAAY,GAAG,UAAU,GAAG,KAAK,CAAC,CAAC;AAI7E,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE,eAA8B,GAAG,OAAO,CAGpH;;;;mBAwBgB,OAAO,UAAU,YAAY;;AAH9C,wBAsBE"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createReadStream, existsSync } from "node:fs";
|
|
2
2
|
import { realpath, stat } from "node:fs/promises";
|
|
3
|
-
import { join, resolve } from "node:path";
|
|
3
|
+
import { isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { pipeline } from "node:stream/promises";
|
|
5
5
|
import "@pi-harness/host-webserver";
|
|
6
6
|
const MIME_TYPES = {
|
|
@@ -14,10 +14,15 @@ function contentType(path) {
|
|
|
14
14
|
const extension = path.slice(path.lastIndexOf(".")).toLowerCase();
|
|
15
15
|
return MIME_TYPES[extension] ?? "application/octet-stream";
|
|
16
16
|
}
|
|
17
|
+
const platformPath = { isAbsolute, relative, sep };
|
|
18
|
+
// Containment is decided with path.relative instead of a "/"-prefixed string compare so backslash-separated Windows paths and other drives are handled; the pathApi parameter exists so tests can exercise path.win32 on any host.
|
|
19
|
+
export function isInsideStaticRoot(root, candidate, pathApi = platformPath) {
|
|
20
|
+
const remainder = pathApi.relative(root, candidate);
|
|
21
|
+
return remainder === "" || (remainder !== ".." && !remainder.startsWith(".." + pathApi.sep) && !pathApi.isAbsolute(remainder));
|
|
22
|
+
}
|
|
17
23
|
function safePath(root, pathname) {
|
|
18
24
|
const candidate = resolve(root, "." + pathname);
|
|
19
|
-
|
|
20
|
-
if (candidate !== root && !candidate.startsWith(rootWithSlash))
|
|
25
|
+
if (!isInsideStaticRoot(root, candidate))
|
|
21
26
|
return undefined;
|
|
22
27
|
return candidate;
|
|
23
28
|
}
|
|
@@ -30,8 +35,7 @@ async function sendFile(path, response) {
|
|
|
30
35
|
}
|
|
31
36
|
async function sendSafeFile(root, path, response) {
|
|
32
37
|
const [rootReal, pathReal] = await Promise.all([realpath(root), realpath(path)]);
|
|
33
|
-
|
|
34
|
-
if (pathReal !== rootReal && !pathReal.startsWith(rootWithSlash))
|
|
38
|
+
if (!isInsideStaticRoot(rootReal, pathReal))
|
|
35
39
|
throw new Error("Static file escapes web root");
|
|
36
40
|
await sendFile(pathReal, response);
|
|
37
41
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createReadStream, existsSync } from "node:fs";
|
|
2
2
|
import { realpath, stat } from "node:fs/promises";
|
|
3
|
-
import { join, resolve } from "node:path";
|
|
3
|
+
import { isAbsolute, join, relative, resolve, sep, type PlatformPath } from "node:path";
|
|
4
4
|
import { pipeline } from "node:stream/promises";
|
|
5
5
|
import type { Context } from "@deepseek-ai/cordis";
|
|
6
6
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
@@ -23,10 +23,18 @@ function contentType(path: string): string {
|
|
|
23
23
|
return MIME_TYPES[extension] ?? "application/octet-stream";
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
type ContainmentPath = Pick<PlatformPath, "isAbsolute" | "relative" | "sep">;
|
|
27
|
+
const platformPath: ContainmentPath = { isAbsolute, relative, sep };
|
|
28
|
+
|
|
29
|
+
// Containment is decided with path.relative instead of a "/"-prefixed string compare so backslash-separated Windows paths and other drives are handled; the pathApi parameter exists so tests can exercise path.win32 on any host.
|
|
30
|
+
export function isInsideStaticRoot(root: string, candidate: string, pathApi: ContainmentPath = platformPath): boolean {
|
|
31
|
+
const remainder = pathApi.relative(root, candidate);
|
|
32
|
+
return remainder === "" || (remainder !== ".." && !remainder.startsWith(".." + pathApi.sep) && !pathApi.isAbsolute(remainder));
|
|
33
|
+
}
|
|
34
|
+
|
|
26
35
|
function safePath(root: string, pathname: string): string | undefined {
|
|
27
36
|
const candidate = resolve(root, "." + pathname);
|
|
28
|
-
|
|
29
|
-
if (candidate !== root && !candidate.startsWith(rootWithSlash)) return undefined;
|
|
37
|
+
if (!isInsideStaticRoot(root, candidate)) return undefined;
|
|
30
38
|
return candidate;
|
|
31
39
|
}
|
|
32
40
|
|
|
@@ -39,8 +47,7 @@ async function sendFile(path: string, response: ServerResponse): Promise<void> {
|
|
|
39
47
|
|
|
40
48
|
async function sendSafeFile(root: string, path: string, response: ServerResponse): Promise<void> {
|
|
41
49
|
const [rootReal, pathReal] = await Promise.all([realpath(root), realpath(path)]);
|
|
42
|
-
|
|
43
|
-
if (pathReal !== rootReal && !pathReal.startsWith(rootWithSlash)) throw new Error("Static file escapes web root");
|
|
50
|
+
if (!isInsideStaticRoot(rootReal, pathReal)) throw new Error("Static file escapes web root");
|
|
44
51
|
await sendFile(pathReal, response);
|
|
45
52
|
}
|
|
46
53
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { mkdtemp, symlink, writeFile } from "node:fs/promises";
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
3
|
+
import { join, posix, win32 } from "node:path";
|
|
4
4
|
import { Context } from "@deepseek-ai/cordis";
|
|
5
5
|
import { afterEach, describe, expect, test } from "vitest";
|
|
6
6
|
import webServerPlugin from "@pi-harness/host-webserver";
|
|
7
|
-
import webAppPlugin from "../src/index.js";
|
|
7
|
+
import webAppPlugin, { isInsideStaticRoot } from "../src/index.js";
|
|
8
8
|
|
|
9
9
|
const contexts: Context[] = [];
|
|
10
10
|
|
|
@@ -27,3 +27,34 @@ describe("web app plugin", () => {
|
|
|
27
27
|
await expect(fetch(context.webServer.url + "/leak").then((response) => response.text())).resolves.toBe("<main>console</main>");
|
|
28
28
|
});
|
|
29
29
|
});
|
|
30
|
+
|
|
31
|
+
describe("static root containment", () => {
|
|
32
|
+
test("accepts files below the root and rejects escapes with Windows separators", () => {
|
|
33
|
+
const root = "C:\\app\\dist";
|
|
34
|
+
expect(isInsideStaticRoot(root, root, win32)).toBe(true);
|
|
35
|
+
expect(isInsideStaticRoot(root, "C:\\app\\dist\\index.html", win32)).toBe(true);
|
|
36
|
+
expect(isInsideStaticRoot(root, "C:\\app\\dist\\assets\\a.js", win32)).toBe(true);
|
|
37
|
+
expect(isInsideStaticRoot(root + "\\", "C:\\app\\dist\\index.html", win32)).toBe(true);
|
|
38
|
+
expect(isInsideStaticRoot(root, "C:\\app\\x", win32)).toBe(false);
|
|
39
|
+
expect(isInsideStaticRoot(root, "C:\\app", win32)).toBe(false);
|
|
40
|
+
expect(isInsideStaticRoot(root, "C:\\app\\dist2\\z", win32)).toBe(false);
|
|
41
|
+
expect(isInsideStaticRoot(root, "D:\\app\\dist\\y", win32)).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("accepts files below the root and rejects escapes with POSIX separators", () => {
|
|
45
|
+
const root = "/srv/app/dist";
|
|
46
|
+
expect(isInsideStaticRoot(root, root, posix)).toBe(true);
|
|
47
|
+
expect(isInsideStaticRoot(root, "/srv/app/dist/index.html", posix)).toBe(true);
|
|
48
|
+
expect(isInsideStaticRoot(root + "/", "/srv/app/dist/assets/a.js", posix)).toBe(true);
|
|
49
|
+
expect(isInsideStaticRoot(root, "/srv/app/x", posix)).toBe(false);
|
|
50
|
+
expect(isInsideStaticRoot(root, "/srv/app", posix)).toBe(false);
|
|
51
|
+
expect(isInsideStaticRoot(root, "/srv/app/dist2/z", posix)).toBe(false);
|
|
52
|
+
expect(isInsideStaticRoot(root, "/etc/hosts", posix)).toBe(false);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("uses the host platform's separators by default", () => {
|
|
56
|
+
const root = join(tmpdir(), "pi-harness-web-root");
|
|
57
|
+
expect(isInsideStaticRoot(root, join(root, "index.html"))).toBe(true);
|
|
58
|
+
expect(isInsideStaticRoot(root, join(root, "..", "escape"))).toBe(false);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-stdio.d.ts","sourceRoot":"","sources":["../src/node-stdio.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAwD,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"node-stdio.d.ts","sourceRoot":"","sources":["../src/node-stdio.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAwD,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAM7G,qBAAa,SAAU,YAAW,cAAc;;gBASlC,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ;IAOxD,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;IA0FnC,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAI/B,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IA6BxB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAqB5B,KAAK,IAAI,IAAI;CAMd"}
|
|
@@ -92,7 +92,9 @@ export class NodeStdio {
|
|
|
92
92
|
rejectOversized(new Error(`Prompt must be at most ${MAX_STDIO_PROMPT_BYTES} UTF-8 bytes`));
|
|
93
93
|
};
|
|
94
94
|
this.#input.on("data", onInputData);
|
|
95
|
-
|
|
95
|
+
// The "> " marker is for a human at a terminal: when stdout is redirected it must not leak into the captured data, so the prompt goes to a TTY stderr instead, or nowhere when neither stream is a terminal.
|
|
96
|
+
const promptOutput = this.#output.isTTY === true ? this.#output : this.#error.isTTY === true ? this.#error : undefined;
|
|
97
|
+
const readline = createInterface({ input: this.#input, output: promptOutput });
|
|
96
98
|
this.#readline = readline;
|
|
97
99
|
readline.on("SIGINT", () => this.close());
|
|
98
100
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-stdio.js","sourceRoot":"","sources":["../src/node-stdio.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAkB,MAAM,wBAAwB,CAAC;AAEzE,OAAO,EAAE,sBAAsB,EAAE,4BAA4B,EAAuB,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"node-stdio.js","sourceRoot":"","sources":["../src/node-stdio.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"relaunch.d.ts","sourceRoot":"","sources":["../src/relaunch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAK9D,eAAO,MAAM,4BAA4B,KAAK,CAAC;AAW/C,wBAAsB,2BAA2B,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"relaunch.d.ts","sourceRoot":"","sources":["../src/relaunch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAK9D,eAAO,MAAM,4BAA4B,KAAK,CAAC;AAW/C,wBAAsB,2BAA2B,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAsCvI;AAED,wBAAgB,mCAAmC,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAQjH"}
|
|
@@ -18,8 +18,13 @@ export async function superviseDevelopmentProcess(command, args, options = {}) {
|
|
|
18
18
|
const result = await new Promise((resolve, reject) => {
|
|
19
19
|
const child = spawn(command, [...args], options);
|
|
20
20
|
const signals = process.platform === "win32" ? ["SIGINT", "SIGTERM"] : ["SIGINT", "SIGTERM", "SIGHUP"];
|
|
21
|
+
// The child inherits stdio and stays in the terminal's foreground process group, so on POSIX it already receives SIGINT and SIGHUP directly; forwarding them would make it count one Ctrl-C as a repeated signal and skip its graceful shutdown. Listeners are still registered for every signal so the supervisor stays alive to collect the child's exit code. SIGTERM is addressed to this pid alone and is the only one relayed.
|
|
22
|
+
const forwarded = new Set(process.platform === "win32" ? ["SIGINT", "SIGTERM"] : ["SIGTERM"]);
|
|
21
23
|
const handlers = signals.map((signal) => {
|
|
22
|
-
const handler = () =>
|
|
24
|
+
const handler = () => {
|
|
25
|
+
if (forwarded.has(signal))
|
|
26
|
+
child.kill(signal);
|
|
27
|
+
};
|
|
23
28
|
process.on(signal, handler);
|
|
24
29
|
return { signal, handler };
|
|
25
30
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"relaunch.js","sourceRoot":"","sources":["../src/relaunch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAqB,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,UAAU,IAAI,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAE9C,MAAM,CAAC,MAAM,4BAA4B,GAAG,EAAE,CAAC;AAC/C,MAAM,iBAAiB,GAAG,MAAM,CAAC;AACjC,MAAM,oBAAoB,GAAG,CAAC,CAAC;AAC/B,MAAM,kBAAkB,GAAG,GAAG,CAAC;AAE/B,SAAS,cAAc,CAAC,MAA6B;IACnD,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,CAAC,CAAC;IAC9B,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACzC,OAAO,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,MAAM,CAAC;AACjD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,2BAA2B,CAAC,OAAe,EAAE,IAAuB,EAAE,UAAwB,EAAE;IACpH,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,OAAO,IAAI,EAAE,CAAC;QACZ,MAAM,MAAM,GAAG,MAAM,IAAI,OAAO,CAAyD,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC3G,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;YACjD,MAAM,OAAO,GAAqB,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;YACzH,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;gBACtC,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"relaunch.js","sourceRoot":"","sources":["../src/relaunch.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,12 +3,13 @@ import type { Readable, Writable } from "node:stream";
|
|
|
3
3
|
import { MAX_STDIO_PROMPT_BYTES, PiHarnessStdioCancelledError, type PiHarnessStdio } from "@pi-harness/core";
|
|
4
4
|
|
|
5
5
|
type TerminalReadable = Readable & { isTTY?: boolean };
|
|
6
|
+
type TerminalWritable = Writable & { isTTY?: boolean };
|
|
6
7
|
type PendingWrite = { readonly promise: Promise<void>; readonly resolve: () => void };
|
|
7
8
|
|
|
8
9
|
export class NodeStdio implements PiHarnessStdio {
|
|
9
10
|
readonly #input: TerminalReadable;
|
|
10
|
-
readonly #output:
|
|
11
|
-
readonly #error:
|
|
11
|
+
readonly #output: TerminalWritable;
|
|
12
|
+
readonly #error: TerminalWritable;
|
|
12
13
|
readonly #abort = new AbortController();
|
|
13
14
|
#readline: Interface | undefined;
|
|
14
15
|
readonly #broken = new WeakSet<Writable>();
|
|
@@ -91,7 +92,9 @@ export class NodeStdio implements PiHarnessStdio {
|
|
|
91
92
|
rejectOversized(new Error(`Prompt must be at most ${MAX_STDIO_PROMPT_BYTES} UTF-8 bytes`));
|
|
92
93
|
};
|
|
93
94
|
this.#input.on("data", onInputData);
|
|
94
|
-
|
|
95
|
+
// The "> " marker is for a human at a terminal: when stdout is redirected it must not leak into the captured data, so the prompt goes to a TTY stderr instead, or nowhere when neither stream is a terminal.
|
|
96
|
+
const promptOutput = this.#output.isTTY === true ? this.#output : this.#error.isTTY === true ? this.#error : undefined;
|
|
97
|
+
const readline = createInterface({ input: this.#input, output: promptOutput });
|
|
95
98
|
this.#readline = readline;
|
|
96
99
|
readline.on("SIGINT", () => this.close());
|
|
97
100
|
try {
|
|
@@ -20,8 +20,12 @@ export async function superviseDevelopmentProcess(command: string, args: readonl
|
|
|
20
20
|
const result = await new Promise<{ code: number | null; signal: NodeJS.Signals | null }>((resolve, reject) => {
|
|
21
21
|
const child = spawn(command, [...args], options);
|
|
22
22
|
const signals: NodeJS.Signals[] = process.platform === "win32" ? ["SIGINT", "SIGTERM"] : ["SIGINT", "SIGTERM", "SIGHUP"];
|
|
23
|
+
// The child inherits stdio and stays in the terminal's foreground process group, so on POSIX it already receives SIGINT and SIGHUP directly; forwarding them would make it count one Ctrl-C as a repeated signal and skip its graceful shutdown. Listeners are still registered for every signal so the supervisor stays alive to collect the child's exit code. SIGTERM is addressed to this pid alone and is the only one relayed.
|
|
24
|
+
const forwarded = new Set<NodeJS.Signals>(process.platform === "win32" ? ["SIGINT", "SIGTERM"] : ["SIGTERM"]);
|
|
23
25
|
const handlers = signals.map((signal) => {
|
|
24
|
-
const handler = () =>
|
|
26
|
+
const handler = () => {
|
|
27
|
+
if (forwarded.has(signal)) child.kill(signal);
|
|
28
|
+
};
|
|
25
29
|
process.on(signal, handler);
|
|
26
30
|
return { signal, handler };
|
|
27
31
|
});
|
|
@@ -210,3 +210,46 @@ describe("NodeStdio interactive prompt", () => {
|
|
|
210
210
|
expect(input.listenerCount("error")).toBe(baseline.error);
|
|
211
211
|
});
|
|
212
212
|
});
|
|
213
|
+
|
|
214
|
+
describe("NodeStdio interactive prompt marker placement", () => {
|
|
215
|
+
function collect(stream: PassThrough): () => string {
|
|
216
|
+
const chunks: Buffer[] = [];
|
|
217
|
+
stream.on("data", (chunk: Buffer) => chunks.push(chunk));
|
|
218
|
+
return () => Buffer.concat(chunks).toString("utf8");
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
test("writes the marker to a TTY stderr instead of a redirected stdout", async () => {
|
|
222
|
+
const input: FakeTty = new PassThrough();
|
|
223
|
+
input.isTTY = true;
|
|
224
|
+
input.setRawMode = () => {};
|
|
225
|
+
const output: FakeTty = new PassThrough();
|
|
226
|
+
const error: FakeTty = new PassThrough();
|
|
227
|
+
error.isTTY = true;
|
|
228
|
+
const written = collect(output);
|
|
229
|
+
const shown = collect(error);
|
|
230
|
+
const stdio = new NodeStdio(input, output, error);
|
|
231
|
+
const read = stdio.readPrompt();
|
|
232
|
+
input.write("hello\n");
|
|
233
|
+
|
|
234
|
+
await expect(settle(read)).resolves.toBe("resolved:hello");
|
|
235
|
+
expect(written()).toBe("");
|
|
236
|
+
expect(shown()).toContain("> ");
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test("writes no marker at all when neither stdout nor stderr is a terminal", async () => {
|
|
240
|
+
const input: FakeTty = new PassThrough();
|
|
241
|
+
input.isTTY = true;
|
|
242
|
+
input.setRawMode = () => {};
|
|
243
|
+
const output: FakeTty = new PassThrough();
|
|
244
|
+
const error: FakeTty = new PassThrough();
|
|
245
|
+
const written = collect(output);
|
|
246
|
+
const shown = collect(error);
|
|
247
|
+
const stdio = new NodeStdio(input, output, error);
|
|
248
|
+
const read = stdio.readPrompt();
|
|
249
|
+
input.write("hello\n");
|
|
250
|
+
|
|
251
|
+
await expect(settle(read)).resolves.toBe("resolved:hello");
|
|
252
|
+
expect(written()).toBe("");
|
|
253
|
+
expect(shown()).toBe("");
|
|
254
|
+
});
|
|
255
|
+
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
1
2
|
import { mkdtemp, readFile, writeFile } from "node:fs/promises";
|
|
2
3
|
import { tmpdir } from "node:os";
|
|
3
4
|
import { join } from "node:path";
|
|
@@ -59,3 +60,37 @@ describe("development profile relaunch", () => {
|
|
|
59
60
|
await expect(superviseDevelopmentProcess(join(tmpdir(), "pi-harness-not-a-binary"), [], { stdio: "ignore" })).rejects.toThrow(/ENOENT/);
|
|
60
61
|
});
|
|
61
62
|
});
|
|
63
|
+
|
|
64
|
+
describe("development profile signal forwarding", () => {
|
|
65
|
+
async function startChild(label: string, body: string): Promise<{ markerPath: string; childPath: string }> {
|
|
66
|
+
const directory = await mkdtemp(join(tmpdir(), `pi-harness-supervisor-${label}-`));
|
|
67
|
+
const markerPath = join(directory, "started.txt");
|
|
68
|
+
const childPath = join(directory, "child.mjs");
|
|
69
|
+
await writeFile(childPath, `import { writeFileSync } from "node:fs"; writeFileSync(process.argv[2], "started"); ${body}`, "utf8");
|
|
70
|
+
return { markerPath, childPath };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
test("relays SIGTERM to the child and reports its signal exit", async () => {
|
|
74
|
+
const { markerPath, childPath } = await startChild("term", "setInterval(() => {}, 1_000);");
|
|
75
|
+
const supervised = superviseDevelopmentProcess(process.execPath, [childPath, markerPath], { stdio: "ignore" });
|
|
76
|
+
await expect.poll(() => existsSync(markerPath), { interval: 20, timeout: 5_000 }).toBe(true);
|
|
77
|
+
|
|
78
|
+
process.emit("SIGTERM", "SIGTERM");
|
|
79
|
+
|
|
80
|
+
await expect(supervised).resolves.toBe(143);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test.skipIf(process.platform === "win32")(
|
|
84
|
+
"does not forward SIGINT or SIGHUP, which the shared foreground process group already delivers to the child",
|
|
85
|
+
async () => {
|
|
86
|
+
const { markerPath, childPath } = await startChild("int", "setTimeout(() => process.exit(21), 800);");
|
|
87
|
+
const supervised = superviseDevelopmentProcess(process.execPath, [childPath, markerPath], { stdio: "ignore" });
|
|
88
|
+
await expect.poll(() => existsSync(markerPath), { interval: 20, timeout: 5_000 }).toBe(true);
|
|
89
|
+
|
|
90
|
+
expect(process.emit("SIGINT", "SIGINT")).toBe(true);
|
|
91
|
+
expect(process.emit("SIGHUP", "SIGHUP")).toBe(true);
|
|
92
|
+
|
|
93
|
+
await expect(supervised).resolves.toBe(21);
|
|
94
|
+
},
|
|
95
|
+
);
|
|
96
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AACpF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAEnD,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,cAAc,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChG;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,IAAI,CAAC;IACtC,gBAAgB,CAAC,OAAO,EAAE,QAAQ,CAAC,SAAS,CAAC,GAAG,MAAM,IAAI,CAAC;IAC3D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,eAAO,MAAM,uBAAuB,OAAO,CAAC;AAE5C,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf,SAAS,EAAE,SAAS,CAAC;KACtB;CACF;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AACpF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAEnD,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,cAAc,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChG;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,IAAI,CAAC;IACtC,gBAAgB,CAAC,OAAO,EAAE,QAAQ,CAAC,SAAS,CAAC,GAAG,MAAM,IAAI,CAAC;IAC3D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,eAAO,MAAM,uBAAuB,OAAO,CAAC;AAE5C,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf,SAAS,EAAE,SAAS,CAAC;KACtB;CACF;;;mBAmEsB,OAAO,UAAU,eAAe;;AAFvD,wBA2DE"}
|
|
@@ -5,13 +5,56 @@ function normalizePath(path) {
|
|
|
5
5
|
throw new Error("Web route path must be absolute without a trailing slash: " + path);
|
|
6
6
|
return path;
|
|
7
7
|
}
|
|
8
|
+
function sendError(response, status, message) {
|
|
9
|
+
response.writeHead(status, { "content-type": "application/json; charset=utf-8" });
|
|
10
|
+
response.end(JSON.stringify({ error: message }));
|
|
11
|
+
}
|
|
8
12
|
function notFound(response) {
|
|
9
|
-
response
|
|
10
|
-
response.end(JSON.stringify({ error: "Not found" }));
|
|
13
|
+
sendError(response, 404, "Not found");
|
|
11
14
|
}
|
|
12
15
|
function hostForUrl(host) {
|
|
13
16
|
return host.includes(":") && !host.startsWith("[") ? "[" + host + "]" : host;
|
|
14
17
|
}
|
|
18
|
+
const LOOPBACK_HOSTNAMES = new Set(["127.0.0.1", "localhost", "[::1]"]);
|
|
19
|
+
const WILDCARD_HOSTS = new Set(["", "0.0.0.0", "::", "[::]"]);
|
|
20
|
+
// Parses an http authority (Host header value, or an Origin with its "http://" prefix removed) into its lowercase hostname (IPv6 stays bracketed, as WHATWG URL reports it) and effective port. Returns undefined for anything that is not a bare authority.
|
|
21
|
+
function parseHttpAuthority(value) {
|
|
22
|
+
let url;
|
|
23
|
+
try {
|
|
24
|
+
url = new URL("http://" + value);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
if (url.username !== "" || url.password !== "" || url.pathname !== "/" || url.search !== "" || url.hash !== "" || url.hostname === "")
|
|
30
|
+
return undefined;
|
|
31
|
+
if (value.endsWith("/") || value.includes("?") || value.includes("#"))
|
|
32
|
+
return undefined;
|
|
33
|
+
return { hostname: url.hostname, port: url.port === "" ? 80 : Number(url.port) };
|
|
34
|
+
}
|
|
35
|
+
// Browsers send the Origin header on every cross-origin request and on all non-GET/HEAD requests, so requiring it to match the (already validated) Host header blocks cross-site requests from pages the user visits. Requests without an Origin header (same-origin navigations, EventSource, curl, Node fetch) pass. The Host check defeats DNS rebinding: a page on attacker.example that resolves to 127.0.0.1 arrives with Host: attacker.example, which is neither loopback nor the configured bind host.
|
|
36
|
+
function rejectForeignRequest(request, response, configuredHost, boundPort) {
|
|
37
|
+
const hostHeader = request.headers.host;
|
|
38
|
+
const authority = hostHeader === undefined ? undefined : parseHttpAuthority(hostHeader);
|
|
39
|
+
if (authority === undefined) {
|
|
40
|
+
sendError(response, 400, "Invalid Host header");
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
const hostnameAllowed = WILDCARD_HOSTS.has(configuredHost) || LOOPBACK_HOSTNAMES.has(authority.hostname) || authority.hostname === hostForUrl(configuredHost).toLowerCase();
|
|
44
|
+
if (!hostnameAllowed || authority.port !== boundPort) {
|
|
45
|
+
sendError(response, 400, "Host header does not match the web server address");
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
const origin = request.headers.origin;
|
|
49
|
+
if (origin === undefined)
|
|
50
|
+
return false;
|
|
51
|
+
const originAuthority = origin.startsWith("http://") ? parseHttpAuthority(origin.slice("http://".length)) : undefined;
|
|
52
|
+
if (originAuthority === undefined || originAuthority.hostname !== authority.hostname || originAuthority.port !== authority.port) {
|
|
53
|
+
sendError(response, 403, "Cross-origin requests are not allowed");
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
15
58
|
export default {
|
|
16
59
|
name: "pi-webserver",
|
|
17
60
|
async apply(context, config) {
|
|
@@ -19,14 +62,19 @@ export default {
|
|
|
19
62
|
const port = config.port ?? DEFAULT_WEB_SERVER_PORT;
|
|
20
63
|
const routes = new Map();
|
|
21
64
|
let fallback;
|
|
65
|
+
// Assigned once listen() completes; tests bind port 0 so the configured port is not the one clients address.
|
|
66
|
+
let boundPort = 0;
|
|
22
67
|
const server = createServer((request, response) => {
|
|
68
|
+
if (rejectForeignRequest(request, response, host, boundPort))
|
|
69
|
+
return;
|
|
23
70
|
const path = new URL(request.url ?? "/", "http://" + hostForUrl(host)).pathname;
|
|
24
71
|
const handler = routes.get(path) ?? fallback;
|
|
25
72
|
if (handler === undefined) {
|
|
26
73
|
notFound(response);
|
|
27
74
|
return;
|
|
28
75
|
}
|
|
29
|
-
|
|
76
|
+
// Invoke inside an async function so a synchronous throw is routed into the same 500 path as an async rejection instead of escaping the request listener as an uncaught exception.
|
|
77
|
+
void (async () => handler(request, response))().catch((error) => {
|
|
30
78
|
if (response.headersSent) {
|
|
31
79
|
response.destroy(error instanceof Error ? error : new Error(String(error)));
|
|
32
80
|
return;
|
|
@@ -45,6 +93,7 @@ export default {
|
|
|
45
93
|
const address = server.address();
|
|
46
94
|
if (address === null || typeof address === "string")
|
|
47
95
|
throw new Error("Web server did not expose a TCP address");
|
|
96
|
+
boundPort = address.port;
|
|
48
97
|
const service = {
|
|
49
98
|
host,
|
|
50
99
|
port: address.port,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAA6C,MAAM,WAAW,CAAC;AAsBpF,MAAM,CAAC,MAAM,uBAAuB,GAAG,IAAI,CAAC;AAQ5C,SAAS,aAAa,CAAC,IAAY;IACjC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,4DAA4D,GAAG,IAAI,CAAC,CAAC;IAC3J,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -33,15 +33,64 @@ function normalizePath(path: string): string {
|
|
|
33
33
|
return path;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
function sendError(response: ServerResponse, status: number, message: string): void {
|
|
37
|
+
response.writeHead(status, { "content-type": "application/json; charset=utf-8" });
|
|
38
|
+
response.end(JSON.stringify({ error: message }));
|
|
39
|
+
}
|
|
40
|
+
|
|
36
41
|
function notFound(response: ServerResponse): void {
|
|
37
|
-
response
|
|
38
|
-
response.end(JSON.stringify({ error: "Not found" }));
|
|
42
|
+
sendError(response, 404, "Not found");
|
|
39
43
|
}
|
|
40
44
|
|
|
41
45
|
function hostForUrl(host: string): string {
|
|
42
46
|
return host.includes(":") && !host.startsWith("[") ? "[" + host + "]" : host;
|
|
43
47
|
}
|
|
44
48
|
|
|
49
|
+
const LOOPBACK_HOSTNAMES = new Set(["127.0.0.1", "localhost", "[::1]"]);
|
|
50
|
+
const WILDCARD_HOSTS = new Set(["", "0.0.0.0", "::", "[::]"]);
|
|
51
|
+
|
|
52
|
+
interface Authority {
|
|
53
|
+
readonly hostname: string;
|
|
54
|
+
readonly port: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Parses an http authority (Host header value, or an Origin with its "http://" prefix removed) into its lowercase hostname (IPv6 stays bracketed, as WHATWG URL reports it) and effective port. Returns undefined for anything that is not a bare authority.
|
|
58
|
+
function parseHttpAuthority(value: string): Authority | undefined {
|
|
59
|
+
let url: URL;
|
|
60
|
+
try {
|
|
61
|
+
url = new URL("http://" + value);
|
|
62
|
+
} catch {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
if (url.username !== "" || url.password !== "" || url.pathname !== "/" || url.search !== "" || url.hash !== "" || url.hostname === "") return undefined;
|
|
66
|
+
if (value.endsWith("/") || value.includes("?") || value.includes("#")) return undefined;
|
|
67
|
+
return { hostname: url.hostname, port: url.port === "" ? 80 : Number(url.port) };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Browsers send the Origin header on every cross-origin request and on all non-GET/HEAD requests, so requiring it to match the (already validated) Host header blocks cross-site requests from pages the user visits. Requests without an Origin header (same-origin navigations, EventSource, curl, Node fetch) pass. The Host check defeats DNS rebinding: a page on attacker.example that resolves to 127.0.0.1 arrives with Host: attacker.example, which is neither loopback nor the configured bind host.
|
|
71
|
+
function rejectForeignRequest(request: IncomingMessage, response: ServerResponse, configuredHost: string, boundPort: number): boolean {
|
|
72
|
+
const hostHeader = request.headers.host;
|
|
73
|
+
const authority = hostHeader === undefined ? undefined : parseHttpAuthority(hostHeader);
|
|
74
|
+
if (authority === undefined) {
|
|
75
|
+
sendError(response, 400, "Invalid Host header");
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
const hostnameAllowed =
|
|
79
|
+
WILDCARD_HOSTS.has(configuredHost) || LOOPBACK_HOSTNAMES.has(authority.hostname) || authority.hostname === hostForUrl(configuredHost).toLowerCase();
|
|
80
|
+
if (!hostnameAllowed || authority.port !== boundPort) {
|
|
81
|
+
sendError(response, 400, "Host header does not match the web server address");
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
const origin = request.headers.origin;
|
|
85
|
+
if (origin === undefined) return false;
|
|
86
|
+
const originAuthority = origin.startsWith("http://") ? parseHttpAuthority(origin.slice("http://".length)) : undefined;
|
|
87
|
+
if (originAuthority === undefined || originAuthority.hostname !== authority.hostname || originAuthority.port !== authority.port) {
|
|
88
|
+
sendError(response, 403, "Cross-origin requests are not allowed");
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
|
|
45
94
|
export default {
|
|
46
95
|
name: "pi-webserver",
|
|
47
96
|
async apply(context: Context, config: WebServerConfig) {
|
|
@@ -49,14 +98,18 @@ export default {
|
|
|
49
98
|
const port = config.port ?? DEFAULT_WEB_SERVER_PORT;
|
|
50
99
|
const routes = new Map<string, WebRoute["handler"]>();
|
|
51
100
|
let fallback: WebRoute["handler"] | undefined;
|
|
101
|
+
// Assigned once listen() completes; tests bind port 0 so the configured port is not the one clients address.
|
|
102
|
+
let boundPort = 0;
|
|
52
103
|
const server = createServer((request, response) => {
|
|
104
|
+
if (rejectForeignRequest(request, response, host, boundPort)) return;
|
|
53
105
|
const path = new URL(request.url ?? "/", "http://" + hostForUrl(host)).pathname;
|
|
54
106
|
const handler = routes.get(path) ?? fallback;
|
|
55
107
|
if (handler === undefined) {
|
|
56
108
|
notFound(response);
|
|
57
109
|
return;
|
|
58
110
|
}
|
|
59
|
-
|
|
111
|
+
// Invoke inside an async function so a synchronous throw is routed into the same 500 path as an async rejection instead of escaping the request listener as an uncaught exception.
|
|
112
|
+
void (async () => handler(request, response))().catch((error: unknown) => {
|
|
60
113
|
if (response.headersSent) {
|
|
61
114
|
response.destroy(error instanceof Error ? error : new Error(String(error)));
|
|
62
115
|
return;
|
|
@@ -74,6 +127,7 @@ export default {
|
|
|
74
127
|
});
|
|
75
128
|
const address = server.address();
|
|
76
129
|
if (address === null || typeof address === "string") throw new Error("Web server did not expose a TCP address");
|
|
130
|
+
boundPort = address.port;
|
|
77
131
|
const service: WebServer = {
|
|
78
132
|
host,
|
|
79
133
|
port: address.port,
|