Fix bin/publish: copy docs.dist from project root

Fix bin/publish: use correct .env path for rspade_system
Fix bin/publish script: prevent grep exit code 1 from terminating script

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2025-10-21 02:08:33 +00:00
commit f6fac6c4bc
79758 changed files with 10547827 additions and 0 deletions

21
vendor/spatie/browsershot/LICENSE.md vendored Executable file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) Spatie bvba <info@spatie.be>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

118
vendor/spatie/browsershot/README.md vendored Executable file
View File

@@ -0,0 +1,118 @@
<div align="left">
<a href="https://spatie.be/open-source?utm_source=github&utm_medium=banner&utm_campaign=browsershot">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://spatie.be/packages/header/browsershot/html/dark.webp">
<img alt="Logo for Browsershot" src="https://spatie.be/packages/header/browsershot/html/light.webp" height="190">
</picture>
</a>
<h1>Render web pages to an image or PDF with Puppeteer</h1>
[![Latest Version](https://img.shields.io/github/release/spatie/browsershot.svg?style=flat-square)](https://github.com/spatie/browsershot/releases)
[![MIT Licensed](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md)
[![run-tests](https://img.shields.io/github/actions/workflow/status/spatie/browsershot/run-tests.yml?label=tests&style=flat-square)](https://github.com/spatie/browsershot/actions)
[![Total Downloads](https://img.shields.io/packagist/dt/spatie/browsershot.svg?style=flat-square)](https://packagist.org/packages/spatie/browsershot)
</div>
The package can convert a web page to an image or PDF. The conversion is done behind the scenes by [Puppeteer](https://github.com/GoogleChrome/puppeteer) which runs a headless version of Google Chrome.
Here's a quick example:
```php
use Spatie\Browsershot\Browsershot;
// an image will be saved
Browsershot::url('https://example.com')->save($pathToImage);
```
It will save a PDF if the path passed to the `save` method has a `pdf` extension.
```php
// a pdf will be saved
Browsershot::url('https://example.com')->save('example.pdf');
```
You can also use an arbitrary html input, simply replace the `url` method with `html`:
```php
Browsershot::html('<h1>Hello world!!</h1>')->save('example.pdf');
```
If your HTML input is already in a file locally use the :
```php
Browsershot::htmlFromFilePath('/local/path/to/file.html')->save('example.pdf');
```
Browsershot also can get the body of an html page after JavaScript has been executed:
```php
Browsershot::url('https://example.com')->bodyHtml(); // returns the html of the body
```
If you wish to retrieve an array list with all of the requests that the page triggered you can do so:
```php
$requests = Browsershot::url('https://example.com')
->triggeredRequests();
foreach ($requests as $request) {
$url = $request['url']; //https://example.com/
}
```
To use Chrome's new [headless mode](https://developers.google.com/web/updates/2017/04/headless-chrome) pass the `newHeadless` method:
```php
Browsershot::url('https://example.com')->newHeadless()->save($pathToImage);
```
## Support us
Learn how to create a package like this one, by watching our premium video course:
[![Laravel Package training](https://spatie.be/github/package-training.jpg)](https://laravelpackage.training)
We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us).
We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards).
## Documentation
All documentation is available [on our documentation site](https://spatie.be/docs/browsershot).
## Testing
For running the testsuite, you'll need to have Puppeteer installed. Pleaser refer to the Browsershot requirements [here](https://spatie.be/docs/browsershot/v4/requirements). Usually `npm -g i puppeteer` will do the trick.
Additionally, you'll need the `pdftotext` CLI which is part of the poppler-utils package. More info can be found in in the [spatie/pdf-to-text readme](https://github.com/spatie/pdf-to-text?tab=readme-ov-file#requirements). Usually `brew install poppler-utils` will suffice.
Finally run the tests with:
```bash
composer test
```
## Contributing
Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details.
## Security
If you've found a bug regarding security please mail [security@spatie.be](mailto:security@spatie.be) instead of using the issue tracker.
## Alternatives
If you're not able to install Node and Puppeteer, take a look at [v2 of browsershot](https://github.com/spatie/browsershot/tree/2.4.1), which uses Chrome headless CLI to take a screenshot. `v2` is not maintained anymore, but should work pretty well.
If using headless Chrome does not work for you take a look at at `v1` of this package which uses the abandoned `PhantomJS` binary.
## Credits
- [Freek Van der Herten](https://github.com/freekmurze)
- [All Contributors](../../contributors)
## License
The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

445
vendor/spatie/browsershot/bin/browser.cjs vendored Executable file
View File

@@ -0,0 +1,445 @@
const fs = require('fs');
const URL = require('url').URL;
const URLParse = require('url').parse;
const [, , ...args] = process.argv;
/**
* There are two ways for Browsershot to communicate with puppeteer:
* - By giving a options JSON dump as an argument
* - Or by providing a temporary file with the options JSON dump,
* the path to this file is then given as an argument with the flag -f
*/
const request = args[0].startsWith('-f ')
? JSON.parse(fs.readFileSync(new URL(args[0].substring(3))))
: JSON.parse(args[0]);
const requestsList = [];
const redirectHistory = [];
const consoleMessages = [];
const failedRequests = [];
const pageErrors = [];
const getOutput = async (request, page = null) => {
let output = {
requestsList,
consoleMessages,
failedRequests,
redirectHistory,
pageErrors,
};
if (
![
'requestsList',
'consoleMessages',
'failedRequests',
'redirectHistory',
'pageErrors',
].includes(request.action) &&
page
) {
if (request.action == 'evaluate') {
output.result = await page.evaluate(request.options.pageFunction);
} else {
const result = await page[request.action](request.options);
// Ignore output result when saving to a file
output.result = request.options.path
? ''
: (result instanceof Uint8Array ? Buffer.from(result) : result).toString('base64');
}
}
if (page) {
return JSON.stringify(output);
}
// this will allow adding additional error info (only reach this point when there's an exception)
return output;
};
const callChrome = async pup => {
let browser;
let page;
let remoteInstance;
const puppet = (pup || require('puppeteer'));
try {
if (request.options.remoteInstanceUrl || request.options.browserWSEndpoint ) {
// default options
let options = {
acceptInsecureCerts: request.options.acceptInsecureCerts
};
// choose only one method to connect to the browser instance
if ( request.options.remoteInstanceUrl ) {
options.browserURL = request.options.remoteInstanceUrl;
} else if ( request.options.browserWSEndpoint ) {
options.browserWSEndpoint = request.options.browserWSEndpoint;
}
try {
browser = await puppet.connect( options );
remoteInstance = true;
} catch (exception) { /** does nothing. fallbacks to launching a chromium instance */}
}
if (!browser) {
browser = await puppet.launch({
headless: request.options.newHeadless ? true : 'shell',
acceptInsecureCerts: request.options.acceptInsecureCerts,
executablePath: request.options.executablePath,
args: request.options.args || [],
pipe: request.options.pipe || false,
env: {
...(request.options.env || {}),
...process.env
},
protocolTimeout: request.options.protocolTimeout ?? 30000,
});
}
page = await browser.newPage();
if (request.options && request.options.disableJavascript) {
await page.setJavaScriptEnabled(false);
}
await page.setRequestInterception(true);
const contentUrl = request.options.contentUrl;
const parsedContentUrl = contentUrl ? contentUrl.replace(/\/$/, "") : undefined;
let pageContent;
if (contentUrl) {
pageContent = fs.readFileSync(request.url.replace('file://', ''));
request.url = contentUrl;
}
page.on('console', (message) =>
consoleMessages.push({
type: message.type(),
message: message.text(),
location: message.location(),
stackTrace: message.stackTrace(),
})
);
page.on('pageerror', (msg) => {
pageErrors.push({
name: msg?.name || 'unknown error',
message: msg?.message || msg?.toString() || 'null'
});
});
page.on('response', function (response) {
const frame = response.request().frame();
if (response.request().isNavigationRequest() && frame && frame.parentFrame() === null) {
redirectHistory.push({
url: response.request().url(),
status: response.status(),
reason: response.statusText(),
headers: response.headers()
})
}
if (response.status() >= 200 && response.status() <= 399) {
return;
}
failedRequests.push({
status: response.status(),
url: response.url(),
});
})
page.on('request', interceptedRequest => {
var headers = interceptedRequest.headers();
if (!request.options || !request.options.disableCaptureURLS) {
requestsList.push({
url: interceptedRequest.url(),
});
}
if (request.options && request.options.disableImages) {
if (interceptedRequest.resourceType() === 'image') {
interceptedRequest.abort();
return;
}
}
if (request.options && request.options.blockDomains) {
const hostname = URLParse(interceptedRequest.url()).hostname;
if (request.options.blockDomains.includes(hostname)) {
interceptedRequest.abort();
return;
}
}
if (request.options && request.options.blockUrls) {
for (const element of request.options.blockUrls) {
if (interceptedRequest.url().indexOf(element) >= 0) {
interceptedRequest.abort();
return;
}
}
}
if (request.options && request.options.disableRedirects) {
if (interceptedRequest.isNavigationRequest() && interceptedRequest.redirectChain().length) {
interceptedRequest.abort();
return
}
}
if (request.options && request.options.extraNavigationHTTPHeaders) {
// Do nothing in case of non-navigation requests.
if (interceptedRequest.isNavigationRequest()) {
headers = Object.assign({}, headers, request.options.extraNavigationHTTPHeaders);
}
}
if (pageContent) {
const interceptedUrl = interceptedRequest.url().replace(/\/$/, "");
// if content url matches the intercepted request url, will return the content fetched from the local file system
if (interceptedUrl === parsedContentUrl) {
interceptedRequest.respond({
headers,
body: pageContent,
});
return;
}
}
if (request.postParams) {
const postParamsArray = request.postParams;
const queryString = Object.keys(postParamsArray)
.map(key => `${key}=${postParamsArray[key]}`)
.join('&');
interceptedRequest.continue({
method: "POST",
postData: queryString,
headers: {
...interceptedRequest.headers(),
"Content-Type": "application/x-www-form-urlencoded"
}
});
return;
}
interceptedRequest.continue({ headers });
});
if (request.options && request.options.dismissDialogs) {
page.on('dialog', async dialog => {
await dialog.dismiss();
});
}
if (request.options && request.options.userAgent) {
await page.setUserAgent(request.options.userAgent);
}
if (request.options && request.options.device) {
const devices = puppet.KnownDevices;
const device = devices[request.options.device];
await page.emulate(device);
}
if (request.options && request.options.emulateMedia) {
await page.emulateMediaType(request.options.emulateMedia);
}
if (request.options && request.options.emulateMediaFeatures) {
await page.emulateMediaFeatures(JSON.parse(request.options.emulateMediaFeatures));
}
if (request.options && request.options.viewport) {
await page.setViewport(request.options.viewport);
}
if (request.options && request.options.extraHTTPHeaders) {
await page.setExtraHTTPHeaders(request.options.extraHTTPHeaders);
}
if (request.options && request.options.authentication) {
await page.authenticate(request.options.authentication);
}
if (request.options && request.options.cookies) {
await page.setCookie(...request.options.cookies);
}
if (request.options && request.options.timeout) {
await page.setDefaultNavigationTimeout(request.options.timeout);
}
const requestOptions = {};
if (request.options && request.options.networkIdleTimeout) {
requestOptions.waitUntil = 'networkidle';
requestOptions.networkIdleTimeout = request.options.networkIdleTimeout;
} else if (request.options && request.options.waitUntil) {
requestOptions.waitUntil = request.options.waitUntil;
}
const response = await page.goto(request.url, requestOptions);
if (request.options.preventUnsuccessfulResponse) {
const status = response.status()
if (status >= 400 && status < 600) {
throw {type: "UnsuccessfulResponse", status};
}
}
if (request.options && request.options.disableImages) {
await page.evaluate(() => {
let images = document.getElementsByTagName('img');
while (images.length > 0) {
images[0].parentNode.removeChild(images[0]);
}
});
}
if (request.options && request.options.types) {
for (let i = 0, len = request.options.types.length; i < len; i++) {
let typeOptions = request.options.types[i];
await page.type(typeOptions.selector, typeOptions.text, {
'delay': typeOptions.delay,
});
}
}
if (request.options && request.options.selects) {
for (let i = 0, len = request.options.selects.length; i < len; i++) {
let selectOptions = request.options.selects[i];
await page.select(selectOptions.selector, selectOptions.value);
}
}
if (request.options && request.options.clicks) {
for (let i = 0, len = request.options.clicks.length; i < len; i++) {
let clickOptions = request.options.clicks[i];
await page.click(clickOptions.selector, {
'button': clickOptions.button,
'clickCount': clickOptions.clickCount,
'delay': clickOptions.delay,
});
}
}
if (request.options && request.options.addStyleTag) {
await page.addStyleTag(JSON.parse(request.options.addStyleTag));
}
if (request.options && request.options.addScriptTag) {
await page.addScriptTag(JSON.parse(request.options.addScriptTag));
}
if (request.options.delay) {
await new Promise(r => setTimeout(r, request.options.delay));
}
if (request.options.initialPageNumber) {
await page.evaluate((initialPageNumber) => {
window.pageStart = initialPageNumber;
const style = document.createElement('style');
style.type = 'text/css';
style.innerHTML = '.empty-page { page-break-after: always; visibility: hidden; }';
document.getElementsByTagName('head')[0].appendChild(style);
const emptyPages = Array.from({length: window.pageStart}).map(() => {
const emptyPage = document.createElement('div');
emptyPage.className = "empty-page";
emptyPage.textContent = "empty";
return emptyPage;
});
document.body.prepend(...emptyPages);
}, request.options.initialPageNumber);
}
if (request.options.function) {
let functionOptions = {
polling: request.options.functionPolling,
timeout: request.options.functionTimeout || request.options.timeout
};
await page.waitForFunction(request.options.function, functionOptions);
}
if (request.options.waitForSelector) {
await page.waitForSelector(request.options.waitForSelector, (request.options.waitForSelectorOptions ? request.options.waitForSelectorOptions : undefined));
}
if (request.options.selector) {
var element;
const index = request.options.selectorIndex || 0;
if(index){
element = await page.$$(request.options.selector);
if(!element.length || typeof element[index] === 'undefined'){
element = null;
}else{
element = element[index];
}
}else{
element = await page.$(request.options.selector);
}
if (element === null) {
throw {type: 'ElementNotFound'};
}
request.options.clip = await element.boundingBox();
}
console.log(await getOutput(request, page));
if (remoteInstance && page) {
await page.close();
}
await (remoteInstance ? browser.disconnect() : browser.close());
} catch (exception) {
if (browser) {
if (remoteInstance && page) {
await page.close();
}
await (remoteInstance ? browser.disconnect() : browser.close());
}
const output = await getOutput(request);
if (exception.type === 'UnsuccessfulResponse') {
output.exception = exception.toString();
console.error(exception.status);
console.log(JSON.stringify(output));
process.exit(3);
}
output.exception = exception.toString();
console.error(exception);
console.log(JSON.stringify(output));
if (exception.type === 'ElementNotFound') {
process.exit(2);
}
process.exit(1);
}
};
if (require.main === module) {
callChrome();
}
exports.callChrome = callChrome;

56
vendor/spatie/browsershot/composer.json vendored Executable file
View File

@@ -0,0 +1,56 @@
{
"name": "spatie/browsershot",
"description": "Convert a webpage to an image or pdf using headless Chrome",
"homepage": "https://github.com/spatie/browsershot",
"keywords": [
"convert",
"webpage",
"image",
"pdf",
"screenshot",
"chrome",
"headless",
"puppeteer"
],
"license": "MIT",
"authors": [
{
"name": "Freek Van der Herten",
"email": "freek@spatie.be",
"homepage": "https://github.com/freekmurze",
"role": "Developer"
}
],
"require": {
"php": "^8.2",
"spatie/temporary-directory": "^2.0",
"symfony/process": "^6.0|^7.0",
"ext-json": "*",
"ext-fileinfo": "*"
},
"require-dev": {
"pestphp/pest": "^3.0",
"spatie/image": "^3.6",
"spatie/pdf-to-text": "^1.52",
"spatie/phpunit-snapshot-assertions": "^4.2.3|^5.0"
},
"autoload": {
"psr-4": {
"Spatie\\Browsershot\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"Spatie\\Browsershot\\Test\\": "tests"
}
},
"scripts": {
"test": "vendor/bin/pest"
},
"config": {
"sort-packages": true,
"allow-plugins": {
"pestphp/pest-plugin": true
}
}
}

1233
vendor/spatie/browsershot/src/Browsershot.php vendored Executable file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,131 @@
<?php
namespace Spatie\Browsershot;
/**
* Class with all outputs generated by puppeteer
*
* All present data is always relative to the last browser call.
*
* This object contains:
*
* - consoleMessages: messages generated with console calls
* - requestsList: list of all requests made
* - failedRequests: list of all failed requests
* - result: result of the last operation called
* - exception: string representation of the exception generated, if any
* - pageErrors: list of all page errors generated during the current command
* - redirectHistory: list of all redirect in the page
*/
class ChromiumResult
{
protected string $result;
protected ?string $exception;
/** @var null|array{
* type: string,
* message: string,
* location: array,
* stackTrace: string
* }
*/
protected ?array $consoleMessages;
/** @var null|array{url: string} */
protected ?array $requestsList;
/** * @var null|array{
* status: int,
* url: string
* }
*/
protected ?array $failedRequests;
/** * @var null|array{
* name: string,
* message: string
* }
*/
protected ?array $pageErrors;
/** @var null|array{
* url: string,
* status: int,
* statusText: string,
* headers: array
* }
*/
protected ?array $redirectHistory;
public function __construct(?array $output)
{
$this->result = $output['result'] ?? '';
$this->exception = $output['exception'] ?? null;
$this->consoleMessages = $output['consoleMessages'] ?? null;
$this->requestsList = $output['requestsList'] ?? null;
$this->failedRequests = $output['failedRequests'] ?? null;
$this->pageErrors = $output['pageErrors'] ?? null;
$this->redirectHistory = $output['redirectHistory'] ?? null;
}
public function getResult(): string
{
return $this->result;
}
public function getException(): ?string
{
return $this->exception;
}
/** @return null|array{
* type: string,
* message: string,
* location: array,
* stackTrace: string
* }
*/
public function getConsoleMessages(): ?array
{
return $this->consoleMessages;
}
/**
* @return null|array{url: string}
*/
public function getRequestsList(): ?array
{
return $this->requestsList;
}
/**
* @return null|array{status: int, url: string}
*/
public function getFailedRequests(): ?array
{
return $this->failedRequests;
}
/** @return null|array{
* name: string,
* message: string
* }
*/
public function getPageErrors(): ?array
{
return $this->pageErrors;
}
/** @return null|array{
* url: string,
* status: int,
* statusText: string,
* headers: array
* }
*/
public function getRedirectHistory(): ?array
{
return $this->redirectHistory;
}
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Spatie\Browsershot\Enums;
enum Polling: string
{
case RequestAnimationFrame = 'raf';
case Mutation = 'mutation';
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Spatie\Browsershot\Exceptions;
use Exception;
class CouldNotTakeBrowsershot extends Exception
{
public static function chromeOutputEmpty(string $screenShotPath, string $output, array $command = []): static
{
$command = json_encode($command);
$message = <<<CONSOLE
For some reason Chrome did not write a file at `{$screenShotPath}`.
Command
=======
{$command}
Output
======
{$output}
CONSOLE;
return new static($message);
}
public static function outputFileDidNotHaveAnExtension(string $path): static
{
return new static("The given path `{$path}` did not contain an extension. Please append an extension.");
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace Spatie\Browsershot\Exceptions;
use Exception;
class ElementNotFound extends Exception
{
public static function make(string $selector): static
{
return new static("The given selector `{$selector} did not match any elements");
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace Spatie\Browsershot\Exceptions;
use Exception;
class FileDoesNotExistException extends Exception
{
public static function make(string $file): static
{
return new static("The file `{$file}` does not exist");
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace Spatie\Browsershot\Exceptions;
use Exception;
class FileUrlNotAllowed extends Exception
{
public static function make(): static
{
return new static('An URL is not allow to start with file:// or file:/');
}
public static function urlCannotBeParsed(string $url): static
{
return new static("The given URL `{$url}` is not a valid URL");
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace Spatie\Browsershot\Exceptions;
use Exception;
class HtmlIsNotAllowedToContainFile extends Exception
{
public static function make(): static
{
return new static('The specified HTML contains `file://` or `file:/`. This is not allowed.');
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace Spatie\Browsershot\Exceptions;
use Exception;
class UnsuccessfulResponse extends Exception
{
public static function make(string $url, string|int $code): static
{
return new static("The given url `{$url}` responds with code {$code}");
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace Spatie\Browsershot;
use Composer\InstalledVersions;
use Exception;
use Spatie\Image\Image;
class ImageManipulations
{
protected array $manipulations = [];
public function __call(string $method, array $parameters): self
{
$this->addManipulation($method, $parameters);
return $this;
}
public function addManipulation(string $name, array $parameters = []): self
{
$this->manipulations[$name] = $parameters;
return $this;
}
public function apply(string $path): void
{
$this->ensureImageDependencyIsInstalled();
$image = Image::load($path);
foreach ($this->manipulations as $manipulationName => $parameters) {
$image->$manipulationName(...$parameters);
}
$image->save($path);
}
public function isEmpty(): bool
{
return count($this->manipulations) === 0;
}
public function ensureImageDependencyIsInstalled(): void
{
if (! InstalledVersions::isInstalled('spatie/image')) {
throw new Exception('The spatie/image package is required to perform image manipulations. Please install it by running `composer require spatie/image`');
}
$installedVersion = InstalledVersions::getVersion('spatie/image');
if (version_compare($installedVersion, '3.0.0', '<')) {
throw new Exception("The spatie/image package must be at least version 3.0.0 to perform image manipulations. Your current version is `{$installedVersion}`");
}
}
}