From 89041c816f58a5ad5a01c6fc977da5302f068d16 Mon Sep 17 00:00:00 2001 From: vectronic Date: Thu, 13 Mar 2025 21:46:39 +0000 Subject: [PATCH 1/9] fix: initial implementation of example host application and tests --- .github/workflows/check-bun-dependencies.yml | 9 + .github/workflows/lint-pr-message.yml | 11 ++ .github/workflows/release-bun-executable.yml | 10 + .../workflows/validate-bun-executable-pr.yml | 8 + .gitignore | 180 ++++++++++++++++++ README.md | 94 ++++++++- bun.lock | 35 ++++ index.ts | 3 + package.json | 32 ++++ script/install.ps1 | 31 +++ script/install.sh | 25 +++ src/ExampleHostApplication.ts | 66 +++++++ tests/hello_test.ts | 8 + tsconfig.json | 27 +++ 14 files changed, 538 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/check-bun-dependencies.yml create mode 100644 .github/workflows/lint-pr-message.yml create mode 100644 .github/workflows/release-bun-executable.yml create mode 100644 .github/workflows/validate-bun-executable-pr.yml create mode 100644 .gitignore create mode 100644 bun.lock create mode 100644 index.ts create mode 100644 package.json create mode 100644 script/install.ps1 create mode 100755 script/install.sh create mode 100644 src/ExampleHostApplication.ts create mode 100644 tests/hello_test.ts create mode 100644 tsconfig.json diff --git a/.github/workflows/check-bun-dependencies.yml b/.github/workflows/check-bun-dependencies.yml new file mode 100644 index 0000000..7948077 --- /dev/null +++ b/.github/workflows/check-bun-dependencies.yml @@ -0,0 +1,9 @@ +name: check-bun-dependencies +on: + workflow_dispatch: + schedule: + - cron: "0 0 * * *" +jobs: + call-check-bun-dependencies: + uses: flowscripter/.github/.github/workflows/check-bun-dependencies.yml@v1 + secrets: inherit diff --git a/.github/workflows/lint-pr-message.yml b/.github/workflows/lint-pr-message.yml new file mode 100644 index 0000000..8d79430 --- /dev/null +++ b/.github/workflows/lint-pr-message.yml @@ -0,0 +1,11 @@ +name: lint-pr-message +on: + pull_request_target: + types: + - opened + - edited + - synchronize +jobs: + call-lint-pr-message: + uses: flowscripter/.github/.github/workflows/lint-pr-message.yml@v1 + secrets: inherit diff --git a/.github/workflows/release-bun-executable.yml b/.github/workflows/release-bun-executable.yml new file mode 100644 index 0000000..b9068a8 --- /dev/null +++ b/.github/workflows/release-bun-executable.yml @@ -0,0 +1,10 @@ +name: release-bun-executable +on: + push: + branches: [main] +jobs: + call-release-bun-executable: + uses: flowscripter/.github/.github/workflows/release-bun-executable.yml@v1 + secrets: inherit + with: + executable-name: "example-host-application" diff --git a/.github/workflows/validate-bun-executable-pr.yml b/.github/workflows/validate-bun-executable-pr.yml new file mode 100644 index 0000000..7897654 --- /dev/null +++ b/.github/workflows/validate-bun-executable-pr.yml @@ -0,0 +1,8 @@ +name: validate-bun-executable-pr +on: + pull_request: + branches: [main] +jobs: + call-validate-bun-executable-pr: + uses: flowscripter/.github/.github/workflows/validate-bun-executable-pr.yml@v1 + secrets: inherit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d652127 --- /dev/null +++ b/.gitignore @@ -0,0 +1,180 @@ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Caches + +.cache + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store +docs/ +template-bun-executable +/.releaserc +functional_tests/features/support/__pycache__/pexpect_wrapper.cpython-311.pyc +example-host-application diff --git a/README.md b/README.md index 14051c7..89f8f8a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,94 @@ # example-host-application -Example host application executable for the https://github.com/flowscripter/dynamic-plugin-framework + +[![version](https://img.shields.io/github/v/release/flowscripter/example-host-application?sort=semver)](https://github.com/flowscripter/example-host-application/releases) +[![build](https://img.shields.io/github/actions/workflow/status/flowscripter/example-host-application/release-bun-executable.yml)](https://github.com/flowscripter/example-host-application/actions/workflows/release-bun-executable.yml) +[![coverage](https://codecov.io/gh/flowscripter/example-host-application/branch/main/graph/badge.svg?token=EMFT2938ZF)](https://codecov.io/gh/flowscripter/example-host-application) +[![docs](https://img.shields.io/badge/docs-API-blue)](https://flowscripter.github.io/example-host-application/index.html) +[![license: MIT](https://img.shields.io/github/license/flowscripter/example-host-application)](https://github.com/flowscripter/example-host-application/blob/main/LICENSE) + +> Example host application executable for the +> [dynamic-plugin-framework](https://github.com/flowscripter/dynamic-plugin-framework) + +## Binary Executable Usage + +**NOTE**: The binaries are 10's of megabytes in size as the entire Bun runtime +is included. + +#### MacOS + +Via [Homebrew](https://brew.sh/): + +`brew install flowscripter/tap/example-host-application` + +#### Linux + +In a terminal: + +`curl -fsSL https://raw.githubusercontent.com/flowscripter/example-host-application/main/script/install.sh | sh` + +#### Windows + +In PowerShell: + +`curl -fsSL https://raw.githubusercontent.com/flowscripter/example-host-application/main/script/install.ps1 | powershell` + +#### Manual Install + +You can download and extract the binary zip files from the +[releases](https://github.com/flowscripter/example-host-application/releases) +page. + +## Functional Tests + +Refer to [functional_tests/README.md](functional_tests/README.md) + +## Development + +Install dependencies: + +`bun install` + +Test: + +`bun test` + +Run: + +`bun run index.ts` + +Compile binary: + +`bun build index.ts --compile --outfile /tmp/example-host-application` + +**NOTE**: The following tasks use Deno as it excels at these and Bun does not +currently provide such functionality: + +Format: + +`deno fmt` + +Lint: + +`deno lint index.ts src/ tests/` + +Generate HTML API Documentation: + +`deno doc --html --name=example-host-application index.ts` + +## Documentation + +### Framework API + +Refer to the +[dynamic-plugin-framework](https://github.com/flowscripter/dynamic-plugin-framework) +for an overview of what this example is demonstrating. + +### API + +Link to auto-generated API docs: + +[API Documentation](https://flowscripter.github.io/example-host-application/index.html) + +## License + +MIT © Flowscripter diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..13a9e10 --- /dev/null +++ b/bun.lock @@ -0,0 +1,35 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "@flowscripter/example-host-application", + "dependencies": { + "@flowscripter/dynamic-plugin-framework": "^1.3.8", + "@flowscripter/example-plugin-api": "^1.0.0", + }, + "devDependencies": { + "@types/bun": "^1.2.4", + }, + "peerDependencies": { + "typescript": "^5.8.2", + }, + }, + }, + "packages": { + "@flowscripter/dynamic-plugin-framework": ["@flowscripter/dynamic-plugin-framework@1.3.8", "", { "peerDependencies": { "typescript": "^5.8.2" } }, "sha512-/XEYnDORtgazu/7lOcqhwbnwyyErzs3p3aMfzRXFDtJNi+uBwNdJDVw81huHOqaf+UDoUPFu3L6SwSZv6LXFMg=="], + + "@flowscripter/example-plugin-api": ["@flowscripter/example-plugin-api@1.0.0", "", { "peerDependencies": { "typescript": "^5.8.2" } }, "sha512-6IND3SxnkIKspmmRUQqe3lITS+tQNNa7gKEkROSu1G1FYae8Gi5AnLDUOx1+JwhcHha+607az+ciXZ35Q/gNtw=="], + + "@types/bun": ["@types/bun@1.2.4", "", { "dependencies": { "bun-types": "1.2.4" } }, "sha512-QtuV5OMR8/rdKJs213iwXDpfVvnskPXY/S0ZiFbsTjQZycuqPbMW8Gf/XhLfwE5njW8sxI2WjISURXPlHypMFA=="], + + "@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], + + "@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="], + + "bun-types": ["bun-types@1.2.4", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-nDPymR207ZZEoWD4AavvEaa/KZe/qlrbMSchqpQwovPZCKc7pwMoENjEtHgMKaAjJhy+x6vfqSBA1QU3bJgs0Q=="], + + "typescript": ["typescript@5.8.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="], + + "undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], + } +} diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..4d9195e --- /dev/null +++ b/index.ts @@ -0,0 +1,3 @@ +import { exampleHostApplication } from "./src/ExampleHostApplication.ts"; + +await exampleHostApplication(); diff --git a/package.json b/package.json new file mode 100644 index 0000000..77de867 --- /dev/null +++ b/package.json @@ -0,0 +1,32 @@ +{ + "name": "@flowscripter/example-host-application", + "description": "Example host application for the https://github.com/flowscripter/dynamic-plugin-framework", + "repository": "@flowscripter/example-host-application", + "license": "MIT", + "keywords": [ + "bun", + "example", + "plugin", + "framework", + "dynamic", + "import", + "executable", + "cli" + ], + "module": "index.ts", + "type": "module", + "version": "0.0.0", + "publishConfig": { + "access": "public" + }, + "devDependencies": { + "@types/bun": "^1.2.4" + }, + "peerDependencies": { + "typescript": "^5.8.2" + }, + "dependencies": { + "@flowscripter/dynamic-plugin-framework": "^1.3.8", + "@flowscripter/example-plugin-api": "^1.0.0" + } +} diff --git a/script/install.ps1 b/script/install.ps1 new file mode 100644 index 0000000..9649ff6 --- /dev/null +++ b/script/install.ps1 @@ -0,0 +1,31 @@ +# Set exit on error +$ErrorActionPreference = "Stop" + +# URL of the ZIP file containing the executable +$zipUrl = "https://github.com/flowscripter/example-host-application/releases/latest/download/example-host-application_Windows_x86_64.zip" +$installDir = "$env:ProgramFiles\example-host-application" + +# Create a temporary directory to download the ZIP +$tempDir = [System.IO.Path]::Combine($env:TEMP, "flowscripter_install") +New-Item -ItemType Directory -Force -Path $tempDir + +# Download the ZIP file +Write-Host "Downloading example-host-application..." +Invoke-WebRequest -Uri $zipUrl -OutFile "$tempDir\executable.zip" + +# Extract the ZIP file +Write-Host "Extracting the ZIP file..." +Expand-Archive -Path "$tempDir\executable.zip" -DestinationPath $tempDir -Force + +# Install the executable +Write-Host "Installing the executable..." +Move-Item -Path "$tempDir\example-host-application.exe" -Destination $installDir -Force + +# Add the executable to the system PATH (for all users) +$env:Path += ";$installDir" +[Environment]::SetEnvironmentVariable("Path", $env:Path, [System.EnvironmentVariableTarget]::Machine) + +# Clean up temporary files +Remove-Item -Recurse -Force $tempDir + +Write-Host "✅ Installation complete! You can now run 'example-host-application' from any command prompt." diff --git a/script/install.sh b/script/install.sh new file mode 100755 index 0000000..4a8ab51 --- /dev/null +++ b/script/install.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +set -e # Exit on error + +# Define the download URL +URL="https://github.com/flowscripter/example-host-application/releases/latest/download/example-host-application_Linux_x86_64.zip" + +# Create a temporary directory +TMP_DIR=$(mktemp -d) +cd "$TMP_DIR" + +# Download and extract +echo "Downloading example-host-application..." +curl -fsSL "$URL" -o executable.zip +unzip executable.zip + +# Install +chmod +x example-host-application +sudo mv example-host-application /usr/local/bin/ + +# Clean up +cd - +rm -rf "$TMP_DIR" + +echo "✅ Installation complete! Run 'example-host-application' to get started." diff --git a/src/ExampleHostApplication.ts b/src/ExampleHostApplication.ts new file mode 100644 index 0000000..0b6f48e --- /dev/null +++ b/src/ExampleHostApplication.ts @@ -0,0 +1,66 @@ +import { + EXTENSION_POINT_1, + type ExtensionPoint1, +} from "@flowscripter/example-plugin-api"; +import { + DefaultPluginManager, + type ExtensionInfo, + UrlListPluginRepository, +} from "@flowscripter/dynamic-plugin-framework"; + +/** + * Searches for an extension, instantiates it and invokes it. + */ +export async function exampleHostApplication(): Promise { + const pluginRepository = new UrlListPluginRepository( + new Set(["file:///Users/nick/projects/flowscripter/example-plugin/bundle/index.js"]), + ); + + const pluginManager = new DefaultPluginManager([pluginRepository]); + + console.info(`Registering extensions for ${EXTENSION_POINT_1} extension point`); + + await pluginManager.registerExtensions(EXTENSION_POINT_1); + + console.info("Registered extensions:"); + + const extensionInfos = await pluginManager.getRegisteredExtensions( + EXTENSION_POINT_1, + ); + + extensionInfos.forEach((extensionInfo: ExtensionInfo) => { + let extensionInfoString = + `extensionHandle: ${extensionInfo.extensionHandle}\n`; + + if (extensionInfo.extensionData) { + extensionInfoString += `extensionData:\n`; + for (const entry of extensionInfo.extensionData.entries()) { + extensionInfoString += `\t${entry[0]} => ${entry[1]}\n`; + } + } + if (extensionInfo.pluginData) { + extensionInfoString += `pluginData:\n`; + for (const entry of extensionInfo.pluginData.entries()) { + extensionInfoString += `\t${entry[0]} => ${entry[1]}\n`; + } + } + + console.info(extensionInfoString); + }); + + if (extensionInfos.length > 0) { + console.info("Instantiating first extension"); + + const extension = await pluginManager.instantiate( + extensionInfos[0].extensionHandle, + new Map([[ + "host_foo", + "host_bar", + ]]), + ) as ExtensionPoint1; + + console.info("Invoking extension"); + + extension.sayHello(); + } +} diff --git a/tests/hello_test.ts b/tests/hello_test.ts new file mode 100644 index 0000000..ec4214b --- /dev/null +++ b/tests/hello_test.ts @@ -0,0 +1,8 @@ +import { describe, test } from "bun:test"; +import { exampleHostApplication } from "../src/ExampleHostApplication.ts"; + +describe("ExampleHostApplication Tests", () => { + test("Invoke example host application", async () => { + await exampleHostApplication(); + }); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..238655f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + // Enable latest features + "lib": ["ESNext", "DOM"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +} From 98aa6300882f495724a21074c442a0737682b25d Mon Sep 17 00:00:00 2001 From: vectronic Date: Sun, 16 Mar 2025 18:42:39 +0000 Subject: [PATCH 2/9] chore: deno fmt --- src/ExampleHostApplication.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/ExampleHostApplication.ts b/src/ExampleHostApplication.ts index 0b6f48e..3f5e2d7 100644 --- a/src/ExampleHostApplication.ts +++ b/src/ExampleHostApplication.ts @@ -13,12 +13,16 @@ import { */ export async function exampleHostApplication(): Promise { const pluginRepository = new UrlListPluginRepository( - new Set(["file:///Users/nick/projects/flowscripter/example-plugin/bundle/index.js"]), + new Set([ + "file:///Users/nick/projects/flowscripter/example-plugin/bundle/index.js", + ]), ); const pluginManager = new DefaultPluginManager([pluginRepository]); - console.info(`Registering extensions for ${EXTENSION_POINT_1} extension point`); + console.info( + `Registering extensions for ${EXTENSION_POINT_1} extension point`, + ); await pluginManager.registerExtensions(EXTENSION_POINT_1); From 8e533a603393f87c607524d5a001de6079b6e39c Mon Sep 17 00:00:00 2001 From: vectronic Date: Sun, 16 Mar 2025 21:13:06 +0000 Subject: [PATCH 3/9] fix: fix gitignore --- .gitignore | 3 +- functional_tests/README.md | 23 ++++++++++ functional_tests/features/environment.py | 9 ++++ functional_tests/features/executable.feature | 7 +++ functional_tests/features/steps/executable.py | 18 ++++++++ .../features/support/pexpect_wrapper.py | 45 +++++++++++++++++++ functional_tests/pip-requirements.txt | 2 + 7 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 functional_tests/README.md create mode 100644 functional_tests/features/environment.py create mode 100644 functional_tests/features/executable.feature create mode 100644 functional_tests/features/steps/executable.py create mode 100644 functional_tests/features/support/pexpect_wrapper.py create mode 100644 functional_tests/pip-requirements.txt diff --git a/.gitignore b/.gitignore index d652127..5ca95c3 100644 --- a/.gitignore +++ b/.gitignore @@ -176,5 +176,6 @@ dist docs/ template-bun-executable /.releaserc -functional_tests/features/support/__pycache__/pexpect_wrapper.cpython-311.pyc +functional_tests/features/support/__pycache__ example-host-application +functional_tests/.venv \ No newline at end of file diff --git a/functional_tests/README.md b/functional_tests/README.md new file mode 100644 index 0000000..437544c --- /dev/null +++ b/functional_tests/README.md @@ -0,0 +1,23 @@ +## Executable Functional Tests + +#### Setup + +Ensure the executable is built: + + bun build ../index.ts --compile --outfile /tmp/example-host-application + +Install requirements: + + pip3 install -r pip-requirements.txt + +#### Testing + +Run the functional tests: + + export EXECUTABLE=/tmp/example-host-application + behave + +To run with logging output from the test steps (this is the best set of +arguments I can find): + + behave --no-logcapture --no-color --logging-level=DEBUG diff --git a/functional_tests/features/environment.py b/functional_tests/features/environment.py new file mode 100644 index 0000000..2779b17 --- /dev/null +++ b/functional_tests/features/environment.py @@ -0,0 +1,9 @@ +import os + +from support.pexpect_wrapper import PExpectWrapper + + +def before_all(context): + + context.config.setup_logging() + context.pexpect_wrapper = PExpectWrapper(os.environ.get('EXECUTABLE')) diff --git a/functional_tests/features/executable.feature b/functional_tests/features/executable.feature new file mode 100644 index 0000000..5c9cb9a --- /dev/null +++ b/functional_tests/features/executable.feature @@ -0,0 +1,7 @@ +Feature: Executable + + Scenario: Executable success + When the executable is launched + Then the executable should complete successfully + And the executable should have output "INFO Invoking extension" + And the executable should have output "INFO hello world" diff --git a/functional_tests/features/steps/executable.py b/functional_tests/features/steps/executable.py new file mode 100644 index 0000000..ae01469 --- /dev/null +++ b/functional_tests/features/steps/executable.py @@ -0,0 +1,18 @@ +from behave import when, then + + +@when('the executable is launched') +def step_impl(context): + context.pexpect_wrapper.start() + + +@then('the executable should complete successfully') +def step_impl(context): + context.pexpect_wrapper.expect_eof() + status = context.pexpect_wrapper.complete() + assert status == 0, 'unexpected exit status: {}'.format(status) + + +@then('the executable should have output "{message}"') +def step_impl(context, message): + context.pexpect_wrapper.expect(message) diff --git a/functional_tests/features/support/pexpect_wrapper.py b/functional_tests/features/support/pexpect_wrapper.py new file mode 100644 index 0000000..fd75ca2 --- /dev/null +++ b/functional_tests/features/support/pexpect_wrapper.py @@ -0,0 +1,45 @@ +from pexpect.popen_spawn import PopenSpawn +from pexpect import EOF +import logging +log = logging.getLogger("pexpect_wrapper") + + +class PExpectWrapper: + + def __init__(self, executable): + self.executable = executable + self.child = None + self.output = None + + def start(self): + assert self.child is None + + self.child = PopenSpawn(self.executable, encoding='utf-8') + + def expect(self, message): + assert self.child is not None + + found = '' + while len(self.output) > 0: + next_line = self.output.pop(0) + log.debug('looking for "{}" in "{}"'.format(message, next_line)) + if message in next_line: + found = next_line + break + + assert found != '', 'expected {} in output'.format(message) + + + def expect_eof(self): + assert self.child is not None + + self.child.expect(EOF) + + def complete(self): + assert self.child is not None + + exit_status = self.child.wait() + + self.output = self.child.before.split('\n') + + return exit_status diff --git a/functional_tests/pip-requirements.txt b/functional_tests/pip-requirements.txt new file mode 100644 index 0000000..ce62611 --- /dev/null +++ b/functional_tests/pip-requirements.txt @@ -0,0 +1,2 @@ +pexpect +behave From d3e3a890d11420c1dae16db6172a8c56214771bd Mon Sep 17 00:00:00 2001 From: vectronic Date: Tue, 18 Mar 2025 15:11:25 +0000 Subject: [PATCH 4/9] fix: fix functional test driver --- functional_tests/features/environment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/functional_tests/features/environment.py b/functional_tests/features/environment.py index 2779b17..9639296 100644 --- a/functional_tests/features/environment.py +++ b/functional_tests/features/environment.py @@ -3,7 +3,7 @@ from support.pexpect_wrapper import PExpectWrapper -def before_all(context): +def before_scenario(context, scenario): context.config.setup_logging() context.pexpect_wrapper = PExpectWrapper(os.environ.get('EXECUTABLE')) From 1ac470d2721f3804366fe9f1a1702e0aa2777f09 Mon Sep 17 00:00:00 2001 From: vectronic Date: Tue, 18 Mar 2025 15:58:05 +0000 Subject: [PATCH 5/9] fix: fix workflows --- .github/workflows/check-bun-dependencies.yml | 3 +++ .github/workflows/lint-pr-message.yml | 2 ++ .github/workflows/release-bun-executable.yml | 6 ++++++ .github/workflows/validate-bun-executable-pr.yml | 2 ++ 4 files changed, 13 insertions(+) diff --git a/.github/workflows/check-bun-dependencies.yml b/.github/workflows/check-bun-dependencies.yml index 7948077..f87e507 100644 --- a/.github/workflows/check-bun-dependencies.yml +++ b/.github/workflows/check-bun-dependencies.yml @@ -3,6 +3,9 @@ on: workflow_dispatch: schedule: - cron: "0 0 * * *" +permissions: + contents: read + pull-requests: write jobs: call-check-bun-dependencies: uses: flowscripter/.github/.github/workflows/check-bun-dependencies.yml@v1 diff --git a/.github/workflows/lint-pr-message.yml b/.github/workflows/lint-pr-message.yml index 8d79430..1f8f1c6 100644 --- a/.github/workflows/lint-pr-message.yml +++ b/.github/workflows/lint-pr-message.yml @@ -5,6 +5,8 @@ on: - opened - edited - synchronize +permissions: + contents: read jobs: call-lint-pr-message: uses: flowscripter/.github/.github/workflows/lint-pr-message.yml@v1 diff --git a/.github/workflows/release-bun-executable.yml b/.github/workflows/release-bun-executable.yml index b9068a8..be8697f 100644 --- a/.github/workflows/release-bun-executable.yml +++ b/.github/workflows/release-bun-executable.yml @@ -2,6 +2,12 @@ name: release-bun-executable on: push: branches: [main] +permissions: + contents: write + issues: write + pull-requests: write + id-token: write + pages: write jobs: call-release-bun-executable: uses: flowscripter/.github/.github/workflows/release-bun-executable.yml@v1 diff --git a/.github/workflows/validate-bun-executable-pr.yml b/.github/workflows/validate-bun-executable-pr.yml index 7897654..a0fe86a 100644 --- a/.github/workflows/validate-bun-executable-pr.yml +++ b/.github/workflows/validate-bun-executable-pr.yml @@ -2,6 +2,8 @@ name: validate-bun-executable-pr on: pull_request: branches: [main] +permissions: + contents: read jobs: call-validate-bun-executable-pr: uses: flowscripter/.github/.github/workflows/validate-bun-executable-pr.yml@v1 From 1a4b043ad67252223ef7d3d8f41684b4962154fe Mon Sep 17 00:00:00 2001 From: vectronic Date: Tue, 18 Mar 2025 17:08:53 +0000 Subject: [PATCH 6/9] fix: update docs as windows install/run not yet working --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 89f8f8a..7a25b1a 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ In a terminal: #### Windows -In PowerShell: +In PowerShell: **(NOTE: This does not currently work...)** `curl -fsSL https://raw.githubusercontent.com/flowscripter/example-host-application/main/script/install.ps1 | powershell` From af7d5e248730a25c103d91b5b27ace179bfa8f38 Mon Sep 17 00:00:00 2001 From: vectronic Date: Tue, 8 Apr 2025 12:53:23 +0100 Subject: [PATCH 7/9] fix: remove unused script --- script/install.ps1 | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 script/install.ps1 diff --git a/script/install.ps1 b/script/install.ps1 deleted file mode 100644 index 9649ff6..0000000 --- a/script/install.ps1 +++ /dev/null @@ -1,31 +0,0 @@ -# Set exit on error -$ErrorActionPreference = "Stop" - -# URL of the ZIP file containing the executable -$zipUrl = "https://github.com/flowscripter/example-host-application/releases/latest/download/example-host-application_Windows_x86_64.zip" -$installDir = "$env:ProgramFiles\example-host-application" - -# Create a temporary directory to download the ZIP -$tempDir = [System.IO.Path]::Combine($env:TEMP, "flowscripter_install") -New-Item -ItemType Directory -Force -Path $tempDir - -# Download the ZIP file -Write-Host "Downloading example-host-application..." -Invoke-WebRequest -Uri $zipUrl -OutFile "$tempDir\executable.zip" - -# Extract the ZIP file -Write-Host "Extracting the ZIP file..." -Expand-Archive -Path "$tempDir\executable.zip" -DestinationPath $tempDir -Force - -# Install the executable -Write-Host "Installing the executable..." -Move-Item -Path "$tempDir\example-host-application.exe" -Destination $installDir -Force - -# Add the executable to the system PATH (for all users) -$env:Path += ";$installDir" -[Environment]::SetEnvironmentVariable("Path", $env:Path, [System.EnvironmentVariableTarget]::Machine) - -# Clean up temporary files -Remove-Item -Recurse -Force $tempDir - -Write-Host "✅ Installation complete! You can now run 'example-host-application' from any command prompt." From f1955dd304c609f142fab4f8122b9cf9f36bbeaa Mon Sep 17 00:00:00 2001 From: vectronic Date: Tue, 8 Apr 2025 12:53:35 +0100 Subject: [PATCH 8/9] doc: update readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7a25b1a..d469858 100644 --- a/README.md +++ b/README.md @@ -28,9 +28,9 @@ In a terminal: #### Windows -In PowerShell: **(NOTE: This does not currently work...)** +Via [Winget](https://github.com/microsoft/winget-cli): -`curl -fsSL https://raw.githubusercontent.com/flowscripter/example-host-application/main/script/install.ps1 | powershell` +`winget install Flowscripter.example-host-application` #### Manual Install From 5190f6572b06381edcd6db981665377dc40188b1 Mon Sep 17 00:00:00 2001 From: vectronic Date: Thu, 17 Apr 2025 23:22:57 +0100 Subject: [PATCH 9/9] fix: looks to be working --- bun.lock | 16 +++++++--------- functional_tests/features/executable.feature | 4 ++-- package.json | 6 +++--- src/ExampleHostApplication.ts | 7 ++++++- 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/bun.lock b/bun.lock index 13a9e10..0a61f07 100644 --- a/bun.lock +++ b/bun.lock @@ -4,11 +4,11 @@ "": { "name": "@flowscripter/example-host-application", "dependencies": { - "@flowscripter/dynamic-plugin-framework": "^1.3.8", - "@flowscripter/example-plugin-api": "^1.0.0", + "@flowscripter/dynamic-plugin-framework": "^1.3.18", + "@flowscripter/example-plugin-api": "^1.0.8", }, "devDependencies": { - "@types/bun": "^1.2.4", + "@types/bun": "^1.2.10", }, "peerDependencies": { "typescript": "^5.8.2", @@ -16,17 +16,15 @@ }, }, "packages": { - "@flowscripter/dynamic-plugin-framework": ["@flowscripter/dynamic-plugin-framework@1.3.8", "", { "peerDependencies": { "typescript": "^5.8.2" } }, "sha512-/XEYnDORtgazu/7lOcqhwbnwyyErzs3p3aMfzRXFDtJNi+uBwNdJDVw81huHOqaf+UDoUPFu3L6SwSZv6LXFMg=="], + "@flowscripter/dynamic-plugin-framework": ["@flowscripter/dynamic-plugin-framework@1.3.18", "", { "peerDependencies": { "typescript": "^5.8.3" } }, "sha512-OjH8O4MWXU/Wt1so49ef6OnnHlJkHuKKya8zVLR3nkxXhHbR3jHf2VmWl+mOR6liRyN9YN5IyeCvKonnAG6jTQ=="], - "@flowscripter/example-plugin-api": ["@flowscripter/example-plugin-api@1.0.0", "", { "peerDependencies": { "typescript": "^5.8.2" } }, "sha512-6IND3SxnkIKspmmRUQqe3lITS+tQNNa7gKEkROSu1G1FYae8Gi5AnLDUOx1+JwhcHha+607az+ciXZ35Q/gNtw=="], + "@flowscripter/example-plugin-api": ["@flowscripter/example-plugin-api@1.0.8", "", { "peerDependencies": { "typescript": "^5.8.2" } }, "sha512-hRF0DRg+8aKmnAwLDQyA7phjRbET0yD9bBYdny2csL2N5JiV/KLFdfwtJSSSL/LlMdkwtl0VKAVroVc78eWAsw=="], - "@types/bun": ["@types/bun@1.2.4", "", { "dependencies": { "bun-types": "1.2.4" } }, "sha512-QtuV5OMR8/rdKJs213iwXDpfVvnskPXY/S0ZiFbsTjQZycuqPbMW8Gf/XhLfwE5njW8sxI2WjISURXPlHypMFA=="], + "@types/bun": ["@types/bun@1.2.10", "", { "dependencies": { "bun-types": "1.2.10" } }, "sha512-eilv6WFM3M0c9ztJt7/g80BDusK98z/FrFwseZgT4bXCq2vPhXD4z8R3oddmAn+R/Nmz9vBn4kweJKmGTZj+lg=="], "@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - "@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="], - - "bun-types": ["bun-types@1.2.4", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-nDPymR207ZZEoWD4AavvEaa/KZe/qlrbMSchqpQwovPZCKc7pwMoENjEtHgMKaAjJhy+x6vfqSBA1QU3bJgs0Q=="], + "bun-types": ["bun-types@1.2.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-b5ITZMnVdf3m1gMvJHG+gIfeJHiQPJak0f7925Hxu6ZN5VKA8AGy4GZ4lM+Xkn6jtWxg5S3ldWvfmXdvnkp3GQ=="], "typescript": ["typescript@5.8.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="], diff --git a/functional_tests/features/executable.feature b/functional_tests/features/executable.feature index 5c9cb9a..a287ec8 100644 --- a/functional_tests/features/executable.feature +++ b/functional_tests/features/executable.feature @@ -3,5 +3,5 @@ Feature: Executable Scenario: Executable success When the executable is launched Then the executable should complete successfully - And the executable should have output "INFO Invoking extension" - And the executable should have output "INFO hello world" + And the executable should have output "Invoking extension" + And the executable should have output "hello world" diff --git a/package.json b/package.json index 77de867..a2b0249 100644 --- a/package.json +++ b/package.json @@ -20,13 +20,13 @@ "access": "public" }, "devDependencies": { - "@types/bun": "^1.2.4" + "@types/bun": "^1.2.10" }, "peerDependencies": { "typescript": "^5.8.2" }, "dependencies": { - "@flowscripter/dynamic-plugin-framework": "^1.3.8", - "@flowscripter/example-plugin-api": "^1.0.0" + "@flowscripter/dynamic-plugin-framework": "^1.3.18", + "@flowscripter/example-plugin-api": "^1.0.8" } } diff --git a/src/ExampleHostApplication.ts b/src/ExampleHostApplication.ts index 3f5e2d7..8a95194 100644 --- a/src/ExampleHostApplication.ts +++ b/src/ExampleHostApplication.ts @@ -14,7 +14,10 @@ import { export async function exampleHostApplication(): Promise { const pluginRepository = new UrlListPluginRepository( new Set([ - "file:///Users/nick/projects/flowscripter/example-plugin/bundle/index.js", + { + url: "https://unpkg.com/@flowscripter/example-plugin/dist/bundle.js", + extensionPoints: [EXTENSION_POINT_1], + }, ]), ); @@ -66,5 +69,7 @@ export async function exampleHostApplication(): Promise { console.info("Invoking extension"); extension.sayHello(); + } else { + throw new Error("No extensions found"); } }