From 2bcf91b39ec5a9037baaa80d2368340e49370d73 Mon Sep 17 00:00:00 2001 From: Christian Hartmann Date: Mon, 7 Oct 2024 22:19:49 +0200 Subject: [PATCH] chore: add basics for openapi extractor Signed-off-by: Christian Hartmann --- .github/workflows/openapi.yml | 94 ++++++++ composer.json | 8 +- lib/Controller/ApiController.php | 50 ++--- lib/Controller/ShareApiController.php | 6 +- psalm.xml | 4 + vendor-bin/openapi-extractor/composer.json | 5 + vendor-bin/openapi-extractor/composer.lock | 240 +++++++++++++++++++++ 7 files changed, 374 insertions(+), 33 deletions(-) create mode 100644 .github/workflows/openapi.yml create mode 100644 vendor-bin/openapi-extractor/composer.json create mode 100644 vendor-bin/openapi-extractor/composer.lock diff --git a/.github/workflows/openapi.yml b/.github/workflows/openapi.yml new file mode 100644 index 000000000..cf6d2778c --- /dev/null +++ b/.github/workflows/openapi.yml @@ -0,0 +1,94 @@ +# This workflow is provided via the organization template repository +# +# https://github.com/nextcloud/.github +# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization +# +# SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors +# SPDX-FileCopyrightText: 2024 Arthur Schiwon +# SPDX-License-Identifier: MIT + +name: OpenAPI + +on: pull_request + +permissions: + contents: read + +concurrency: + group: openapi-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + openapi: + runs-on: ubuntu-latest + + if: ${{ github.repository_owner != 'nextcloud-gmbh' }} + + steps: + - name: Checkout + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - name: Get php version + id: php_versions + uses: icewind1991/nextcloud-version-matrix@58becf3b4bb6dc6cef677b15e2fd8e7d48c0908f # v1.3.1 + + - name: Set up php + uses: shivammathur/setup-php@c541c155eee45413f5b09a52248675b1a2575231 # v2.31.1 + with: + php-version: ${{ steps.php_versions.outputs.php-available }} + extensions: xml + coverage: none + ini-file: development + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Check Typescript OpenApi types + id: check_typescript_openapi + uses: andstor/file-existence-action@076e0072799f4942c8bc574a82233e1e4d13e9d6 # v3.0.0 + with: + files: "src/types/openapi/openapi*.ts" + + - name: Read package.json node and npm engines version + if: steps.check_typescript_openapi.outputs.files_exists == 'true' + uses: skjnldsv/read-package-engines-version-actions@06d6baf7d8f41934ab630e97d9e6c0bc9c9ac5e4 # v3 + id: node_versions + # Continue if no package.json + continue-on-error: true + with: + fallbackNode: '^20' + fallbackNpm: '^10' + + - name: Set up node ${{ steps.node_versions.outputs.nodeVersion }} + if: ${{ steps.node_versions.outputs.nodeVersion }} + uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + with: + node-version: ${{ steps.node_versions.outputs.nodeVersion }} + + - name: Set up npm ${{ steps.node_versions.outputs.npmVersion }} + if: ${{ steps.node_versions.outputs.nodeVersion }} + run: npm i -g 'npm@${{ steps.node_versions.outputs.npmVersion }}' + + - name: Install dependencies & build + if: ${{ steps.node_versions.outputs.nodeVersion }} + env: + CYPRESS_INSTALL_BINARY: 0 + PUPPETEER_SKIP_DOWNLOAD: true + run: | + npm ci + + - name: Set up dependencies + run: composer i + + - name: Regenerate OpenAPI + run: composer run openapi + + - name: Check openapi*.json and typescript changes + run: | + bash -c "[[ ! \"`git status --porcelain `\" ]] || (echo 'Please run \"composer run openapi\" and commit the openapi*.json files and (if applicable) src/types/openapi/openapi*.ts, see the section \"Show changes on failure\" for details' && exit 1)" + + - name: Show changes on failure + if: failure() + run: | + git status + git --no-pager diff + exit 1 # make it red to grab attention diff --git a/composer.json b/composer.json index b019428bd..7cbdb2751 100644 --- a/composer.json +++ b/composer.json @@ -12,17 +12,15 @@ } }, "scripts": { - "post-install-cmd": [ - "@composer bin psalm install --ansi", - "@composer bin cs-fixer install --ansi" - ], + "post-install-cmd": "@composer bin all install --ansi", "bin": "echo 'bin not installed'", "cs:fix": "php-cs-fixer fix", "cs:check": "php-cs-fixer fix --dry-run --diff", "lint": "find . -name \\*.php -not -path './vendor*/*' -print0 | xargs -0 -n1 php -l", "test:unit": "phpunit -c tests/phpunit.xml", "test:integration": "phpunit -c tests/phpunit.integration.xml", - "psalm": "psalm" + "psalm": "psalm", + "openapi": "generate-spec" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8", diff --git a/lib/Controller/ApiController.php b/lib/Controller/ApiController.php index 00f08d73d..a1b3ddfff 100644 --- a/lib/Controller/ApiController.php +++ b/lib/Controller/ApiController.php @@ -50,6 +50,7 @@ use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\IMapperException; +use OCP\AppFramework\Http; use OCP\AppFramework\Http\Attribute\ApiRoute; use OCP\AppFramework\Http\Attribute\CORS; use OCP\AppFramework\Http\Attribute\NoAdminRequired; @@ -57,7 +58,6 @@ use OCP\AppFramework\Http\Attribute\PublicPage; use OCP\AppFramework\Http\DataDownloadResponse; use OCP\AppFramework\Http\DataResponse; -use OCP\AppFramework\Http\Response; use OCP\AppFramework\OCS\OCSBadRequestException; use OCP\AppFramework\OCS\OCSForbiddenException; use OCP\AppFramework\OCS\OCSNotFoundException; @@ -104,7 +104,7 @@ public function __construct( /** * Handle CORS options request by calling parent function */ - #[ApiRoute(verb: 'OPTIONS', url: Constants::API_BASE . '{path}', requirements: Constants::API_V3_REQUIREMENTS)] + #[ApiRoute(verb: 'OPTIONS', url: '/api/v3/{path}', requirements: ['path' => '.+'])] public function preflightedCors() { parent::preflightedCors(); } @@ -118,7 +118,7 @@ public function preflightedCors() { */ #[CORS()] #[NoAdminRequired()] - #[ApiRoute(verb: 'GET', url: Constants::API_BASE . 'forms', requirements: Constants::API_V3_REQUIREMENTS)] + #[ApiRoute(verb: 'GET', url: '/api/v3/forms')] public function getForms(string $type = 'owned'): DataResponse { if ($type === 'owned') { $forms = $this->formMapper->findAllByOwnerId($this->currentUser->getUID()); @@ -147,7 +147,7 @@ public function getForms(string $type = 'owned'): DataResponse { */ #[CORS()] #[NoAdminRequired()] - #[ApiRoute(verb: 'POST', url: Constants::API_BASE . 'forms', requirements: Constants::API_V3_REQUIREMENTS)] + #[ApiRoute(verb: 'POST', url: '/api/v3/forms')] public function newForm(?int $fromId = null): DataResponse { // Check if user is allowed if (!$this->configService->canCreateForms()) { @@ -222,7 +222,7 @@ public function newForm(?int $fromId = null): DataResponse { */ #[CORS()] #[NoAdminRequired()] - #[ApiRoute(verb: 'GET', url: Constants::API_BASE . 'forms/{formId}', requirements: Constants::API_V3_REQUIREMENTS)] + #[ApiRoute(verb: 'GET', url: '/api/v3/forms/{formId}')] public function getForm(int $formId): DataResponse { try { $form = $this->formMapper->findById($formId); @@ -252,7 +252,7 @@ public function getForm(int $formId): DataResponse { */ #[CORS()] #[NoAdminRequired()] - #[ApiRoute(verb: 'PATCH', url: Constants::API_BASE . 'forms/{formId}', requirements: Constants::API_V3_REQUIREMENTS)] + #[ApiRoute(verb: 'PATCH', url: '/api/v3/forms/{formId}')] public function updateForm(int $formId, array $keyValuePairs): DataResponse { $this->logger->debug('Updating form: formId: {formId}, values: {keyValuePairs}', [ 'formId' => $formId, @@ -339,7 +339,7 @@ public function updateForm(int $formId, array $keyValuePairs): DataResponse { */ #[CORS()] #[NoAdminRequired()] - #[ApiRoute(verb: 'DELETE', url: Constants::API_BASE . 'forms/{formId}', requirements: Constants::API_V3_REQUIREMENTS)] + #[ApiRoute(verb: 'DELETE', url: '/api/v3/forms/{formId}')] public function deleteForm(int $formId): DataResponse { $this->logger->debug('Delete Form: {formId}', [ 'formId' => $formId, @@ -362,7 +362,7 @@ public function deleteForm(int $formId): DataResponse { */ #[CORS()] #[NoAdminRequired()] - #[ApiRoute(verb: 'GET', url: Constants::API_BASE . 'forms/{formId}/questions', requirements: Constants::API_V3_REQUIREMENTS)] + #[ApiRoute(verb: 'GET', url: '/api/v3/forms/{formId}/questions')] public function getQuestions(int $formId): DataResponse { try { $form = $this->formMapper->findById($formId); @@ -392,7 +392,7 @@ public function getQuestions(int $formId): DataResponse { */ #[CORS()] #[NoAdminRequired()] - #[ApiRoute(verb: 'GET', url: Constants::API_BASE . 'forms/{formId}/questions/{questionId}', requirements: Constants::API_V3_REQUIREMENTS)] + #[ApiRoute(verb: 'GET', url: '/api/v3/forms/{formId}/questions/{questionId}')] public function getQuestion(int $formId, int $questionId): DataResponse { try { $form = $this->formMapper->findById($formId); @@ -428,7 +428,7 @@ public function getQuestion(int $formId, int $questionId): DataResponse { */ #[CORS()] #[NoAdminRequired()] - #[ApiRoute(verb: 'POST', url: Constants::API_BASE . 'forms/{formId}/questions', requirements: Constants::API_V3_REQUIREMENTS)] + #[ApiRoute(verb: 'POST', url: '/api/v3/forms/{formId}/questions')] public function newQuestion(int $formId, ?string $type = null, string $text = '', ?int $fromId = null): DataResponse { $form = $this->getFormIfAllowed($formId); if ($this->formsService->isFormArchived($form)) { @@ -534,7 +534,7 @@ public function newQuestion(int $formId, ?string $type = null, string $text = '' */ #[CORS()] #[NoAdminRequired()] - #[ApiRoute(verb: 'PATCH', url: Constants::API_BASE . 'forms/{formId}/questions/{questionId}', requirements: Constants::API_V3_REQUIREMENTS)] + #[ApiRoute(verb: 'PATCH', url: '/api/v3/forms/{formId}/questions/{questionId}')] public function updateQuestion(int $formId, int $questionId, array $keyValuePairs): DataResponse { $this->logger->debug('Updating question: formId: {formId}, questionId: {questionId}, values: {keyValuePairs}', [ 'formId' => $formId, @@ -603,7 +603,7 @@ public function updateQuestion(int $formId, int $questionId, array $keyValuePair */ #[CORS()] #[NoAdminRequired()] - #[ApiRoute(verb: 'DELETE', url: Constants::API_BASE . 'forms/{formId}/questions/{questionId}', requirements: Constants::API_V3_REQUIREMENTS)] + #[ApiRoute(verb: 'DELETE', url: '/api/v3/forms/{formId}/questions/{questionId}')] public function deleteQuestion(int $formId, int $questionId): DataResponse { $this->logger->debug('Mark question as deleted: {questionId}', [ 'questionId' => $questionId, @@ -659,7 +659,7 @@ public function deleteQuestion(int $formId, int $questionId): DataResponse { */ #[CORS()] #[NoAdminRequired()] - #[ApiRoute(verb: 'PATCH', url: Constants::API_BASE . 'forms/{formId}/questions', requirements: Constants::API_V3_REQUIREMENTS)] + #[ApiRoute(verb: 'PATCH', url: '/api/v3/forms/{formId}/questions')] public function reorderQuestions(int $formId, array $newOrder): DataResponse { $this->logger->debug('Reordering Questions on Form {formId} as Question-Ids {newOrder}', [ 'formId' => $formId, @@ -751,7 +751,7 @@ public function reorderQuestions(int $formId, array $newOrder): DataResponse { */ #[CORS()] #[NoAdminRequired()] - #[ApiRoute(verb: 'POST', url: Constants::API_BASE . 'forms/{formId}/questions/{questionId}/options', requirements: Constants::API_V3_REQUIREMENTS)] + #[ApiRoute(verb: 'POST', url: '/api/v3/forms/{formId}/questions/{questionId}/options')] public function newOption(int $formId, int $questionId, array $optionTexts): DataResponse { $this->logger->debug('Adding new options: formId: {formId}, questionId: {questionId}, text: {text}', [ 'formId' => $formId, @@ -824,7 +824,7 @@ public function newOption(int $formId, int $questionId, array $optionTexts): Dat */ #[CORS()] #[NoAdminRequired()] - #[ApiRoute(verb: 'PATCH', url: Constants::API_BASE . 'forms/{formId}/questions/{questionId}/options/{optionId}', requirements: Constants::API_V3_REQUIREMENTS)] + #[ApiRoute(verb: 'PATCH', url: '/api/v3/forms/{formId}/questions/{questionId}/options/{optionId}')] public function updateOption(int $formId, int $questionId, int $optionId, array $keyValuePairs): DataResponse { $this->logger->debug('Updating option: form: {formId}, question: {questionId}, option: {optionId}, values: {keyValuePairs}', [ 'formId' => $formId, @@ -888,7 +888,7 @@ public function updateOption(int $formId, int $questionId, int $optionId, array */ #[CORS()] #[NoAdminRequired()] - #[ApiRoute(verb: 'DELETE', url: Constants::API_BASE . 'forms/{formId}/questions/{questionId}/options/{optionId}', requirements: Constants::API_V3_REQUIREMENTS)] + #[ApiRoute(verb: 'DELETE', url: '/api/v3/forms/{formId}/questions/{questionId}/options/{optionId}')] public function deleteOption(int $formId, int $questionId, int $optionId): DataResponse { $this->logger->debug('Deleting option: {optionId}', [ 'optionId' => $optionId @@ -936,7 +936,7 @@ public function deleteOption(int $formId, int $questionId, int $optionId): DataR */ #[CORS()] #[NoAdminRequired()] - #[ApiRoute(verb: 'PATCH', url: Constants::API_BASE . 'forms/{formId}/questions/{questionId}/options', requirements: Constants::API_V3_REQUIREMENTS)] + #[ApiRoute(verb: 'PATCH', url: '/api/v3/forms/{formId}/questions/{questionId}/options')] public function reorderOptions(int $formId, int $questionId, array $newOrder) { $form = $this->getFormIfAllowed($formId); if ($this->formsService->isFormArchived($form)) { @@ -1027,7 +1027,7 @@ public function reorderOptions(int $formId, int $questionId, array $newOrder) { */ #[CORS()] #[NoAdminRequired()] - #[ApiRoute(verb: 'GET', url: Constants::API_BASE . 'forms/{formId}/submissions', requirements: Constants::API_V3_REQUIREMENTS)] + #[ApiRoute(verb: 'GET', url: '/api/v3/forms/{formId}/submissions')] public function getSubmissions(int $formId, ?string $fileFormat = null): DataResponse|DataDownloadResponse { try { $form = $this->formMapper->findById($formId); @@ -1088,7 +1088,7 @@ public function getSubmissions(int $formId, ?string $fileFormat = null): DataRes */ #[CORS()] #[NoAdminRequired()] - #[ApiRoute(verb: 'DELETE', url: Constants::API_BASE . 'forms/{formId}/submissions', requirements: Constants::API_V3_REQUIREMENTS)] + #[ApiRoute(verb: 'DELETE', url: '/api/v3/forms/{formId}/submissions')] public function deleteAllSubmissions(int $formId): DataResponse { $this->logger->debug('Delete all submissions to form: {formId}', [ 'formId' => $formId, @@ -1128,7 +1128,7 @@ public function deleteAllSubmissions(int $formId): DataResponse { #[NoAdminRequired()] #[NoCSRFRequired()] #[PublicPage()] - #[ApiRoute(verb: 'POST', url: Constants::API_BASE . 'forms/{formId}/submissions', requirements: Constants::API_V3_REQUIREMENTS)] + #[ApiRoute(verb: 'POST', url: '/api/v3/forms/{formId}/submissions')] public function newSubmission(int $formId, array $answers, string $shareHash = ''): DataResponse { $this->logger->debug('Inserting submission: formId: {formId}, answers: {answers}, shareHash: {shareHash}', [ 'formId' => $formId, @@ -1221,7 +1221,7 @@ public function newSubmission(int $formId, array $answers, string $shareHash = ' */ #[CORS()] #[NoAdminRequired()] - #[ApiRoute(verb: 'DELETE', url: Constants::API_BASE . 'forms/{formId}/submissions/{submissionId}', requirements: Constants::API_V3_REQUIREMENTS)] + #[ApiRoute(verb: 'DELETE', url: '/api/v3/forms/{formId}/submissions/{submissionId}')] public function deleteSubmission(int $formId, int $submissionId): DataResponse { $this->logger->debug('Delete Submission: {submissionId}', [ 'submissionId' => $submissionId, @@ -1265,7 +1265,7 @@ public function deleteSubmission(int $formId, int $submissionId): DataResponse { */ #[CORS()] #[NoAdminRequired()] - #[ApiRoute(verb: 'POST', url: Constants::API_BASE . 'forms/{formId}/submissions/export', requirements: Constants::API_V3_REQUIREMENTS)] + #[ApiRoute(verb: 'POST', url: '/api/v3/forms/{formId}/submissions/export')] public function exportSubmissionsToCloud(int $formId, string $path, string $fileFormat = Constants::DEFAULT_FILE_FORMAT) { try { $form = $this->formMapper->findById($formId); @@ -1290,13 +1290,13 @@ public function exportSubmissionsToCloud(int $formId, string $path, string $file * @param int $formId id of the form * @param int $questionId id of the question * @param string $shareHash hash of the form share - * @return Response + * @return DataResponse */ #[CORS()] #[NoAdminRequired()] #[PublicPage()] - #[ApiRoute(verb: 'POST', url: Constants::API_BASE . 'forms/{formId}/submissions/files/{questionId}', requirements: Constants::API_V3_REQUIREMENTS)] - public function uploadFiles(int $formId, int $questionId, string $shareHash = ''): Response { + #[ApiRoute(verb: 'POST', url: '/api/v3/forms/{formId}/submissions/files/{questionId}')] + public function uploadFiles(int $formId, int $questionId, string $shareHash = ''): DataResponse { $this->logger->debug('Uploading files for formId: {formId}, questionId: {questionId}', [ 'formId' => $formId, 'questionId' => $questionId diff --git a/lib/Controller/ShareApiController.php b/lib/Controller/ShareApiController.php index 3adba90ca..088685f5b 100644 --- a/lib/Controller/ShareApiController.php +++ b/lib/Controller/ShareApiController.php @@ -91,7 +91,7 @@ public function __construct( */ #[CORS()] #[NoAdminRequired()] - #[ApiRoute(verb: 'POST', url: Constants::API_BASE . 'forms/{formId}/shares', requirements: Constants::API_V3_REQUIREMENTS)] + #[ApiRoute(verb: 'POST', url: '/api/v3/forms/{formId}/shares')] public function newShare(int $formId, int $shareType, string $shareWith = '', array $permissions = [Constants::PERMISSION_SUBMIT]): DataResponse { $this->logger->debug('Adding new share: formId: {formId}, shareType: {shareType}, shareWith: {shareWith}, permissions: {permissions}', [ 'formId' => $formId, @@ -217,7 +217,7 @@ public function newShare(int $formId, int $shareType, string $shareWith = '', ar */ #[CORS()] #[NoAdminRequired()] - #[ApiRoute(verb: 'PATCH', url: Constants::API_BASE . 'forms/{formId}/shares/{shareId}', requirements: Constants::API_V3_REQUIREMENTS)] + #[ApiRoute(verb: 'PATCH', url: '/api/v3/forms/{formId}/shares/{shareId}')] public function updateShare(int $formId, int $shareId, array $keyValuePairs): DataResponse { $this->logger->debug('Updating share: {shareId} of form {formId}, permissions: {permissions}', [ 'formId' => $formId, @@ -308,7 +308,7 @@ public function updateShare(int $formId, int $shareId, array $keyValuePairs): Da */ #[CORS()] #[NoAdminRequired()] - #[ApiRoute(verb: 'DELETE', url: Constants::API_BASE . 'forms/{formId}/shares/{shareId}', requirements: Constants::API_V3_REQUIREMENTS)] + #[ApiRoute(verb: 'DELETE', url: '/api/v3/forms/{formId}/shares/{shareId}')] public function deleteShare(int $formId, int $shareId): DataResponse { $this->logger->debug('Deleting share: {shareId} of form {formId}', [ 'formId' => $formId, diff --git a/psalm.xml b/psalm.xml index 62d66ea73..1f4f5af69 100644 --- a/psalm.xml +++ b/psalm.xml @@ -22,6 +22,10 @@ + + + + diff --git a/vendor-bin/openapi-extractor/composer.json b/vendor-bin/openapi-extractor/composer.json new file mode 100644 index 000000000..71320cbc6 --- /dev/null +++ b/vendor-bin/openapi-extractor/composer.json @@ -0,0 +1,5 @@ +{ + "require-dev": { + "nextcloud/openapi-extractor": "^1.0" + } +} diff --git a/vendor-bin/openapi-extractor/composer.lock b/vendor-bin/openapi-extractor/composer.lock new file mode 100644 index 000000000..9e19a1f49 --- /dev/null +++ b/vendor-bin/openapi-extractor/composer.lock @@ -0,0 +1,240 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "6964fdd288b96adcca764f7b24d4d678", + "packages": [], + "packages-dev": [ + { + "name": "adhocore/cli", + "version": "v1.7.2", + "source": { + "type": "git", + "url": "https://github.com/adhocore/php-cli.git", + "reference": "57834cbaa4fb68cda849417ab86577fba2b15298" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/adhocore/php-cli/zipball/57834cbaa4fb68cda849417ab86577fba2b15298", + "reference": "57834cbaa4fb68cda849417ab86577fba2b15298", + "shasum": "" + }, + "require": { + "php": ">=8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Ahc\\Cli\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jitendra Adhikari", + "email": "jiten.adhikary@gmail.com" + } + ], + "description": "Command line interface library for PHP", + "keywords": [ + "argument-parser", + "argv-parser", + "cli", + "cli-action", + "cli-app", + "cli-color", + "cli-option", + "cli-writer", + "command", + "console", + "console-app", + "php-cli", + "php8", + "stream-input", + "stream-output" + ], + "support": { + "issues": "https://github.com/adhocore/php-cli/issues", + "source": "https://github.com/adhocore/php-cli/tree/v1.7.2" + }, + "funding": [ + { + "url": "https://paypal.me/ji10", + "type": "custom" + }, + { + "url": "https://github.com/adhocore", + "type": "github" + } + ], + "time": "2024-09-05T00:08:47+00:00" + }, + { + "name": "nextcloud/openapi-extractor", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/nextcloud-releases/openapi-extractor.git", + "reference": "88e347097db28b6e3f8f3c221502b80a4f455b1f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nextcloud-releases/openapi-extractor/zipball/88e347097db28b6e3f8f3c221502b80a4f455b1f", + "reference": "88e347097db28b6e3f8f3c221502b80a4f455b1f", + "shasum": "" + }, + "require": { + "adhocore/cli": "^1.7", + "ext-simplexml": "*", + "nikic/php-parser": "^5.0", + "php": "^8.1", + "phpstan/phpdoc-parser": "^1.28" + }, + "require-dev": { + "nextcloud/coding-standard": "^1.2", + "nextcloud/ocp": "dev-master" + }, + "bin": [ + "generate-spec", + "merge-specs" + ], + "type": "library", + "autoload": { + "psr-4": { + "OpenAPIExtractor\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "AGPL-3.0-or-later" + ], + "description": "A tool for extracting OpenAPI specifications from Nextcloud source code", + "support": { + "issues": "https://github.com/nextcloud-releases/openapi-extractor/issues", + "source": "https://github.com/nextcloud-releases/openapi-extractor/tree/v1.0.0" + }, + "time": "2024-08-20T16:46:27+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "3abf7425cd284141dc5d8d14a9ee444de3345d1a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3abf7425cd284141dc5d8d14a9ee444de3345d1a", + "reference": "3abf7425cd284141dc5d8d14a9ee444de3345d1a", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.0" + }, + "time": "2024-09-29T13:56:26+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "1.32.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "6ca22b154efdd9e3c68c56f5d94670920a1c19a4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/6ca22b154efdd9e3c68c56f5d94670920a1c19a4", + "reference": "6ca22b154efdd9e3c68c56f5d94670920a1c19a4", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^4.15", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.32.0" + }, + "time": "2024-09-26T07:23:32+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.6.0" +}