knuckleswtf / scribe

Generate API documentation for humans from your Laravel codebase.✍
https://scribe.knuckles.wtf/laravel/
MIT License
1.7k stars 303 forks source link

Documation not loading on browser #813

Open engrnand opened 7 months ago

engrnand commented 7 months ago

Scribe version

4

PHP version

8.3

Framework

Laravel

Framework version

10

Scribe config

<?php

use Knuckles\Scribe\Extracting\Strategies;

return [

    'theme' => 'default',

    /*
     * The HTML <title> for the generated documentation. If this is empty, Scribe will infer it from config('app.name').
     */
    'title' => "Laravel",

    /*
     * A short description of your API. Will be included in the docs webpage, Postman collection and OpenAPI spec.
     */
    'description' => '',

    /*
     * The base URL displayed in the docs. If this is empty, Scribe will use the value of config('app.url').
     */
    'base_url' => env('APP_URL', 'https://laravel.local'),

    /*
     * Tell Scribe what routes to generate documentation for.
     * Each group contains rules defining which routes should be included ('match', 'include' and 'exclude' sections)
     * and settings which should be applied to them ('apply' section).
     */
    'routes' => [
        [
            /*
             * Specify conditions to determine what routes will be a part of this group.
             * A route must fulfill ALL conditions to be included.
             */
            'match' => [
                /*
                 * Match only routes whose paths match this pattern (use * as a wildcard to match any characters). Example: 'users/*'.
                 */
                'prefixes' => ['api/*'],

                /*
                 * Match only routes whose domains match this pattern (use * as a wildcard to match any characters). Example: 'api.*'.
                 */
                'domains' => ['*'],

                /*
                 * [Dingo router only] Match only routes registered under this version. Wildcards are not supported.
                 */
                'versions' => ['v1'],
            ],

            /*
             * Include these routes even if they did not match the rules above.
             * The route can be referenced by name or path here. Wildcards are supported.
             */
            'include' => [
                // 'users.index', 'healthcheck*'
            ],

            /*
             * Exclude these routes even if they matched the rules above.
             * The route can be referenced by name or path here. Wildcards are supported.
             */
            'exclude' => [
                'api/test*', 'api/firebase/user_sync'
            ],

            /*
             * Settings to be applied to all the matched routes in this group when generating documentation
             */
            'apply' => [
                /*
                 * Additional headers to be added to the example requests
                 */
                'headers' => [
                    'Content-Type' => 'application/json',
                    'Accept' => 'application/json',
                ],

                /*
                 * If no @response or @transformer declarations are found for the route,
                 * Scribe will try to get a sample response by attempting an API call.
                 * Configure the settings for the API call here.
                 */
                'response_calls' => [
                    /*
                     * API calls will be made only for routes in this group matching these HTTP methods (GET, POST, etc).
                     * List the methods here or use '*' to mean all methods. Leave empty to disable API calls.
                     */
                    'methods' => ['GET'],

                    /*
                     * Laravel config variables which should be set for the API call.
                     * This is a good place to ensure that notifications, emails and other external services
                     * are not triggered during the documentation API calls.
                     * You can also create a `.env.docs` file and run the generate command with `--env docs`.
                     */
                    'config' => [
                        'app.env' => 'documentation',
                        'app.debug' => false,
                    ],

                    /*
                     * Query parameters which should be sent with the API call.
                     */
                    'queryParams' => [
                        // 'key' => 'value',
                    ],

                    /*
                     * Body parameters which should be sent with the API call.
                     */
                    'bodyParams' => [
                        // 'key' => 'value',
                    ],

                    /*
                     * Files which should be sent with the API call.
                     * Each value should be a valid path (absolute or relative to your project directory) to a file on this machine (but not in the project root).
                     */
                    'fileParams' => [
                        // 'key' => 'storage/app/image.png',
                    ],

                    /*
                     * Cookies which should be sent with the API call.
                     */
                    'cookies' => [
                        // 'name' => 'value'
                    ],
                ],
            ],
        ],
    ],

    /*
     * The type of documentation output to generate.
     * - "static" will generate a static HTMl page in the /public/docs folder,
     * - "laravel" will generate the documentation as a Blade view, so you can add routing and authentication.
     */
    'type' => 'laravel',

    /*
     * Settings for `static` type output.
     */
    'static' => [
        /*
         * HTML documentation, assets and Postman collection will be generated to this folder.
         * Source Markdown will still be in resources/docs.
         */
        'output_path' => 'public/docs',
    ],

    /*
     * Settings for `laravel` type output.
     */
    'laravel' => [
        /*
         * Whether to automatically create a docs endpoint for you to view your generated docs.
         * If this is false, you can still set up routing manually.
         */
        'add_routes' => true,

        /*
         * URL path to use for the docs endpoint (if `add_routes` is true).
         * By default, `/docs` opens the HTML page, `/docs.postman` opens the Postman collection, and `/docs.openapi` the OpenAPI spec.
         */
        'docs_url' => 'docs',

        /*
         * Directory within `public` in which to store CSS and JS assets.
         * By default, assets are stored in `public/vendor/scribe`.
         * If set, assets will be stored in `public/{{assets_directory}}`
         */
        'assets_directory' => null,

        /*
         * Middleware to attach to the docs endpoint (if `add_routes` is true).
         */
        'middleware' => [],
    ],

    'try_it_out' => [
        /**
         * Add a Try It Out button to your endpoints so consumers can test endpoints right from their browser.
         * Don't forget to enable CORS headers for your endpoints.
         */
        'enabled' => true,

        /**
         * The base URL for the API tester to use (for example, you can set this to your staging URL).
         * Leave as null to use the current app URL (config(app.url)).
         */
        'base_url' => env('APP_URL', 'https://laravel.local'),

        /**
         * Fetch a CSRF token before each request, and add it as an X-XSRF-TOKEN header. Needed if you're using Laravel Sanctum.
         */
        'use_csrf' => false,

        /**
         * The URL to fetch the CSRF token from (if `use_csrf` is true).
         */
        'csrf_url' => '/sanctum/csrf-cookie',
    ],

    /*
     * How is your API authenticated? This information will be used in the displayed docs, generated examples and response calls.
     */
    'auth' => [
        /*
         * Set this to true if any endpoints in your API use authentication.
         */
        'enabled' => true,

        /*
         * Set this to true if your API should be authenticated by default. If so, you must also set `enabled` (above) to true.
         * You can then use @unauthenticated or @authenticated on individual endpoints to change their status from the default.
         */
        'default' => false,

        /*
         * Where is the auth value meant to be sent in a request?
         * Options: query, body, basic, bearer, header (for custom header)
         */
        'in' => 'bearer',

        /*
         * The name of the auth parameter (eg token, key, apiKey) or header (eg Authorization, Api-Key).
         */
        'name' => 'key',

        /*
         * The value of the parameter to be used by Scribe to authenticate response calls.
         * This will NOT be included in the generated documentation.
         * If this value is empty, Scribe will use a random value.
         */
        'use_value' => env('SCRIBE_AUTH_KEY'),

        /*
         * Placeholder your users will see for the auth parameter in the example requests.
         * Set this to null if you want Scribe to use a random value as placeholder instead.
         */
        'placeholder' => '{YOUR_AUTH_KEY}',

        /*
         * Any extra authentication-related info for your users. For instance, you can describe how to find or generate their auth credentials.
         * Markdown and HTML are supported.
         */
        'extra_info' => 'You can retrieve your token by visiting your dashboard and clicking <b>Generate API token</b>.',
    ],

    /*
     * Text to place in the "Introduction" section, right after the `description`. Markdown and HTML are supported.
     */
    'intro_text' => <<<INTRO
This documentation aims to provide all the information you need to work with our API.

<aside>As you scroll, you'll see code examples for working with the API in different programming languages in the dark area to the right (or as part of the content on mobile).
You can switch the language used with the tabs at the top right (or from the nav menu at the top left on mobile).</aside>
INTRO,

    /*
     * Example requests for each endpoint will be shown in each of these languages.
     * Supported options are: bash, javascript, php, python
     * To add a language of your own, see https://scribe.knuckles.wtf/laravel/advanced/example-requests
     *
     */
    'example_languages' => [
        'bash',
        'javascript',
    ],

    /*
     * Generate a Postman collection (v2.1.0) in addition to HTML docs.
     * For 'static' docs, the collection will be generated to public/docs/collection.json.
     * For 'laravel' docs, it will be generated to storage/app/scribe/collection.json.
     * Setting `laravel.add_routes` to true (above) will also add a route for the collection.
     */
    'postman' => [
        'enabled' => true,

        /*
         * Manually override some generated content in the spec. Dot notation is supported.
         */
        'overrides' => [
            // 'info.version' => '2.0.0',
        ],
    ],

    /*
     * Generate an OpenAPI spec (v3.0.1) in addition to docs webpage.
     * For 'static' docs, the collection will be generated to public/docs/openapi.yaml.
     * For 'laravel' docs, it will be generated to storage/app/scribe/openapi.yaml.
     * Setting `laravel.add_routes` to true (above) will also add a route for the spec.
     */
    'openapi' => [
        'enabled' => true,

        /*
         * Manually override some generated content in the spec. Dot notation is supported.
         */
        'overrides' => [
            // 'info.version' => '2.0.0',
        ],
    ],

    'groups' => [
        /*
         * Endpoints which don't have a @group will be placed in this default group.
         */
        'default' => 'Endpoints',

        /*
         * By default, Scribe will sort groups alphabetically, and endpoints in the order their routes are defined.
         * You can override this by listing the groups, subgroups and endpoints here in the order you want them.
         *
         * Any groups, subgroups or endpoints you don't list here will be added as usual after the ones here.
         * If an endpoint/subgroup is listed under a group it doesn't belong in, it will be ignored.
         * Note: you must include the initial '/' when writing an endpoint.
         */
        'order' => [
            // 'This group will come first',
            // 'This group will come next' => [
            //     'POST /this-endpoint-will-comes-first',
            //     'GET /this-endpoint-will-comes-next',
            // ],
            // 'This group will come third' => [
            //     'This subgroup will come first' => [
            //         'GET /this-other-endpoint-will-comes-first',
            //         'GET /this-other-endpoint-will-comes-next',
            //     ]
            // ]
        ],
    ],

    /*
     * Custom logo path. This will be used as the value of the src attribute for the <img> tag,
     * so make sure it points to an accessible URL or path. Set to false to not use a logo.
     *
     * For example, if your logo is in public/img:
     * - 'logo' => '../img/logo.png' // for `static` type (output folder is public/docs)
     * - 'logo' => 'img/logo.png' // for `laravel` type
     *
     */
    'logo' => false,

    /**
     * Customize the "Last updated" value displayed in the docs by specifying tokens and formats.
     * Examples:
     * - {date:F j Y} => March 28, 2022
     * - {git:short} => Short hash of the last Git commit
     *
     * Available tokens are `{date:<format>}` and `{git:<format>}`.
     * The format you pass to `date` will be passed to PhP's `date()` function.
     * The format you pass to `git` can be either "short" or "long".
     */
    'last_updated' => 'Last updated: {date:F j, Y}',

    'examples' => [
        /*
         * If you would like the package to generate the same example values for parameters on each run,
         * set this to any number (eg. 1234)
         */
        'faker_seed' => null,

        /*
         * With API resources and transformers, Scribe tries to generate example models to use in your API responses.
         * By default, Scribe will try the model's factory, and if that fails, try fetching the first from the database.
         * You can reorder or remove strategies here.
         */
        'models_source' => ['factoryCreate', 'factoryMake', 'databaseFirst'],
    ],

    /**
     * The strategies Scribe will use to extract information about your routes at each stage.
     * If you create or install a custom strategy, add it here.
     */
    'strategies' => [
        'metadata' => [
            Strategies\Metadata\GetFromDocBlocks::class,
            Strategies\Metadata\GetFromMetadataAttributes::class,
        ],
        'urlParameters' => [
            Strategies\UrlParameters\GetFromLaravelAPI::class,
            Strategies\UrlParameters\GetFromLumenAPI::class,
            Strategies\UrlParameters\GetFromUrlParamAttribute::class,
            Strategies\UrlParameters\GetFromUrlParamTag::class,
        ],
        'queryParameters' => [
            Strategies\QueryParameters\GetFromFormRequest::class,
            Strategies\QueryParameters\GetFromInlineValidator::class,
            Strategies\QueryParameters\GetFromQueryParamAttribute::class,
            Strategies\QueryParameters\GetFromQueryParamTag::class,
        ],
        'headers' => [
            Strategies\Headers\GetFromRouteRules::class,
            Strategies\Headers\GetFromHeaderAttribute::class,
            Strategies\Headers\GetFromHeaderTag::class,
        ],
        'bodyParameters' => [
            Strategies\BodyParameters\GetFromFormRequest::class,
            Strategies\BodyParameters\GetFromInlineValidator::class,
            Strategies\BodyParameters\GetFromBodyParamAttribute::class,
            Strategies\BodyParameters\GetFromBodyParamTag::class,
        ],
        'responses' => [
            Strategies\Responses\UseResponseAttributes::class,
            Strategies\Responses\UseTransformerTags::class,
            Strategies\Responses\UseApiResourceTags::class,
            Strategies\Responses\UseResponseTag::class,
            Strategies\Responses\UseResponseFileTag::class,
            Strategies\Responses\ResponseCalls::class,
        ],
        'responseFields' => [
            Strategies\ResponseFields\GetFromResponseFieldAttribute::class,
            Strategies\ResponseFields\GetFromResponseFieldTag::class,
        ],
    ],

    'fractal' => [
        /* If you are using a custom serializer with league/fractal, you can specify it here.
         * Leave as null to use no serializer or return simple JSON.
         */
        'serializer' => null,
    ],

    /*
     * [Advanced] Custom implementation of RouteMatcherInterface to customise how routes are matched
     *
     */
    'routeMatcher' => \Knuckles\Scribe\Matching\RouteMatcher::class,

    /**
     * For response calls, API resource responses and transformer responses,
     * Scribe will try to start database transactions, so no changes are persisted to your database.
     * Tell Scribe which connections should be transacted here.
     * If you only use one db connection, you can leave this as is.
     */
    'database_connections_to_transact' => [config('database.default')],
    'external' => ['html_attributes' => []]
];

What happened?

I'm facing an issue because I have a huge number of endpoints, and I also have multiple API route files. Initially, the docs were not generating successfully, but after increasing the memory limit, it started working. However, it is unable to load on the browser. After some time, it opens a waiting popup and then gets killed. I need a solution for this. Additionally, I have more than 200 module files, so I'm not creating individual config files for each. Therefore, I need a proper solution for this or something else that will fix it.

Docs

codespearhead commented 7 months ago

What happens to the terminal when you access the documentation URL via cURL?

codespearhead commented 7 months ago

Change type from "laravel" to "static" and double click the generated HTML.

If opening up the static HTML still crashes the page, whip up the Chrome DevTools and show us what comes up in the "network" and "console" tabs once you try to load the page from the file.

If the static-generated version of the documentation doesn't crash the page, do as suggested in previous paragraph in your current setup (type => "laravel") and show us what comes up in the terminal running the server as well.

engrnand commented 7 months ago

I have change type from laravel to static, and delete all auto generated directories of scribe from view,storage,public direcory after that I have run php artisan optimize:clear command ,then I have run scribe generate command php -d memory_limit=1G artisan scribe:generate . here is page data

codespearhead commented 7 months ago

Are you able to run php -d memory_limit=1G artisan scribe:generate with scribe.php's config 'type' => 'static' and share the contents of public/docs with us?

codespearhead commented 7 months ago

Opening the 150_000-line index.html file as is will consume about 1.2GB of RAM, which might throw Error code: out of memory at the end user's machine's discretion.

Commenting out the following lines in the header of that file will make the page claim below 150MB of RAM, which is about half of what you'd expect from the YouTube home page.

<link rel="stylesheet" href="../docs/css/theme-default.style.css" media="screen">
<link rel="stylesheet" href="../docs/css/theme-default.print.css" media="print">
<link rel="stylesheet" href="https://unpkg.com/@highlightjs/cdn-assets@11.6.0/styles/obsidian.min.css">
<script src="https://unpkg.com/@highlightjs/cdn-assets@11.6.0/highlight.min.js"></script>

Hence, the inflated RAM usage comes from CSS styling.

engrnand commented 7 months ago

If I remove the scripts, it removes all the styling of the web page. And Is this problem solvable?

codespearhead commented 7 months ago

Given that CSS styling is a front-end concern, the solution naturally belongs to the repository of the OpenAPI UI theme you're using.

However, I don't know where the default theme comes from.

Hence, here's a fix:

<script>
  // Make .invisible class available globally
  const invisibleStyle = `
  .invisible {
      display: none !important;
  }
  `;
  const styleElement = document.createElement("style");
  styleElement.textContent = invisibleStyle;
  document.head.appendChild(styleElement);

  // Display API sections conditionally to avoid CSS rendering overload
  function handleHashChange() {
    const targetCssSelector = ".page-wrapper > .content > *";
    const sectionContainer = document.querySelectorAll(targetCssSelector);
    sectionContainer.forEach(element => element.classList.add("invisible"))

    // Get fragment identifier without the "#" symbol
    const fragmentIdentifier = window.location.hash.substring(1);

    // Get element matching the fragmentIdentifier in the URL or the first section
    let elementToShow;
    if (fragmentIdentifier) {
      elementToShow = document.getElementById(fragmentIdentifier);
    } else {
      const firstH1 = document.querySelector(".page-wrapper > .content > h1");
      if (firstH1) {
        elementToShow = firstH1;
      }
    }

    // Show the element matching the fragmentIdentifier (elementToShow)
    if (elementToShow) {
      elementToShow.classList.remove("invisible");

      // Loop through the siblings of the elementToShow until another h1 tag is found
      let nextElement = elementToShow.nextElementSibling;
      while (nextElement && !nextElement.matches('h1')) {
        nextElement.classList.remove("invisible");
        nextElement = nextElement.nextElementSibling;
      }
    }
  }

  window.addEventListener("hashchange", handleHashChange);
  document.addEventListener("DOMContentLoaded", handleHashChange);
</script>

Adding this right after the opening body tag (<body>) of the static-generated index.html will make the documentation page consume 450MB of RAM instead of 1.2GB of RAM.

As of today, I don't know the codebase well enough to make it work with scribe.php's config 'type' => 'laravel'.

engrnand commented 7 months ago

thanks, it works , I have create command for dynamically add script

 public function handle()
      {
          ini_set('memory_limit', '1G');
          Artisan::call("scribe:generate");
          $output = Artisan::output();
          echo $output;

        if (config('scribe.type') == 'laravel') {
            $indexFilePath =
                $indexFilePath = resource_path('views/scribe/index.blade.php');
        } else {
            $indexFilePath = public_path('docs/index.html');
        }
        $indexContent = File::get($indexFilePath);
        $scriptContent = '<script src="/uploads/scripts/scribe-custom.js"></script>';

        // Find the position to insert the script content
        $headEndPosition = strripos($indexContent, '</head>');

        if ($headEndPosition !== false) {
            $indexContent = substr_replace($indexContent, $scriptContent, $headEndPosition, 0);
        } else {
            // Handle case where </head> tag is not found
            $this->error('</head> tag not found in index.blade.php. Script content was not inserted.');
            return;
        }

        // Write the modified content back to index.blade.php
        File::put($indexFilePath, $indexContent);
        $this->info('Script content inserted successfully inside <head> tag of index.blade.php.');
    }
shalvah commented 7 months ago

Thanks for assisting @codespearhead !

Alternatively, can you try setting type to external_static or external_laravel? (and also set the theme value) This will make Scribe only generate an OpenAPI spec, and render client-side documentation from that spec.

engrnand commented 6 months ago

for external_staticor external_laravel need to create custom view, I think you guys need to fix it by default in the package image

shalvah commented 6 months ago

No, you didn't configure it correctly.

Your config should look like this:

'type' => 'external_laravel',
'theme' => 'elements',
engrnand commented 6 months ago

facing error: when set above configration 313576092-d8cb947e-422f-4df3-9439-beb60e4509cb

WesWeCan commented 6 months ago

facing error: when set above configration 313576092-d8cb947e-422f-4df3-9439-beb60e4509cb

This seems happen because in the elements component looks for an openapi.yaml file in ../docs/ but that file is not generated.

When I just hardcode the openapi spec link that is generated.

config/scribe.php

'type' => 'external_laravel', 'theme' => 'elements',

From scribe/index.blade.php <elements-api apiDescriptionUrl="../docs/openapi.yaml" router="hash" layout="sidebar" hideTryIt="" logo="" />

To scribe/index.blade.php <elements-api apiDescriptionUrl="/docs.openapi" router="hash" layout="sidebar" hideTryIt="" logo="" />

But this solution seems to break the CSRF protection with Try It Out.

When you don't use the external_laravel type ther seems to be a bug with the Highlight.js that freezes up the whole page, maybe that is the issue why it is not loading?

Commenting these out solves the issue of the incredible slow loading time, but at the expense that your Try It Out is not correctly styled.

scribe/index.blade.php

shalvah commented 6 months ago

Oof. Maybe HighlightJS can't handle it. Is there no way to get the CSRF protection working? If possible, check out the documentation for elements/scalar/rapidoc. They might have some extra options you can pass with Scribe's config external.html_attributes.

max13fr commented 4 months ago

Hello,

Here a working solution :

    'type' => 'external_laravel',
    'theme' => 'elements',
    'external' => [
        'html_attributes' => [
            'apiDescriptionUrl' => '/docs.openapi',
            'basePath' => '/docs',
            'layout' => 'responsive',
            'router' => 'hash',
            'tryItCredentialsPolicy' => 'same-origin',
        ]
    ],

I looked at the code, the issue is in this file : https://github.com/knuckleswtf/scribe/blob/master/src/Writing/HtmlWriter.php#L32

In laravel mode we have $this->assetPathPrefix set to '../docs/'

In the ExternalHtmlWriter we are appending 'openapi.yaml' so we have in finally $metadata['openapi_spec_url'] = '../docs/openapi.yaml' (https://github.com/knuckleswtf/scribe/blob/master/src/Writing/ExternalHtmlWriter.php#L36)

I'm not sure how to fix that without breaking other mode.

Best regards, Max