diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8be3fe6..f4d5c5c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,6 +14,12 @@ jobs: php-version: ['8.1', '8.2', '8.3', '8.4'] steps: - uses: actions/checkout@v5 + - name: Set up Node + uses: actions/setup-node@v5 + with: + node-version: 20 + cache: npm + - run: npm ci - name: Set up PHP uses: shivammathur/setup-php@v2 with: diff --git a/package-lock.json b/package-lock.json index dd4f7af..c7d0d76 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "@castleio/castle-js": "^2.8.4" + "@castleio/castle-js": "^2.8.5" }, "devDependencies": { "tailwindcss": "^3.4.19" @@ -29,9 +29,9 @@ } }, "node_modules/@castleio/castle-js": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/@castleio/castle-js/-/castle-js-2.8.4.tgz", - "integrity": "sha512-RV5iEURaNyDpJpmKIPNHlKcU35/4wVAh1xyjDnVzM7sUz0y7UHJocviGBS3dmvipoddgIqMn0CGXSi8Bsy8FNA==", + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/@castleio/castle-js/-/castle-js-2.8.5.tgz", + "integrity": "sha512-HiFmmp6HQgk64jCsAYov9Dnc+rNWrcb2u9PA9c3phfelQ5qFNt1fRcLY+NUgnZZqAMoR3fRqL8qYgOMkZjfZ4Q==", "license": "MIT" }, "node_modules/@jridgewell/gen-mapping": { diff --git a/package.json b/package.json index 66f5a68..d4910fb 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "description": "A small PHP app demonstrating key Castle workflows on top of the Castle PHP SDK.", "scripts": { "build:css": "tailwindcss -i ./src/tailwind.css -o ./static/styles.css --minify", - "watch:css": "tailwindcss -i ./src/tailwind.css -o ./static/styles.css --watch" + "watch:css": "tailwindcss -i ./src/tailwind.css -o ./static/styles.css --watch", + "postinstall": "node scripts/ensure-castle-umd.js" }, "repository": { "type": "git", @@ -12,7 +13,7 @@ }, "license": "MIT", "dependencies": { - "@castleio/castle-js": "^2.8.4" + "@castleio/castle-js": "^2.8.5" }, "devDependencies": { "tailwindcss": "^3.4.19" diff --git a/scripts/ensure-castle-umd.js b/scripts/ensure-castle-umd.js new file mode 100644 index 0000000..6bd1f7f --- /dev/null +++ b/scripts/ensure-castle-umd.js @@ -0,0 +1,15 @@ +const fs = require('fs'); +const path = require('path'); + +const dist = path.join(__dirname, '..', 'node_modules', '@castleio', 'castle-js', 'dist'); +const dest = path.join(dist, 'castle.umd.js'); +if (!fs.existsSync(dist) || fs.existsSync(dest)) { + process.exit(0); +} + +const source = fs.readdirSync(dist).find((name) => ( + name.startsWith('castle.') && name.endsWith('.js') && name !== 'castle.js' +)); +if (source) { + fs.copyFileSync(path.join(dist, source), dest); +} diff --git a/src/helpers.php b/src/helpers.php index a0e978a..34ab5f9 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -202,14 +202,23 @@ function project_root(): string return dirname(__DIR__); } -// Serve the Castle browser SDK straight from the npm install (node_modules) -// instead of vendoring it into the repo. -function serve_castle_js(string $filename): void +function resolve_castle_js(string $filename): ?string { - $dir = project_root() . '/node_modules/@castleio/castle-js/dist'; + $dir = realpath(project_root() . '/node_modules/@castleio/castle-js/dist'); + if ($dir === false) { + return null; + } $path = realpath($dir . '/' . $filename); + if ($path !== false && str_starts_with($path, $dir . DIRECTORY_SEPARATOR) && is_file($path)) { + return $path; + } + return null; +} - if ($path === false || strpos($path, realpath($dir) ?: $dir) !== 0 || !is_file($path)) { +function serve_castle_js(string $filename): void +{ + $path = resolve_castle_js($filename); + if ($path === null) { http_response_code(404); echo 'Not found'; return; diff --git a/static/app.js b/static/app.js index 7318be6..29ff9f8 100644 --- a/static/app.js +++ b/static/app.js @@ -17,9 +17,14 @@ async function postJSON(url, data) { // Resolve a Castle request token, falling back gracefully if the browser SDK // is unavailable (e.g. no publishable key configured). +function castleClient() { + return window.__castle || window.Castle; +} + function withRequestToken(callback) { - if (window.Castle && typeof Castle.createRequestToken === "function") { - Castle.createRequestToken() + var sdk = castleClient(); + if (sdk && typeof sdk.createRequestToken === "function") { + sdk.createRequestToken() .then(callback) .catch(function (err) { console.error("Castle.createRequestToken failed", err); diff --git a/tests/PagesTest.php b/tests/PagesTest.php index 00cbcab..b1adbbf 100644 --- a/tests/PagesTest.php +++ b/tests/PagesTest.php @@ -9,6 +9,14 @@ public function testHomeRenders(): void $html = render_page('home', default_params()); $this->assertStringContainsStringIgnoringCase('assertStringContainsString('Castle workflows demo', $html); + $this->assertStringContainsString('/vendor/castle-js/castle.umd.js', $html); + } + + public function testResolveCastleJsServesNpmInstall(): void + { + $path = resolve_castle_js('castle.umd.js'); + $this->assertNotNull($path); + $this->assertFileExists($path); } /** diff --git a/views/layout.php b/views/layout.php index 9b78186..269eae8 100644 --- a/views/layout.php +++ b/views/layout.php @@ -18,10 +18,18 @@ - + +