From 5a73b2a23590adb99fce3c195209de3d5b531cd4 Mon Sep 17 00:00:00 2001 From: Christoph Kappestein Date: Sat, 17 Aug 2024 22:38:03 +0200 Subject: [PATCH] add typescript server generator --- src/Generator/Server/Dto/Context.php | 31 ++ src/Generator/Server/Dto/File.php | 58 ++++ src/Generator/Server/Dto/Folder.php | 80 +++++ src/Generator/Server/Dto/Type.php | 40 +++ src/Generator/Server/ServerAbstract.php | 286 ++++++++++++++++++ .../Server/Template/Typescript/.eslintrc.js | 25 ++ .../Server/Template/Typescript/.gitignore | 56 ++++ .../Server/Template/Typescript/.prettierrc | 4 + .../Server/Template/Typescript/README.md | 73 +++++ .../Server/Template/Typescript/nest-cli.json | 8 + .../Server/Template/Typescript/package.json | 69 +++++ .../Template/Typescript/src/app.module.ts | 10 + .../Typescript/src/controller/.gitkeep | 0 .../Template/Typescript/src/dto/.gitkeep | 0 .../Server/Template/Typescript/src/main.ts | 8 + .../Template/Typescript/tsconfig.build.json | 4 + .../Server/Template/Typescript/tsconfig.json | 21 ++ src/Generator/Server/TypeScript.php | 110 +++++++ src/Repository/LocalRepository.php | 8 + tests/Generator/GeneratorTestCase.php | 4 +- tests/Generator/Server/TypeScriptTest.php | 62 ++++ .../Server/resource/typescript/.eslintrc.js | 25 ++ .../Server/resource/typescript/.gitignore | 56 ++++ .../Server/resource/typescript/.prettierrc | 4 + .../Server/resource/typescript/README.md | 73 +++++ .../Server/resource/typescript/nest-cli.json | 8 + .../Server/resource/typescript/package.json | 69 +++++ .../resource/typescript/src/app.module.ts | 10 + .../src/controller/app.controller.ts | 35 +++ .../resource/typescript/src/dto/Entry.ts | 6 + .../typescript/src/dto/EntryCollection.ts | 4 + .../typescript/src/dto/EntryCreate.ts | 6 + .../typescript/src/dto/EntryDelete.ts | 6 + .../typescript/src/dto/EntryMessage.ts | 4 + .../resource/typescript/src/dto/EntryPatch.ts | 6 + .../typescript/src/dto/EntryUpdate.ts | 6 + .../Server/resource/typescript/src/main.ts | 8 + .../resource/typescript/tsconfig.build.json | 4 + .../Server/resource/typescript/tsconfig.json | 21 ++ .../resource/typescript_complex/.eslintrc.js | 25 ++ .../resource/typescript_complex/.gitignore | 56 ++++ .../resource/typescript_complex/.prettierrc | 4 + .../resource/typescript_complex/README.md | 73 +++++ .../resource/typescript_complex/nest-cli.json | 8 + .../resource/typescript_complex/package.json | 69 +++++ .../typescript_complex/src/app.module.ts | 10 + .../src/controller/bar.controller.ts | 17 ++ .../src/controller/foo/bar.controller.ts | 17 ++ .../src/controller/foo/baz.controller.ts | 17 ++ .../typescript_complex/src/dto/Entry.ts | 6 + .../src/dto/EntryCollection.ts | 4 + .../typescript_complex/src/dto/EntryCreate.ts | 6 + .../src/dto/EntryMessage.ts | 4 + .../resource/typescript_complex/src/main.ts | 8 + .../typescript_complex/tsconfig.build.json | 4 + .../resource/typescript_complex/tsconfig.json | 21 ++ tests/GeneratorRegistryTest.php | 2 + 57 files changed, 1656 insertions(+), 3 deletions(-) create mode 100644 src/Generator/Server/Dto/Context.php create mode 100644 src/Generator/Server/Dto/File.php create mode 100644 src/Generator/Server/Dto/Folder.php create mode 100644 src/Generator/Server/Dto/Type.php create mode 100644 src/Generator/Server/ServerAbstract.php create mode 100644 src/Generator/Server/Template/Typescript/.eslintrc.js create mode 100644 src/Generator/Server/Template/Typescript/.gitignore create mode 100644 src/Generator/Server/Template/Typescript/.prettierrc create mode 100644 src/Generator/Server/Template/Typescript/README.md create mode 100644 src/Generator/Server/Template/Typescript/nest-cli.json create mode 100644 src/Generator/Server/Template/Typescript/package.json create mode 100644 src/Generator/Server/Template/Typescript/src/app.module.ts create mode 100644 src/Generator/Server/Template/Typescript/src/controller/.gitkeep create mode 100644 src/Generator/Server/Template/Typescript/src/dto/.gitkeep create mode 100644 src/Generator/Server/Template/Typescript/src/main.ts create mode 100644 src/Generator/Server/Template/Typescript/tsconfig.build.json create mode 100644 src/Generator/Server/Template/Typescript/tsconfig.json create mode 100644 src/Generator/Server/TypeScript.php create mode 100644 tests/Generator/Server/TypeScriptTest.php create mode 100644 tests/Generator/Server/resource/typescript/.eslintrc.js create mode 100644 tests/Generator/Server/resource/typescript/.gitignore create mode 100644 tests/Generator/Server/resource/typescript/.prettierrc create mode 100644 tests/Generator/Server/resource/typescript/README.md create mode 100644 tests/Generator/Server/resource/typescript/nest-cli.json create mode 100644 tests/Generator/Server/resource/typescript/package.json create mode 100644 tests/Generator/Server/resource/typescript/src/app.module.ts create mode 100644 tests/Generator/Server/resource/typescript/src/controller/app.controller.ts create mode 100644 tests/Generator/Server/resource/typescript/src/dto/Entry.ts create mode 100644 tests/Generator/Server/resource/typescript/src/dto/EntryCollection.ts create mode 100644 tests/Generator/Server/resource/typescript/src/dto/EntryCreate.ts create mode 100644 tests/Generator/Server/resource/typescript/src/dto/EntryDelete.ts create mode 100644 tests/Generator/Server/resource/typescript/src/dto/EntryMessage.ts create mode 100644 tests/Generator/Server/resource/typescript/src/dto/EntryPatch.ts create mode 100644 tests/Generator/Server/resource/typescript/src/dto/EntryUpdate.ts create mode 100644 tests/Generator/Server/resource/typescript/src/main.ts create mode 100644 tests/Generator/Server/resource/typescript/tsconfig.build.json create mode 100644 tests/Generator/Server/resource/typescript/tsconfig.json create mode 100644 tests/Generator/Server/resource/typescript_complex/.eslintrc.js create mode 100644 tests/Generator/Server/resource/typescript_complex/.gitignore create mode 100644 tests/Generator/Server/resource/typescript_complex/.prettierrc create mode 100644 tests/Generator/Server/resource/typescript_complex/README.md create mode 100644 tests/Generator/Server/resource/typescript_complex/nest-cli.json create mode 100644 tests/Generator/Server/resource/typescript_complex/package.json create mode 100644 tests/Generator/Server/resource/typescript_complex/src/app.module.ts create mode 100644 tests/Generator/Server/resource/typescript_complex/src/controller/bar.controller.ts create mode 100644 tests/Generator/Server/resource/typescript_complex/src/controller/foo/bar.controller.ts create mode 100644 tests/Generator/Server/resource/typescript_complex/src/controller/foo/baz.controller.ts create mode 100644 tests/Generator/Server/resource/typescript_complex/src/dto/Entry.ts create mode 100644 tests/Generator/Server/resource/typescript_complex/src/dto/EntryCollection.ts create mode 100644 tests/Generator/Server/resource/typescript_complex/src/dto/EntryCreate.ts create mode 100644 tests/Generator/Server/resource/typescript_complex/src/dto/EntryMessage.ts create mode 100644 tests/Generator/Server/resource/typescript_complex/src/main.ts create mode 100644 tests/Generator/Server/resource/typescript_complex/tsconfig.build.json create mode 100644 tests/Generator/Server/resource/typescript_complex/tsconfig.json diff --git a/src/Generator/Server/Dto/Context.php b/src/Generator/Server/Dto/Context.php new file mode 100644 index 00000000..b714b426 --- /dev/null +++ b/src/Generator/Server/Dto/Context.php @@ -0,0 +1,31 @@ + + * + * Copyright 2010-2024 Christoph Kappestein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace PSX\Api\Generator\Server\Dto; + +/** + * @author Christoph Kappestein + * @license http://www.apache.org/licenses/LICENSE-2.0 + * @link https://phpsx.org + * @extends \ArrayObject + */ +class Context extends \ArrayObject +{ +} diff --git a/src/Generator/Server/Dto/File.php b/src/Generator/Server/Dto/File.php new file mode 100644 index 00000000..631d80b6 --- /dev/null +++ b/src/Generator/Server/Dto/File.php @@ -0,0 +1,58 @@ + + * + * Copyright 2010-2024 Christoph Kappestein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace PSX\Api\Generator\Server\Dto; + +use PSX\Api\OperationInterface; + +/** + * @author Christoph Kappestein + * @license http://www.apache.org/licenses/LICENSE-2.0 + * @link https://phpsx.org + */ +class File +{ + private string $name; + private array $operations; + + public function __construct(string $name) + { + $this->name = $name; + $this->operations = []; + } + + public function getName(): string + { + return $this->name; + } + + /** + * @return array + */ + public function getOperations(): array + { + return $this->operations; + } + + public function addOperation(string $name, OperationInterface $operation): void + { + $this->operations[$name] = $operation; + } +} diff --git a/src/Generator/Server/Dto/Folder.php b/src/Generator/Server/Dto/Folder.php new file mode 100644 index 00000000..c6840ea4 --- /dev/null +++ b/src/Generator/Server/Dto/Folder.php @@ -0,0 +1,80 @@ + + * + * Copyright 2010-2024 Christoph Kappestein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace PSX\Api\Generator\Server\Dto; + +/** + * @author Christoph Kappestein + * @license http://www.apache.org/licenses/LICENSE-2.0 + * @link https://phpsx.org + */ +class Folder +{ + private array $folders; + private array $files; + + public function __construct() + { + $this->folders = []; + $this->files = []; + } + + /** + * @return array + */ + public function getFolders(): array + { + return $this->folders; + } + + public function hasFolders(): bool + { + return count($this->folders) > 0; + } + + public function addFolder(string $name, Folder $folder): void + { + $this->folders[$name] = $folder; + } + + public function getFolder(string $name): ?Folder + { + return $this->folders[$name] ?? null; + } + + /** + * @return array + */ + public function getFiles(): array + { + return $this->files; + } + + public function addFile(string $name, File $file): void + { + $this->files[$name] = $file; + } + + public function getFile(string $name): ?File + { + return $this->files[$name] ?? null; + } + +} diff --git a/src/Generator/Server/Dto/Type.php b/src/Generator/Server/Dto/Type.php new file mode 100644 index 00000000..1addd831 --- /dev/null +++ b/src/Generator/Server/Dto/Type.php @@ -0,0 +1,40 @@ + + * + * Copyright 2010-2023 Christoph Kappestein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace PSX\Api\Generator\Server\Dto; + +/** + * Type + * + * @author Christoph Kappestein + * @license http://www.apache.org/licenses/LICENSE-2.0 + * @link https://phpsx.org + */ +class Type +{ + public function __construct( + public string $type, + public string $docType, + public bool $isMap = false, + public bool $isArray = false, + ) + { + } +} diff --git a/src/Generator/Server/ServerAbstract.php b/src/Generator/Server/ServerAbstract.php new file mode 100644 index 00000000..06da659e --- /dev/null +++ b/src/Generator/Server/ServerAbstract.php @@ -0,0 +1,286 @@ + + * + * Copyright 2010-2024 Christoph Kappestein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace PSX\Api\Generator\Server; + +use PSX\Api\Exception\InvalidTypeException; +use PSX\Api\Generator\Client\Util\Naming; +use PSX\Api\Generator\Server\Dto\Context; +use PSX\Api\Generator\Server\Dto\File; +use PSX\Api\Generator\Server\Dto\Folder; +use PSX\Api\GeneratorInterface; +use PSX\Api\OperationInterface; +use PSX\Api\SpecificationInterface; +use PSX\Schema\DefinitionsInterface; +use PSX\Schema\Exception\TypeNotFoundException; +use PSX\Schema\Generator; +use PSX\Schema\Generator\Code\Chunks; +use PSX\Schema\Generator\Normalizer\NormalizerInterface; +use PSX\Schema\Generator\NormalizerAwareInterface; +use PSX\Schema\Generator\Type; +use PSX\Schema\Generator\TypeAwareInterface; +use PSX\Schema\GeneratorInterface as SchemaGeneratorInterface; +use PSX\Schema\Schema; +use PSX\Schema\Type\AnyType; +use PSX\Schema\Type\MapType; +use PSX\Schema\Type\ReferenceType; +use PSX\Schema\Type\StructType; +use PSX\Schema\TypeFactory; +use PSX\Schema\TypeInterface; +use Twig\Environment; +use Twig\Loader\FilesystemLoader; + +/** + * @author Christoph Kappestein + * @license http://www.apache.org/licenses/LICENSE-2.0 + * @link https://phpsx.org + */ +abstract class ServerAbstract implements GeneratorInterface +{ + private string $templateDir; + private Environment $templateEngine; + private SchemaGeneratorInterface $generator; + private Type\GeneratorInterface $typeGenerator; + protected NormalizerInterface $normalizer; + protected Naming $naming; + + public function __construct() + { + $this->templateDir = __DIR__ . '/Template/' . $this->getTemplateDir(); + + $this->templateEngine = new Environment(new FilesystemLoader($this->templateDir), [ + 'cache' => false, + ]); + $this->generator = $this->newGenerator(); + + if ($this->generator instanceof TypeAwareInterface) { + $this->typeGenerator = $this->generator->getTypeGenerator(); + } else { + throw new \RuntimeException('Provided generator is not type aware'); + } + + if ($this->generator instanceof NormalizerAwareInterface) { + $this->normalizer = $this->generator->getNormalizer(); + } else { + throw new \RuntimeException('Provided generator is not normalizer aware'); + } + + $this->naming = new Naming($this->normalizer); + } + + public function generate(SpecificationInterface $specification): Chunks|string + { + $context = $this->buildContext($specification); + $folder = $this->buildFolderStructure($specification); + $chunks = $this->copyFiles($this->templateDir, $context); + + $controllerChunks = $chunks->findByPath($this->getControllerPath()); + if (!$controllerChunks instanceof Chunks) { + throw new \RuntimeException('Could not find configured controller path'); + } + + $this->generateRecursive($folder, $controllerChunks, $specification); + + $modelChunks = $chunks->findByPath($this->getModelPath()); + if (!$modelChunks instanceof Chunks) { + throw new \RuntimeException('Could not find configured model path'); + } + + $this->generateSchema($specification->getDefinitions(), $modelChunks); + + return $chunks; + } + + /** + * @return SchemaGeneratorInterface&Generator\TypeAwareInterface&Generator\NormalizerAwareInterface + */ + abstract protected function newGenerator(): SchemaGeneratorInterface; + + private function generateRecursive(Folder $folder, Chunks $chunks, SpecificationInterface $specification): void + { + foreach ($folder->getFolders() as $name => $child) { + $result = $chunks->getChunk($name); + + if (!$result instanceof Chunks) { + $result = new Chunks(); + $chunks->append($this->buildFolderName($name), $result); + } + + $this->generateRecursive($child, $result, $specification); + } + + foreach ($folder->getFiles() as $name => $file) { + $content = $this->generateControllerFile($file, $specification); + + $chunks->append($this->buildControllerFileName($name) . '.' . $this->getFileExtension(), $content); + } + } + + abstract protected function getControllerPath(): string; + abstract protected function getModelPath(): string; + abstract protected function buildControllerFileName(string $name): string; + abstract protected function generateControllerFile(File $file, SpecificationInterface $specification): string; + abstract protected function getFileExtension(): string; + + protected function buildFolderName(string $name): string + { + return $name; + } + + protected function buildFolderStructure(SpecificationInterface $specification): Folder + { + $folder = new Folder(); + $operations = $specification->getOperations(); + foreach ($operations->getAll() as $operationId => $operation) { + $this->buildRecursive($folder, explode('.', $operationId), $operation); + } + + return $folder; + } + + /** + * @throws InvalidTypeException + * @throws TypeNotFoundException + */ + protected function newType(TypeInterface $type, DefinitionsInterface $definitions): Dto\Type + { + if ($type instanceof ReferenceType) { + // in case we have a reference type we take a look at the reference, normally this is a struct type but in + // some special cases we need to extract the type + $refType = $definitions->getType($type->getRef()); + if ($refType instanceof ReferenceType) { + $refType = $definitions->getType($refType->getRef()); + } + + if (!$refType instanceof StructType && !$refType instanceof MapType && !$refType instanceof AnyType) { + throw new InvalidTypeException('A reference can only point to a struct or map type, got: ' . get_class($refType) . ' for reference: ' . $type->getRef()); + } + } + + return new Dto\Type( + $this->typeGenerator->getType($type), + $this->typeGenerator->getDocType($type), + ); + } + + protected function generateSchema(DefinitionsInterface $definitions, Generator\Code\Chunks $chunks): void + { + $schema = new Schema(TypeFactory::getAny(), $definitions); + $result = $this->generator->generate($schema); + + if ($result instanceof Generator\Code\Chunks) { + foreach ($result->getChunks() as $identifier => $code) { + $chunks->append($this->getFileName($identifier), $this->getFileContent($code, $identifier)); + } + } else { + $chunks->append($this->getFileName('RootSchema'), $result); + } + } + + protected function getFileName(string $identifier): string + { + $identifier = $this->generator->getNormalizer()->file($identifier); + + return $identifier . '.' . $this->getFileExtension(); + } + + protected function getFileContent(string $code, string $identifier): string + { + return $code; + } + + private function buildRecursive(Folder $parent, array $operationId, OperationInterface $operation): void + { + if (count($operationId) === 1 || count($operationId) === 2) { + $fileName = $operationId[0] ?? null; + $method = $operationId[1] ?? null; + + if ($method === null) { + $method = $fileName; + $fileName = 'app'; + } + + $file = $parent->getFile($fileName); + if ($file === null) { + $file = new File($fileName); + $parent->addFile($fileName, $file); + } + + $file->addOperation($method, $operation); + } elseif (count($operationId) > 2) { + $name = $operationId[0] ?? null; + unset($operationId[0]); + + $child = $parent->getFolder($name); + if ($child === null) { + $child = new Folder(); + $parent->addFolder($name, $child); + } + + $this->buildRecursive($child, array_values($operationId), $operation); + } + } + + private function buildContext(SpecificationInterface $specification): Context + { + $context = new Context(); + + return $context; + } + + private function copyFiles(string $templateDir, Context $context): Chunks + { + $chunks = new Chunks(); + $files = scandir($templateDir); + foreach ($files as $file) { + if ($file === '.' || $file === '..' || $file === '.gitkeep') { + continue; + } + + $templatePath = $templateDir . '/' . $file; + + if (is_dir($templatePath)) { + $result = $this->copyFiles($templatePath, $context); + + $chunks->append($file, $result); + } elseif (is_file($templatePath)) { + $extension = pathinfo($file, PATHINFO_EXTENSION); + if ($extension === 'twig') { + $file = substr($file, 0, -5); + $result = $this->templateEngine->render(substr($templatePath, strlen($this->templateDir)), $context->getArrayCopy()); + } else { + $result = file_get_contents($templatePath); + } + + $chunks->append($file, $result); + } + } + + return $chunks; + } + + private function getTemplateDir(): string + { + $parts = explode('\\', static::class); + $lastKey = array_key_last($parts); + + return $parts[$lastKey]; + } +} diff --git a/src/Generator/Server/Template/Typescript/.eslintrc.js b/src/Generator/Server/Template/Typescript/.eslintrc.js new file mode 100644 index 00000000..259de13c --- /dev/null +++ b/src/Generator/Server/Template/Typescript/.eslintrc.js @@ -0,0 +1,25 @@ +module.exports = { + parser: '@typescript-eslint/parser', + parserOptions: { + project: 'tsconfig.json', + tsconfigRootDir: __dirname, + sourceType: 'module', + }, + plugins: ['@typescript-eslint/eslint-plugin'], + extends: [ + 'plugin:@typescript-eslint/recommended', + 'plugin:prettier/recommended', + ], + root: true, + env: { + node: true, + jest: true, + }, + ignorePatterns: ['.eslintrc.js'], + rules: { + '@typescript-eslint/interface-name-prefix': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-explicit-any': 'off', + }, +}; diff --git a/src/Generator/Server/Template/Typescript/.gitignore b/src/Generator/Server/Template/Typescript/.gitignore new file mode 100644 index 00000000..4b56acfb --- /dev/null +++ b/src/Generator/Server/Template/Typescript/.gitignore @@ -0,0 +1,56 @@ +# compiled output +/dist +/node_modules +/build + +# Logs +logs +*.log +npm-debug.log* +pnpm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# OS +.DS_Store + +# Tests +/coverage +/.nyc_output + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# temp directory +.temp +.tmp + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json diff --git a/src/Generator/Server/Template/Typescript/.prettierrc b/src/Generator/Server/Template/Typescript/.prettierrc new file mode 100644 index 00000000..dcb72794 --- /dev/null +++ b/src/Generator/Server/Template/Typescript/.prettierrc @@ -0,0 +1,4 @@ +{ + "singleQuote": true, + "trailingComma": "all" +} \ No newline at end of file diff --git a/src/Generator/Server/Template/Typescript/README.md b/src/Generator/Server/Template/Typescript/README.md new file mode 100644 index 00000000..00a13b11 --- /dev/null +++ b/src/Generator/Server/Template/Typescript/README.md @@ -0,0 +1,73 @@ +

+ Nest Logo +

+ +[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456 +[circleci-url]: https://circleci.com/gh/nestjs/nest + +

A progressive Node.js framework for building efficient and scalable server-side applications.

+

+NPM Version +Package License +NPM Downloads +CircleCI +Coverage +Discord +Backers on Open Collective +Sponsors on Open Collective + + Support us + +

+ + +## Description + +[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. + +## Installation + +```bash +$ npm install +``` + +## Running the app + +```bash +# development +$ npm run start + +# watch mode +$ npm run start:dev + +# production mode +$ npm run start:prod +``` + +## Test + +```bash +# unit tests +$ npm run test + +# e2e tests +$ npm run test:e2e + +# test coverage +$ npm run test:cov +``` + +## Support + +Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). + +## Stay in touch + +- Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) +- Website - [https://nestjs.com](https://nestjs.com/) +- Twitter - [@nestframework](https://twitter.com/nestframework) + +## License + +Nest is [MIT licensed](LICENSE). diff --git a/src/Generator/Server/Template/Typescript/nest-cli.json b/src/Generator/Server/Template/Typescript/nest-cli.json new file mode 100644 index 00000000..f9aa683b --- /dev/null +++ b/src/Generator/Server/Template/Typescript/nest-cli.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "sourceRoot": "src", + "compilerOptions": { + "deleteOutDir": true + } +} diff --git a/src/Generator/Server/Template/Typescript/package.json b/src/Generator/Server/Template/Typescript/package.json new file mode 100644 index 00000000..65fda335 --- /dev/null +++ b/src/Generator/Server/Template/Typescript/package.json @@ -0,0 +1,69 @@ +{ + "name": "sdkgen", + "version": "0.0.1", + "description": "", + "author": "", + "private": true, + "license": "UNLICENSED", + "scripts": { + "build": "nest build", + "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", + "start": "nest start", + "start:dev": "nest start --watch", + "start:debug": "nest start --debug --watch", + "start:prod": "node dist/main", + "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", + "test": "jest", + "test:watch": "jest --watch", + "test:cov": "jest --coverage", + "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", + "test:e2e": "jest --config ./test/jest-e2e.json" + }, + "dependencies": { + "@nestjs/common": "^10.0.0", + "@nestjs/core": "^10.0.0", + "@nestjs/platform-express": "^10.0.0", + "reflect-metadata": "^0.2.0", + "rxjs": "^7.8.1" + }, + "devDependencies": { + "@nestjs/cli": "^10.0.0", + "@nestjs/schematics": "^10.0.0", + "@nestjs/testing": "^10.0.0", + "@types/express": "^4.17.17", + "@types/jest": "^29.5.2", + "@types/node": "^20.3.1", + "@types/supertest": "^6.0.0", + "@typescript-eslint/eslint-plugin": "^7.0.0", + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.42.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-prettier": "^5.0.0", + "jest": "^29.5.0", + "prettier": "^3.0.0", + "source-map-support": "^0.5.21", + "supertest": "^7.0.0", + "ts-jest": "^29.1.0", + "ts-loader": "^9.4.3", + "ts-node": "^10.9.1", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.1.3" + }, + "jest": { + "moduleFileExtensions": [ + "js", + "json", + "ts" + ], + "rootDir": "src", + "testRegex": ".*\\.spec\\.ts$", + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + }, + "collectCoverageFrom": [ + "**/*.(t|j)s" + ], + "coverageDirectory": "../coverage", + "testEnvironment": "node" + } +} diff --git a/src/Generator/Server/Template/Typescript/src/app.module.ts b/src/Generator/Server/Template/Typescript/src/app.module.ts new file mode 100644 index 00000000..353fdd3b --- /dev/null +++ b/src/Generator/Server/Template/Typescript/src/app.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { AppController } from './app.controller'; +import { AppService } from './app.service'; + +@Module({ + imports: [], + controllers: [AppController], + providers: [], +}) +export class AppModule {} diff --git a/src/Generator/Server/Template/Typescript/src/controller/.gitkeep b/src/Generator/Server/Template/Typescript/src/controller/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/src/Generator/Server/Template/Typescript/src/dto/.gitkeep b/src/Generator/Server/Template/Typescript/src/dto/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/src/Generator/Server/Template/Typescript/src/main.ts b/src/Generator/Server/Template/Typescript/src/main.ts new file mode 100644 index 00000000..13cad38c --- /dev/null +++ b/src/Generator/Server/Template/Typescript/src/main.ts @@ -0,0 +1,8 @@ +import { NestFactory } from '@nestjs/core'; +import { AppModule } from './app.module'; + +async function bootstrap() { + const app = await NestFactory.create(AppModule); + await app.listen(3000); +} +bootstrap(); diff --git a/src/Generator/Server/Template/Typescript/tsconfig.build.json b/src/Generator/Server/Template/Typescript/tsconfig.build.json new file mode 100644 index 00000000..64f86c6b --- /dev/null +++ b/src/Generator/Server/Template/Typescript/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] +} diff --git a/src/Generator/Server/Template/Typescript/tsconfig.json b/src/Generator/Server/Template/Typescript/tsconfig.json new file mode 100644 index 00000000..95f5641c --- /dev/null +++ b/src/Generator/Server/Template/Typescript/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "module": "commonjs", + "declaration": true, + "removeComments": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "target": "ES2021", + "sourceMap": true, + "outDir": "./dist", + "baseUrl": "./", + "incremental": true, + "skipLibCheck": true, + "strictNullChecks": false, + "noImplicitAny": false, + "strictBindCallApply": false, + "forceConsistentCasingInFileNames": false, + "noFallthroughCasesInSwitch": false + } +} diff --git a/src/Generator/Server/TypeScript.php b/src/Generator/Server/TypeScript.php new file mode 100644 index 00000000..310c58aa --- /dev/null +++ b/src/Generator/Server/TypeScript.php @@ -0,0 +1,110 @@ + + * + * Copyright 2010-2024 Christoph Kappestein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace PSX\Api\Generator\Server; + +use PSX\Api\Generator\Server\Dto\File; +use PSX\Api\Generator\Server\ServerAbstract; +use PSX\Api\Operation\ArgumentInterface; +use PSX\Api\SpecificationInterface; +use PSX\Schema\Generator; +use PSX\Schema\GeneratorInterface as SchemaGeneratorInterface; + +/** + * @author Christoph Kappestein + * @license http://www.apache.org/licenses/LICENSE-2.0 + * @link https://phpsx.org + */ +class TypeScript extends ServerAbstract +{ + protected function newGenerator(): SchemaGeneratorInterface + { + return new Generator\TypeScript(); + } + + protected function getControllerPath(): string + { + return 'src/controller'; + } + + protected function getModelPath(): string + { + return 'src/dto'; + } + + protected function buildControllerFileName(string $name): string + { + return $name . '.controller'; + } + + protected function getFileExtension(): string + { + return 'ts'; + } + + protected function generateControllerFile(File $file, SpecificationInterface $specification): string + { + $controllerClass = $this->buildControllerClass($file); + + $controller = 'import { Controller, Get, Post, Put, Patch, Delete, HttpCode, Param, Query, Headers, Body } from \'@nestjs/common\'' . "\n"; + $controller.= "\n"; + $controller.= '@Controller()' . "\n"; + $controller.= 'export class ' . $controllerClass . ' {' . "\n"; + + foreach ($file->getOperations() as $operationName => $operation) { + $method = ucfirst(strtolower($operation->getMethod())); + + $args = []; + foreach ($operation->getArguments()->getAll() as $argumentName => $argument) { + if ($argument->getIn() === ArgumentInterface::IN_PATH) { + $type = $this->newType($argument->getSchema(), $specification->getDefinitions()); + $args[] = '@Param(\'' . $argumentName . '\') ' . $this->normalizer->argument($argumentName) . ': ' . $type->type; + } elseif ($argument->getIn() === ArgumentInterface::IN_QUERY) { + $type = $this->newType($argument->getSchema(), $specification->getDefinitions()); + $args[] = '@Query(\'' . $argumentName . '\') ' . $this->normalizer->argument($argumentName) . '?: ' . $type->type; + } elseif ($argument->getIn() === ArgumentInterface::IN_HEADER) { + $type = $this->newType($argument->getSchema(), $specification->getDefinitions()); + $args[] = '@Headers(\'' . $argumentName . '\') ' . $this->normalizer->argument($argumentName) . '?: ' . $type->type; + } elseif ($argument->getIn() === ArgumentInterface::IN_BODY) { + $type = $this->newType($argument->getSchema(), $specification->getDefinitions()); + $args[] = '@Body() ' . $this->normalizer->argument($argumentName) . ': ' . $type->type; + } + } + + $type = $this->newType($operation->getReturn()->getSchema(), $specification->getDefinitions()); + + $controller.= ' @' . $method . '()' . "\n"; + $controller.= ' @HttpCode(' . $operation->getReturn()->getCode() . ')' . "\n"; + $controller.= ' ' . $operationName . '(' . implode(', ', $args) . '): ' . $type->type . ' {' . "\n"; + $controller.= ' // @TODO implement method' . "\n"; + $controller.= ' }' . "\n"; + $controller.= "\n"; + } + + $controller.= '}' . "\n"; + + return $controller; + } + + private function buildControllerClass(File $file): string + { + return ucfirst($file->getName()) . 'Controller'; + } +} diff --git a/src/Repository/LocalRepository.php b/src/Repository/LocalRepository.php index f19e9bed..40096d91 100644 --- a/src/Repository/LocalRepository.php +++ b/src/Repository/LocalRepository.php @@ -35,6 +35,8 @@ class LocalRepository implements RepositoryInterface public const CLIENT_PHP = 'client-php'; public const CLIENT_TYPESCRIPT = 'client-typescript'; + public const SERVER_TYPESCRIPT = 'server-typescript'; + public const MARKUP_CLIENT = 'markup-client'; public const MARKUP_HTML = 'markup-html'; public const MARKUP_MARKDOWN = 'markup-markdown'; @@ -58,6 +60,12 @@ public function getAll(): array 'application/typescript' ); + $result[self::SERVER_TYPESCRIPT] = new GeneratorConfig( + fn(?string $baseUrl, ?Config $config) => new Generator\Server\Typescript(), + 'ts', + 'application/typescript' + ); + $result[self::MARKUP_CLIENT] = new GeneratorConfig( fn(?string $baseUrl, ?Config $config) => new Generator\Markup\Client(), 'md', diff --git a/tests/Generator/GeneratorTestCase.php b/tests/Generator/GeneratorTestCase.php index e7d7ca2c..3f581c11 100644 --- a/tests/Generator/GeneratorTestCase.php +++ b/tests/Generator/GeneratorTestCase.php @@ -201,9 +201,7 @@ protected function getPaths(): array protected function writeChunksToFolder(Chunks $result, string $target): void { - foreach ($result->getChunks() as $file => $code) { - file_put_contents($target . '/' . $file, $code); - } + iterator_to_array($result->writeToFolder($target)); } private function addSchema(SpecificationBuilderInterface $builder, string $schema): TypeInterface diff --git a/tests/Generator/Server/TypeScriptTest.php b/tests/Generator/Server/TypeScriptTest.php new file mode 100644 index 00000000..123a3135 --- /dev/null +++ b/tests/Generator/Server/TypeScriptTest.php @@ -0,0 +1,62 @@ + + * + * Copyright 2010-2023 Christoph Kappestein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace PSX\Api\Tests\Generator\Server; + +use PSX\Api\Generator\Server\TypeScript; +use PSX\Api\Tests\Generator\GeneratorTestCase; + +/** + * TypeScriptTest + * + * @author Christoph Kappestein + * @license http://www.apache.org/licenses/LICENSE-2.0 + * @link https://phpsx.org + */ +class TypeScriptTest extends GeneratorTestCase +{ + public function testGenerate() + { + $generator = new TypeScript(); + + $result = $generator->generate($this->getSpecification()); + $target = __DIR__ . '/resource/typescript'; + + $this->writeChunksToFolder($result, $target); + + $this->assertFileExists($target . '/src/controller/app.controller.ts'); + $this->assertFileExists($target . '/src/dto/Entry.ts'); + } + + public function testGenerateCollection() + { + $generator = new TypeScript(); + + $result = $generator->generate($this->getSpecificationCollection()); + $target = __DIR__ . '/resource/typescript_complex'; + + $this->writeChunksToFolder($result, $target); + + $this->assertFileExists($target . '/src/controller/foo/bar.controller.ts'); + $this->assertFileExists($target . '/src/controller/foo/baz.controller.ts'); + $this->assertFileExists($target . '/src/controller/bar.controller.ts'); + $this->assertFileExists($target . '/src/dto/Entry.ts'); + } +} diff --git a/tests/Generator/Server/resource/typescript/.eslintrc.js b/tests/Generator/Server/resource/typescript/.eslintrc.js new file mode 100644 index 00000000..259de13c --- /dev/null +++ b/tests/Generator/Server/resource/typescript/.eslintrc.js @@ -0,0 +1,25 @@ +module.exports = { + parser: '@typescript-eslint/parser', + parserOptions: { + project: 'tsconfig.json', + tsconfigRootDir: __dirname, + sourceType: 'module', + }, + plugins: ['@typescript-eslint/eslint-plugin'], + extends: [ + 'plugin:@typescript-eslint/recommended', + 'plugin:prettier/recommended', + ], + root: true, + env: { + node: true, + jest: true, + }, + ignorePatterns: ['.eslintrc.js'], + rules: { + '@typescript-eslint/interface-name-prefix': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-explicit-any': 'off', + }, +}; diff --git a/tests/Generator/Server/resource/typescript/.gitignore b/tests/Generator/Server/resource/typescript/.gitignore new file mode 100644 index 00000000..4b56acfb --- /dev/null +++ b/tests/Generator/Server/resource/typescript/.gitignore @@ -0,0 +1,56 @@ +# compiled output +/dist +/node_modules +/build + +# Logs +logs +*.log +npm-debug.log* +pnpm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# OS +.DS_Store + +# Tests +/coverage +/.nyc_output + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# temp directory +.temp +.tmp + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json diff --git a/tests/Generator/Server/resource/typescript/.prettierrc b/tests/Generator/Server/resource/typescript/.prettierrc new file mode 100644 index 00000000..dcb72794 --- /dev/null +++ b/tests/Generator/Server/resource/typescript/.prettierrc @@ -0,0 +1,4 @@ +{ + "singleQuote": true, + "trailingComma": "all" +} \ No newline at end of file diff --git a/tests/Generator/Server/resource/typescript/README.md b/tests/Generator/Server/resource/typescript/README.md new file mode 100644 index 00000000..00a13b11 --- /dev/null +++ b/tests/Generator/Server/resource/typescript/README.md @@ -0,0 +1,73 @@ +

+ Nest Logo +

+ +[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456 +[circleci-url]: https://circleci.com/gh/nestjs/nest + +

A progressive Node.js framework for building efficient and scalable server-side applications.

+

+NPM Version +Package License +NPM Downloads +CircleCI +Coverage +Discord +Backers on Open Collective +Sponsors on Open Collective + + Support us + +

+ + +## Description + +[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. + +## Installation + +```bash +$ npm install +``` + +## Running the app + +```bash +# development +$ npm run start + +# watch mode +$ npm run start:dev + +# production mode +$ npm run start:prod +``` + +## Test + +```bash +# unit tests +$ npm run test + +# e2e tests +$ npm run test:e2e + +# test coverage +$ npm run test:cov +``` + +## Support + +Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). + +## Stay in touch + +- Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) +- Website - [https://nestjs.com](https://nestjs.com/) +- Twitter - [@nestframework](https://twitter.com/nestframework) + +## License + +Nest is [MIT licensed](LICENSE). diff --git a/tests/Generator/Server/resource/typescript/nest-cli.json b/tests/Generator/Server/resource/typescript/nest-cli.json new file mode 100644 index 00000000..f9aa683b --- /dev/null +++ b/tests/Generator/Server/resource/typescript/nest-cli.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "sourceRoot": "src", + "compilerOptions": { + "deleteOutDir": true + } +} diff --git a/tests/Generator/Server/resource/typescript/package.json b/tests/Generator/Server/resource/typescript/package.json new file mode 100644 index 00000000..65fda335 --- /dev/null +++ b/tests/Generator/Server/resource/typescript/package.json @@ -0,0 +1,69 @@ +{ + "name": "sdkgen", + "version": "0.0.1", + "description": "", + "author": "", + "private": true, + "license": "UNLICENSED", + "scripts": { + "build": "nest build", + "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", + "start": "nest start", + "start:dev": "nest start --watch", + "start:debug": "nest start --debug --watch", + "start:prod": "node dist/main", + "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", + "test": "jest", + "test:watch": "jest --watch", + "test:cov": "jest --coverage", + "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", + "test:e2e": "jest --config ./test/jest-e2e.json" + }, + "dependencies": { + "@nestjs/common": "^10.0.0", + "@nestjs/core": "^10.0.0", + "@nestjs/platform-express": "^10.0.0", + "reflect-metadata": "^0.2.0", + "rxjs": "^7.8.1" + }, + "devDependencies": { + "@nestjs/cli": "^10.0.0", + "@nestjs/schematics": "^10.0.0", + "@nestjs/testing": "^10.0.0", + "@types/express": "^4.17.17", + "@types/jest": "^29.5.2", + "@types/node": "^20.3.1", + "@types/supertest": "^6.0.0", + "@typescript-eslint/eslint-plugin": "^7.0.0", + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.42.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-prettier": "^5.0.0", + "jest": "^29.5.0", + "prettier": "^3.0.0", + "source-map-support": "^0.5.21", + "supertest": "^7.0.0", + "ts-jest": "^29.1.0", + "ts-loader": "^9.4.3", + "ts-node": "^10.9.1", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.1.3" + }, + "jest": { + "moduleFileExtensions": [ + "js", + "json", + "ts" + ], + "rootDir": "src", + "testRegex": ".*\\.spec\\.ts$", + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + }, + "collectCoverageFrom": [ + "**/*.(t|j)s" + ], + "coverageDirectory": "../coverage", + "testEnvironment": "node" + } +} diff --git a/tests/Generator/Server/resource/typescript/src/app.module.ts b/tests/Generator/Server/resource/typescript/src/app.module.ts new file mode 100644 index 00000000..353fdd3b --- /dev/null +++ b/tests/Generator/Server/resource/typescript/src/app.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { AppController } from './app.controller'; +import { AppService } from './app.service'; + +@Module({ + imports: [], + controllers: [AppController], + providers: [], +}) +export class AppModule {} diff --git a/tests/Generator/Server/resource/typescript/src/controller/app.controller.ts b/tests/Generator/Server/resource/typescript/src/controller/app.controller.ts new file mode 100644 index 00000000..09988465 --- /dev/null +++ b/tests/Generator/Server/resource/typescript/src/controller/app.controller.ts @@ -0,0 +1,35 @@ +import { Controller, Get, Post, Put, Patch, Delete, HttpCode, Param, Query, Headers, Body } from '@nestjs/common' + +@Controller() +export class AppController { + @Get() + @HttpCode(200) + get(@Param('name') name: string, @Param('type') type: string, @Query('startIndex') startIndex?: number, @Query('float') float?: number, @Query('boolean') boolean?: boolean, @Query('date') date?: string, @Query('datetime') datetime?: string, @Query('args') args?: Entry): EntryCollection { + // @TODO implement method + } + + @Post() + @HttpCode(201) + create(@Param('name') name: string, @Param('type') type: string, @Body() payload: EntryCreate): EntryMessage { + // @TODO implement method + } + + @Put() + @HttpCode(200) + update(@Param('name') name: string, @Param('type') type: string, @Body() payload: Record): Record { + // @TODO implement method + } + + @Delete() + @HttpCode(204) + delete(@Param('name') name: string, @Param('type') type: string, @Body() payload: EntryDelete): EntryMessage { + // @TODO implement method + } + + @Patch() + @HttpCode(200) + patch(@Param('name') name: string, @Param('type') type: string, @Body() payload: Array): Array { + // @TODO implement method + } + +} diff --git a/tests/Generator/Server/resource/typescript/src/dto/Entry.ts b/tests/Generator/Server/resource/typescript/src/dto/Entry.ts new file mode 100644 index 00000000..65de8319 --- /dev/null +++ b/tests/Generator/Server/resource/typescript/src/dto/Entry.ts @@ -0,0 +1,6 @@ +export interface Entry { + id?: number + userId?: number + title?: string + date?: string +} diff --git a/tests/Generator/Server/resource/typescript/src/dto/EntryCollection.ts b/tests/Generator/Server/resource/typescript/src/dto/EntryCollection.ts new file mode 100644 index 00000000..a7aa0d0d --- /dev/null +++ b/tests/Generator/Server/resource/typescript/src/dto/EntryCollection.ts @@ -0,0 +1,4 @@ +import {Entry} from "./Entry"; +export interface EntryCollection { + entry?: Array +} diff --git a/tests/Generator/Server/resource/typescript/src/dto/EntryCreate.ts b/tests/Generator/Server/resource/typescript/src/dto/EntryCreate.ts new file mode 100644 index 00000000..ebbc1aba --- /dev/null +++ b/tests/Generator/Server/resource/typescript/src/dto/EntryCreate.ts @@ -0,0 +1,6 @@ +export interface EntryCreate { + id?: number + userId?: number + title: string + date: string +} diff --git a/tests/Generator/Server/resource/typescript/src/dto/EntryDelete.ts b/tests/Generator/Server/resource/typescript/src/dto/EntryDelete.ts new file mode 100644 index 00000000..83da1b20 --- /dev/null +++ b/tests/Generator/Server/resource/typescript/src/dto/EntryDelete.ts @@ -0,0 +1,6 @@ +export interface EntryDelete { + id: number + userId?: number + title?: string + date?: string +} diff --git a/tests/Generator/Server/resource/typescript/src/dto/EntryMessage.ts b/tests/Generator/Server/resource/typescript/src/dto/EntryMessage.ts new file mode 100644 index 00000000..b7a4b1da --- /dev/null +++ b/tests/Generator/Server/resource/typescript/src/dto/EntryMessage.ts @@ -0,0 +1,4 @@ +export interface EntryMessage { + success?: boolean + message?: string +} diff --git a/tests/Generator/Server/resource/typescript/src/dto/EntryPatch.ts b/tests/Generator/Server/resource/typescript/src/dto/EntryPatch.ts new file mode 100644 index 00000000..1d1a676d --- /dev/null +++ b/tests/Generator/Server/resource/typescript/src/dto/EntryPatch.ts @@ -0,0 +1,6 @@ +export interface EntryPatch { + id: number + userId?: number + title?: string + date?: string +} diff --git a/tests/Generator/Server/resource/typescript/src/dto/EntryUpdate.ts b/tests/Generator/Server/resource/typescript/src/dto/EntryUpdate.ts new file mode 100644 index 00000000..0a94f447 --- /dev/null +++ b/tests/Generator/Server/resource/typescript/src/dto/EntryUpdate.ts @@ -0,0 +1,6 @@ +export interface EntryUpdate { + id: number + userId?: number + title?: string + date?: string +} diff --git a/tests/Generator/Server/resource/typescript/src/main.ts b/tests/Generator/Server/resource/typescript/src/main.ts new file mode 100644 index 00000000..13cad38c --- /dev/null +++ b/tests/Generator/Server/resource/typescript/src/main.ts @@ -0,0 +1,8 @@ +import { NestFactory } from '@nestjs/core'; +import { AppModule } from './app.module'; + +async function bootstrap() { + const app = await NestFactory.create(AppModule); + await app.listen(3000); +} +bootstrap(); diff --git a/tests/Generator/Server/resource/typescript/tsconfig.build.json b/tests/Generator/Server/resource/typescript/tsconfig.build.json new file mode 100644 index 00000000..64f86c6b --- /dev/null +++ b/tests/Generator/Server/resource/typescript/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] +} diff --git a/tests/Generator/Server/resource/typescript/tsconfig.json b/tests/Generator/Server/resource/typescript/tsconfig.json new file mode 100644 index 00000000..95f5641c --- /dev/null +++ b/tests/Generator/Server/resource/typescript/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "module": "commonjs", + "declaration": true, + "removeComments": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "target": "ES2021", + "sourceMap": true, + "outDir": "./dist", + "baseUrl": "./", + "incremental": true, + "skipLibCheck": true, + "strictNullChecks": false, + "noImplicitAny": false, + "strictBindCallApply": false, + "forceConsistentCasingInFileNames": false, + "noFallthroughCasesInSwitch": false + } +} diff --git a/tests/Generator/Server/resource/typescript_complex/.eslintrc.js b/tests/Generator/Server/resource/typescript_complex/.eslintrc.js new file mode 100644 index 00000000..259de13c --- /dev/null +++ b/tests/Generator/Server/resource/typescript_complex/.eslintrc.js @@ -0,0 +1,25 @@ +module.exports = { + parser: '@typescript-eslint/parser', + parserOptions: { + project: 'tsconfig.json', + tsconfigRootDir: __dirname, + sourceType: 'module', + }, + plugins: ['@typescript-eslint/eslint-plugin'], + extends: [ + 'plugin:@typescript-eslint/recommended', + 'plugin:prettier/recommended', + ], + root: true, + env: { + node: true, + jest: true, + }, + ignorePatterns: ['.eslintrc.js'], + rules: { + '@typescript-eslint/interface-name-prefix': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-explicit-any': 'off', + }, +}; diff --git a/tests/Generator/Server/resource/typescript_complex/.gitignore b/tests/Generator/Server/resource/typescript_complex/.gitignore new file mode 100644 index 00000000..4b56acfb --- /dev/null +++ b/tests/Generator/Server/resource/typescript_complex/.gitignore @@ -0,0 +1,56 @@ +# compiled output +/dist +/node_modules +/build + +# Logs +logs +*.log +npm-debug.log* +pnpm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# OS +.DS_Store + +# Tests +/coverage +/.nyc_output + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# temp directory +.temp +.tmp + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json diff --git a/tests/Generator/Server/resource/typescript_complex/.prettierrc b/tests/Generator/Server/resource/typescript_complex/.prettierrc new file mode 100644 index 00000000..dcb72794 --- /dev/null +++ b/tests/Generator/Server/resource/typescript_complex/.prettierrc @@ -0,0 +1,4 @@ +{ + "singleQuote": true, + "trailingComma": "all" +} \ No newline at end of file diff --git a/tests/Generator/Server/resource/typescript_complex/README.md b/tests/Generator/Server/resource/typescript_complex/README.md new file mode 100644 index 00000000..00a13b11 --- /dev/null +++ b/tests/Generator/Server/resource/typescript_complex/README.md @@ -0,0 +1,73 @@ +

+ Nest Logo +

+ +[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456 +[circleci-url]: https://circleci.com/gh/nestjs/nest + +

A progressive Node.js framework for building efficient and scalable server-side applications.

+

+NPM Version +Package License +NPM Downloads +CircleCI +Coverage +Discord +Backers on Open Collective +Sponsors on Open Collective + + Support us + +

+ + +## Description + +[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. + +## Installation + +```bash +$ npm install +``` + +## Running the app + +```bash +# development +$ npm run start + +# watch mode +$ npm run start:dev + +# production mode +$ npm run start:prod +``` + +## Test + +```bash +# unit tests +$ npm run test + +# e2e tests +$ npm run test:e2e + +# test coverage +$ npm run test:cov +``` + +## Support + +Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). + +## Stay in touch + +- Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) +- Website - [https://nestjs.com](https://nestjs.com/) +- Twitter - [@nestframework](https://twitter.com/nestframework) + +## License + +Nest is [MIT licensed](LICENSE). diff --git a/tests/Generator/Server/resource/typescript_complex/nest-cli.json b/tests/Generator/Server/resource/typescript_complex/nest-cli.json new file mode 100644 index 00000000..f9aa683b --- /dev/null +++ b/tests/Generator/Server/resource/typescript_complex/nest-cli.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "sourceRoot": "src", + "compilerOptions": { + "deleteOutDir": true + } +} diff --git a/tests/Generator/Server/resource/typescript_complex/package.json b/tests/Generator/Server/resource/typescript_complex/package.json new file mode 100644 index 00000000..65fda335 --- /dev/null +++ b/tests/Generator/Server/resource/typescript_complex/package.json @@ -0,0 +1,69 @@ +{ + "name": "sdkgen", + "version": "0.0.1", + "description": "", + "author": "", + "private": true, + "license": "UNLICENSED", + "scripts": { + "build": "nest build", + "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", + "start": "nest start", + "start:dev": "nest start --watch", + "start:debug": "nest start --debug --watch", + "start:prod": "node dist/main", + "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", + "test": "jest", + "test:watch": "jest --watch", + "test:cov": "jest --coverage", + "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", + "test:e2e": "jest --config ./test/jest-e2e.json" + }, + "dependencies": { + "@nestjs/common": "^10.0.0", + "@nestjs/core": "^10.0.0", + "@nestjs/platform-express": "^10.0.0", + "reflect-metadata": "^0.2.0", + "rxjs": "^7.8.1" + }, + "devDependencies": { + "@nestjs/cli": "^10.0.0", + "@nestjs/schematics": "^10.0.0", + "@nestjs/testing": "^10.0.0", + "@types/express": "^4.17.17", + "@types/jest": "^29.5.2", + "@types/node": "^20.3.1", + "@types/supertest": "^6.0.0", + "@typescript-eslint/eslint-plugin": "^7.0.0", + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.42.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-prettier": "^5.0.0", + "jest": "^29.5.0", + "prettier": "^3.0.0", + "source-map-support": "^0.5.21", + "supertest": "^7.0.0", + "ts-jest": "^29.1.0", + "ts-loader": "^9.4.3", + "ts-node": "^10.9.1", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.1.3" + }, + "jest": { + "moduleFileExtensions": [ + "js", + "json", + "ts" + ], + "rootDir": "src", + "testRegex": ".*\\.spec\\.ts$", + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + }, + "collectCoverageFrom": [ + "**/*.(t|j)s" + ], + "coverageDirectory": "../coverage", + "testEnvironment": "node" + } +} diff --git a/tests/Generator/Server/resource/typescript_complex/src/app.module.ts b/tests/Generator/Server/resource/typescript_complex/src/app.module.ts new file mode 100644 index 00000000..353fdd3b --- /dev/null +++ b/tests/Generator/Server/resource/typescript_complex/src/app.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { AppController } from './app.controller'; +import { AppService } from './app.service'; + +@Module({ + imports: [], + controllers: [AppController], + providers: [], +}) +export class AppModule {} diff --git a/tests/Generator/Server/resource/typescript_complex/src/controller/bar.controller.ts b/tests/Generator/Server/resource/typescript_complex/src/controller/bar.controller.ts new file mode 100644 index 00000000..91c977fc --- /dev/null +++ b/tests/Generator/Server/resource/typescript_complex/src/controller/bar.controller.ts @@ -0,0 +1,17 @@ +import { Controller, Get, Post, Put, Patch, Delete, HttpCode, Param, Query, Headers, Body } from '@nestjs/common' + +@Controller() +export class BarController { + @Get() + @HttpCode(200) + find(@Param('foo') foo: string): EntryCollection { + // @TODO implement method + } + + @Post() + @HttpCode(201) + put(@Body() payload: EntryCreate): EntryMessage { + // @TODO implement method + } + +} diff --git a/tests/Generator/Server/resource/typescript_complex/src/controller/foo/bar.controller.ts b/tests/Generator/Server/resource/typescript_complex/src/controller/foo/bar.controller.ts new file mode 100644 index 00000000..7738f1e3 --- /dev/null +++ b/tests/Generator/Server/resource/typescript_complex/src/controller/foo/bar.controller.ts @@ -0,0 +1,17 @@ +import { Controller, Get, Post, Put, Patch, Delete, HttpCode, Param, Query, Headers, Body } from '@nestjs/common' + +@Controller() +export class BarController { + @Get() + @HttpCode(200) + get(): EntryCollection { + // @TODO implement method + } + + @Post() + @HttpCode(201) + create(@Body() payload: EntryCreate): EntryMessage { + // @TODO implement method + } + +} diff --git a/tests/Generator/Server/resource/typescript_complex/src/controller/foo/baz.controller.ts b/tests/Generator/Server/resource/typescript_complex/src/controller/foo/baz.controller.ts new file mode 100644 index 00000000..2f1d2b9c --- /dev/null +++ b/tests/Generator/Server/resource/typescript_complex/src/controller/foo/baz.controller.ts @@ -0,0 +1,17 @@ +import { Controller, Get, Post, Put, Patch, Delete, HttpCode, Param, Query, Headers, Body } from '@nestjs/common' + +@Controller() +export class BazController { + @Get() + @HttpCode(200) + get(@Param('year') year: string): EntryCollection { + // @TODO implement method + } + + @Post() + @HttpCode(201) + create(@Body() payload: EntryCreate): EntryMessage { + // @TODO implement method + } + +} diff --git a/tests/Generator/Server/resource/typescript_complex/src/dto/Entry.ts b/tests/Generator/Server/resource/typescript_complex/src/dto/Entry.ts new file mode 100644 index 00000000..65de8319 --- /dev/null +++ b/tests/Generator/Server/resource/typescript_complex/src/dto/Entry.ts @@ -0,0 +1,6 @@ +export interface Entry { + id?: number + userId?: number + title?: string + date?: string +} diff --git a/tests/Generator/Server/resource/typescript_complex/src/dto/EntryCollection.ts b/tests/Generator/Server/resource/typescript_complex/src/dto/EntryCollection.ts new file mode 100644 index 00000000..a7aa0d0d --- /dev/null +++ b/tests/Generator/Server/resource/typescript_complex/src/dto/EntryCollection.ts @@ -0,0 +1,4 @@ +import {Entry} from "./Entry"; +export interface EntryCollection { + entry?: Array +} diff --git a/tests/Generator/Server/resource/typescript_complex/src/dto/EntryCreate.ts b/tests/Generator/Server/resource/typescript_complex/src/dto/EntryCreate.ts new file mode 100644 index 00000000..ebbc1aba --- /dev/null +++ b/tests/Generator/Server/resource/typescript_complex/src/dto/EntryCreate.ts @@ -0,0 +1,6 @@ +export interface EntryCreate { + id?: number + userId?: number + title: string + date: string +} diff --git a/tests/Generator/Server/resource/typescript_complex/src/dto/EntryMessage.ts b/tests/Generator/Server/resource/typescript_complex/src/dto/EntryMessage.ts new file mode 100644 index 00000000..b7a4b1da --- /dev/null +++ b/tests/Generator/Server/resource/typescript_complex/src/dto/EntryMessage.ts @@ -0,0 +1,4 @@ +export interface EntryMessage { + success?: boolean + message?: string +} diff --git a/tests/Generator/Server/resource/typescript_complex/src/main.ts b/tests/Generator/Server/resource/typescript_complex/src/main.ts new file mode 100644 index 00000000..13cad38c --- /dev/null +++ b/tests/Generator/Server/resource/typescript_complex/src/main.ts @@ -0,0 +1,8 @@ +import { NestFactory } from '@nestjs/core'; +import { AppModule } from './app.module'; + +async function bootstrap() { + const app = await NestFactory.create(AppModule); + await app.listen(3000); +} +bootstrap(); diff --git a/tests/Generator/Server/resource/typescript_complex/tsconfig.build.json b/tests/Generator/Server/resource/typescript_complex/tsconfig.build.json new file mode 100644 index 00000000..64f86c6b --- /dev/null +++ b/tests/Generator/Server/resource/typescript_complex/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] +} diff --git a/tests/Generator/Server/resource/typescript_complex/tsconfig.json b/tests/Generator/Server/resource/typescript_complex/tsconfig.json new file mode 100644 index 00000000..95f5641c --- /dev/null +++ b/tests/Generator/Server/resource/typescript_complex/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "module": "commonjs", + "declaration": true, + "removeComments": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "target": "ES2021", + "sourceMap": true, + "outDir": "./dist", + "baseUrl": "./", + "incremental": true, + "skipLibCheck": true, + "strictNullChecks": false, + "noImplicitAny": false, + "strictBindCallApply": false, + "forceConsistentCasingInFileNames": false, + "noFallthroughCasesInSwitch": false + } +} diff --git a/tests/GeneratorRegistryTest.php b/tests/GeneratorRegistryTest.php index e85e8a4c..8ff08e25 100644 --- a/tests/GeneratorRegistryTest.php +++ b/tests/GeneratorRegistryTest.php @@ -51,6 +51,8 @@ public function typeProvider(): array [LocalRepository::CLIENT_PHP], [LocalRepository::CLIENT_TYPESCRIPT], + [LocalRepository::SERVER_TYPESCRIPT], + [LocalRepository::MARKUP_CLIENT], [LocalRepository::MARKUP_HTML], [LocalRepository::MARKUP_MARKDOWN],