From b19e0488e61b8a1a5226454f7f770590dce4bbee Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Wed, 19 Aug 2026 11:12:46 +0530 Subject: [PATCH 1/2] fix: validate template runtime builds --- .github/scripts/load-composer-classes.php | 50 +++++ .github/workflows/build.yml | 237 ++++++++++++++++++++++ php/email-contact-form/src/index.php | 4 +- php/starter/composer.json | 2 +- php/starter/composer.lock | 18 +- 5 files changed, 299 insertions(+), 12 deletions(-) create mode 100644 .github/scripts/load-composer-classes.php create mode 100644 .github/workflows/build.yml diff --git a/.github/scripts/load-composer-classes.php b/.github/scripts/load-composer-classes.php new file mode 100644 index 00000000..1a15295e --- /dev/null +++ b/.github/scripts/load-composer-classes.php @@ -0,0 +1,50 @@ +\n"); + exit(2); +} + +$template = realpath($argv[1]); +if ($template === false || !is_file($template . '/vendor/autoload.php')) { + fwrite(STDERR, "Installed template not found: {$argv[1]}\n"); + exit(2); +} + +require $template . '/vendor/autoload.php'; + +$symbols = []; +foreach (glob($template . '/src/*.php') ?: [] as $source) { + $contents = file_get_contents($source); + if ($contents === false) { + fwrite(STDERR, "Unable to read PHP source: {$source}\n"); + exit(2); + } + + preg_match_all('/^\s*use\s+(?!function\s|const\s)([^;]+);/m', $contents, $matches); + foreach ($matches[1] as $declaration) { + $symbols[] = preg_split('/\s+as\s+/i', trim($declaration))[0]; + } +} + +// A PHP use declaration does not autoload its class. Load every imported symbol explicitly so +// dependency parse errors (including case-insensitive duplicate methods) fail during CI. +foreach (array_unique($symbols) as $symbol) { + $loaded = class_exists($symbol) + || interface_exists($symbol) + || trait_exists($symbol) + || (function_exists('enum_exists') && enum_exists($symbol)); + + if (!$loaded) { + fwrite(STDERR, "Unable to load imported symbol: {$symbol}\n"); + exit(1); + } +} + +$entrypoint = $template . '/src/index.php'; +if (is_file($entrypoint) && !is_callable(require $entrypoint)) { + fwrite(STDERR, "PHP entrypoint did not return a callable: {$entrypoint}\n"); + exit(1); +} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..f4f93d3a --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,237 @@ +name: Template builds + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: + pull_request: + schedule: + # Re-resolve compatible dependency updates weekly and make sure they still build. + - cron: "0 7 * * 1" + workflow_dispatch: + +permissions: + contents: read + +jobs: + node: + name: Node.js and TypeScript + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: "22" + - name: Build templates + run: | + set -euo pipefail + for dir in node/*/ node-typescript/*/; do + [ -f "${dir}package.json" ] || continue + echo "::group::${dir%/}" + npm ci --prefix "$dir" --ignore-scripts --no-audit --no-fund + if [ -f "${dir}tsconfig.json" ]; then + npm run --prefix "$dir" build + else + find "${dir}src" -type f -name '*.js' -exec node --check {} \; + fi + echo "::endgroup::" + done + + bun: + name: Bun + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 + with: + bun-version: latest + - name: Build templates + run: | + set -euo pipefail + for dir in bun/*/; do + [ -f "${dir}package.json" ] || continue + echo "::group::${dir%/}" + (cd "$dir" && bun install --frozen-lockfile --ignore-scripts && bun -e "await import('./src/main.ts')") + echo "::endgroup::" + done + + python: + name: Python ${{ matrix.name }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - name: "3.9" + version: "3.9" + templates: "python" + - name: "3.12 MCP" + version: "3.12" + templates: "mcp" + - name: "3.11 ML" + version: "3.11" + templates: "python-ml" + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: ${{ matrix.version }} + - name: Build templates + env: + TEMPLATE_SET: ${{ matrix.templates }} + run: | + set -euo pipefail + if [ "$TEMPLATE_SET" = mcp ]; then + dirs=(python/mcp-server/) + elif [ "$TEMPLATE_SET" = python-ml ]; then + dirs=(python-ml/*/) + else + dirs=(python/*/) + fi + for dir in "${dirs[@]}"; do + [ -f "${dir}requirements.txt" ] || continue + if [ "$TEMPLATE_SET" = python ] && [ "$dir" = python/mcp-server/ ]; then continue; fi + echo "::group::${dir%/}" + rm -rf /tmp/template-venv + python -m venv /tmp/template-venv + /tmp/template-venv/bin/pip install --disable-pip-version-check -r "${dir}requirements.txt" + /tmp/template-venv/bin/pip check + /tmp/template-venv/bin/python -m compileall -q "${dir}src" + echo "::endgroup::" + done + + php: + name: PHP + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2 + with: + php-version: "8.3" + tools: composer + - name: Build and load templates + run: | + set -euo pipefail + for dir in php/*/; do + [ -f "${dir}composer.json" ] || continue + echo "::group::${dir%/}" + composer install --working-dir="$dir" --no-interaction --no-progress --optimize-autoloader + find "${dir}src" -type f -name '*.php' -exec php -l {} \; + php .github/scripts/load-composer-classes.php "$dir" + echo "::endgroup::" + done + + ruby: + name: Ruby + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: ruby/setup-ruby@a30dfa457ad68707b8b910ac3a244714b61c0626 # v1.320.0 + with: + ruby-version: "3.1" + bundler: latest + - name: Build templates + run: | + set -euo pipefail + for dir in ruby/*/; do + [ -f "${dir}Gemfile" ] || continue + echo "::group::${dir%/}" + (cd "$dir" && bundle install && find lib -type f -name '*.rb' -exec ruby -c {} \;) + echo "::endgroup::" + done + + dart: + name: Dart + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: dart-lang/setup-dart@65eb853c7ba17dde3be364c3d2858773e7144260 # v1.7.2 + with: + sdk: stable + - name: Build templates + run: | + set -euo pipefail + for dir in dart/*/; do + [ -f "${dir}pubspec.yaml" ] || continue + echo "::group::${dir%/}" + (cd "$dir" && dart pub get && dart analyze) + echo "::endgroup::" + done + + deno: + name: Deno + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: denoland/setup-deno@22d081ff2d3a40755e97629de92e3bcbfa7cf2ed # v2 + with: + deno-version: v2.x + - name: Build templates + run: | + set -euo pipefail + for dir in deno/*/; do + echo "::group::${dir%/}" + (cd "$dir" && deno check src/main.ts) + echo "::endgroup::" + done + + go: + name: Go + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 + with: + go-version: "1.26.6" + cache: false + - name: Build templates + run: for dir in go/*/; do [ ! -f "${dir}go.mod" ] || (cd "$dir" && go build ./...); done + + rust: + name: Rust + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: dtolnay/rust-toolchain@bd41891a8e7f4b8649f6d684415e1a6155fe4e22 # 1.83.0 + - name: Build templates + run: for dir in rust/*/; do [ ! -f "${dir}Cargo.toml" ] || (cd "$dir" && cargo build --locked); done + + runtime-builds: + name: ${{ matrix.template }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - template: cpp/starter + image: openruntimes/cpp:v4-17 + entrypoint: src/main.cc + - template: dotnet/starter + image: openruntimes/dotnet:v4-6.0 + entrypoint: src/Index.cs + - template: java/starter + image: openruntimes/java:v4-17.0 + entrypoint: src/Main.java + - template: kotlin/starter + image: openruntimes/kotlin:v4-1.8 + entrypoint: src/Main.kt + - template: kotlin/sync-with-meilisearch + image: openruntimes/kotlin:v4-1.8 + entrypoint: src/Main.kt + - template: swift/starter + image: openruntimes/swift:v5-6.2 + entrypoint: Sources/index.swift + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Build in Open Runtimes + env: + IMAGE: ${{ matrix.image }} + ENTRYPOINT: ${{ matrix.entrypoint }} + TEMPLATE: ${{ matrix.template }} + run: | + docker run --rm \ + -e OPEN_RUNTIMES_ENTRYPOINT="$ENTRYPOINT" \ + -v "$PWD/$TEMPLATE:/mnt/code" \ + "$IMAGE" sh helpers/build.sh + rm -f "$TEMPLATE/code.tar.gz" diff --git a/php/email-contact-form/src/index.php b/php/email-contact-form/src/index.php index 85413867..e7ce93f4 100644 --- a/php/email-contact-form/src/index.php +++ b/php/email-contact-form/src/index.php @@ -1,8 +1,8 @@ 'invalid-request', diff --git a/php/starter/composer.json b/php/starter/composer.json index 1795e06d..f54dbe53 100644 --- a/php/starter/composer.json +++ b/php/starter/composer.json @@ -3,6 +3,6 @@ "type": "library", "require": { "php": ">=8.0.0", - "appwrite/appwrite": "^17.4.1" + "appwrite/appwrite": "^18.0.1" } } diff --git a/php/starter/composer.lock b/php/starter/composer.lock index 19c7528c..4fbd8773 100644 --- a/php/starter/composer.lock +++ b/php/starter/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e0ef301e94ff2127c00897dfb64613fb", + "content-hash": "abb8f33d9f904ad668c641fa940e8b5e", "packages": [ { "name": "appwrite/appwrite", - "version": "17.4.1", + "version": "18.0.1", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-for-php.git", - "reference": "3aa44b77a1f25aef6c14b14d4e5965b88903a4a7" + "reference": "950112a73c05297e23fa2eddb68f4ce8a6024e03" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-for-php/zipball/3aa44b77a1f25aef6c14b14d4e5965b88903a4a7", - "reference": "3aa44b77a1f25aef6c14b14d4e5965b88903a4a7", + "url": "https://api.github.com/repos/appwrite/sdk-for-php/zipball/950112a73c05297e23fa2eddb68f4ce8a6024e03", + "reference": "950112a73c05297e23fa2eddb68f4ce8a6024e03", "shasum": "" }, "require": { @@ -26,7 +26,7 @@ "php": ">=7.1.0" }, "require-dev": { - "mockery/mockery": "^1.6.6", + "mockery/mockery": "^1.6.12", "phpunit/phpunit": "^10" }, "type": "library", @@ -43,10 +43,10 @@ "support": { "email": "team@appwrite.io", "issues": "https://github.com/appwrite/sdk-for-php/issues", - "source": "https://github.com/appwrite/sdk-for-php/tree/17.4.1", + "source": "https://github.com/appwrite/sdk-for-php/tree/18.0.1", "url": "https://appwrite.io/support" }, - "time": "2025-10-09T11:03:41+00:00" + "time": "2025-11-10T12:50:46+00:00" } ], "packages-dev": [], @@ -59,5 +59,5 @@ "php": ">=8.0.0" }, "platform-dev": {}, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } From f9640699f5ceb84f599aba224ecf007985d78ee8 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Wed, 19 Aug 2026 11:21:44 +0530 Subject: [PATCH 2/2] fix: address build and import validation failures --- .github/scripts/load-composer-classes.php | 24 ++++++++++++++++++++++- rust/starter/Cargo.lock | 6 +++--- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/.github/scripts/load-composer-classes.php b/.github/scripts/load-composer-classes.php index 1a15295e..7cea4ccf 100644 --- a/.github/scripts/load-composer-classes.php +++ b/.github/scripts/load-composer-classes.php @@ -15,6 +15,28 @@ require $template . '/vendor/autoload.php'; +function importedClasses(string $declaration): array +{ + $prefix = ''; + if (str_contains($declaration, '{')) { + [$prefix, $declaration] = explode('{', $declaration, 2); + $declaration = strstr($declaration, '}', true) ?: ''; + } + + $classes = []; + foreach (explode(',', $declaration) as $import) { + $import = trim($import); + if ($import === '' || preg_match('/^(?:function|const)\s/i', $import)) { + continue; + } + + $class = preg_split('/\s+as\s+/i', $import)[0]; + $classes[] = trim($prefix) . $class; + } + + return $classes; +} + $symbols = []; foreach (glob($template . '/src/*.php') ?: [] as $source) { $contents = file_get_contents($source); @@ -25,7 +47,7 @@ preg_match_all('/^\s*use\s+(?!function\s|const\s)([^;]+);/m', $contents, $matches); foreach ($matches[1] as $declaration) { - $symbols[] = preg_split('/\s+as\s+/i', trim($declaration))[0]; + array_push($symbols, ...importedClasses($declaration)); } } diff --git a/rust/starter/Cargo.lock b/rust/starter/Cargo.lock index a5643a17..6017a671 100644 --- a/rust/starter/Cargo.lock +++ b/rust/starter/Cargo.lock @@ -127,7 +127,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -245,9 +245,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.13" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +checksum = "a9f37a958b41b3b19ee2707c06439c0e9e547e847223eb791ecb0cb821c65e27" dependencies = [ "atomic-waker", "bytes",