From df778ed469de9aa9c9db8b213d2ed2f54b7ddc30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Sun, 3 May 2020 17:52:06 +0200 Subject: [PATCH 01/92] [Docs] Add testing & version policies --- .github/CONTRIBUTING.MD | 91 ++++++++++++++++++++++++++++------------- 1 file changed, 62 insertions(+), 29 deletions(-) diff --git a/.github/CONTRIBUTING.MD b/.github/CONTRIBUTING.MD index 56719bd929..dda4525fba 100644 --- a/.github/CONTRIBUTING.MD +++ b/.github/CONTRIBUTING.MD @@ -4,43 +4,62 @@ Thanks for your interest in FoalTS! There are several ways to contribute. **Reporting bugs are greatly appreciated**, so do not hesitate to open an issue/PR for that! -- [Submit an issue](#submit-an-issue) -- [Submit a PR](#submit-a-pr) -- [Security](#security) +## Security Vulnerabilities -## Submit an issue +If you think you have found a security hole, please do NOT submit an issue but send an email directly to loic.poullain@centraliens.net. -If you find a security vulnerability, please do NOT open an issue. Email loic.poullain@centraliens.net instead. +## Pull Requests -- [Report a bug](https://github.com/FoalTS/foal/issues/new) -- [Suggest a new feature](https://github.com/FoalTS/foal/issues/new) -- [Other (ask a question, etc)](https://github.com/FoalTS/foal/issues/new) +There are [pending issues](https://github.com/FoalTS/foal/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) that may require your help. -## Submit a PR +If you wish to submit a PR, please first submit an issue for discussion (or add a comment on an existing issue). -If the PR is about code (not documentation), please submit an issue first to discuss on this. There are also [pending issues](https://github.com/FoalTS/foal/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) that may require your help. +PRs that correct grammatical errors or small bugs can be submitted directly. -### Set up the development/test environment +## Development Environment -1. Install [docker](https://www.docker.com/). -2. Install [lerna](https://lernajs.io/) by running `npm i -g lerna` -3. Start the dev/test environment by running `npm run start-docker` (to stop use `npm run stop-docker`) +The framework development environment uses [lerna](https://lernajs.io/) for managing packages and [docker](https://www.docker.com/) for database provisioning. -### Install dependencies +**Steps:** +1. Install docker. +2. Install lerna + ``` + npm install -g lerna + ``` +3. Start the databases. + ```sh + npm run start-docker # use `npm run stop-docker` to stop them + ``` +4. Install the root dependencies. + ``` + npm install + ``` +5. Install the dependencies of each package and build each package. + ``` + lerna bootstrap + ``` +6. Check code format. + ``` + npm run lint + ``` +7. Run all the tests. + ``` + lerna run --no-bail test + ``` -Run `lerna bootstrap`. +Tests can also be run individually for each package using `npm run test` or `npm run dev:test` (watch mode) at the root of the package directory. -### Run tests and linting +## Dependency Policy -Run `lerna run --no-bail test` and `npm run lint` from the root directory. +**Do not add new dependencies** (unless they have been improved). Do not install `@types` packages. -You can also run the tests of only one package by going to its directory and running `npm run test` or `npm run dev:test` (watch mode). +TODO -### General guidelines +## Testing and Documentation Policy -Do not install any new dependencies unless they have been approved. Dependencies (except peer ones) should point to *minor* versions (`~1.2.0` instead of `^1.2.0`). +**Testing and documentating the framework is put on a very high priority**. Each line of code must be tested. It is okay to delay the release of a new version if it is to ensure that it is based on robust testing. -When writting code, use the *Test-Driven Developpement (TDD)* approach. +If you wish to submit a PR, please use the *Test-Driven Developpement (TDD)* approach: 1. Write a test. 2. Check that the test fails. 3. Write just enough code to make the test pass. @@ -49,15 +68,31 @@ When writting code, use the *Test-Driven Developpement (TDD)* approach. This method may seem cumbersome at first glance, but it ensures that every line of code in the framework is tested. Reviewers must pull the branch and verify that the tests are actually testing something. If they change even one line of code, they must see that at least one of the tests fails. -**A PR without tests is automatically rejected.** +A PR without robust tests is automatically rejected. -## Security +## Semantic Versionning -To report a security issue please email directly loic.poullain@centraliens.net. +The framework follows the semantic versioning specification. -## Project Structure +| Code status | Stage | Example version | +| --- | --- | --- | +| Backward compatible bug fixes | Patch release | 1.0.1 | +| Backward compatible new features | Minor release | 1.1.0 | +| Changes that break backward compatibility | Major release | 2.0.0 | -The FoalTS project consists of several packages. The publication and dependency management is handled by [lerna](https://github.com/lerna/lerna), a tool for managing JavaScript projects with multiple packages. +## Long-Term Support Policy and Schedule + +All of major releases are supported for 18 months. +- 12 months of *active support* (new features, bug fixes, etc). +- 6 months of *maintenance (LTS)* (critical fixes and security patches). + +| Release | Status | Active Start | Maintenance LTS Start | End-of-life | +| --- | --- | --- | --- | --- | +| 2.x | *Pending* | Summer 2020 | Summer 2021 | 2021-12-31 | +| 1.x | *Active* | 2019-07-11 | Summer 2020 | 2020-12-31 | +| 0.8 | *End-of-Life* | 2019-02-16 | - | 2019-07-11 | + +## Project Architecture ### `@foal/cli` Package Structure @@ -70,8 +105,6 @@ generate # Handles the commands `foal createapp` and `foal generate` '- utils # Contains some helpers shared by all the generators ``` -Usually components that do not rely on a third-party library are located in the `@foal/core` package. - ## Conventions ### Import declarations From 77b6cdd749786a7050c10ce2e83cf34986d09c02 Mon Sep 17 00:00:00 2001 From: Juan Manuel Alberro Date: Tue, 5 May 2020 11:24:22 +0100 Subject: [PATCH 02/92] Update angular-react-vue.md Updated create-react-app command The --typescript option has been deprecated and will be removed in a future release. --- docs/frontend-integration/angular-react-vue.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/frontend-integration/angular-react-vue.md b/docs/frontend-integration/angular-react-vue.md index adef3a42eb..2c0fa0b823 100644 --- a/docs/frontend-integration/angular-react-vue.md +++ b/docs/frontend-integration/angular-react-vue.md @@ -32,7 +32,7 @@ mkdir my-app cd my-app foal createapp backend -npx create-react-app frontend --typescript +npx create-react-app frontend --template typescript cd backend foal connect react ../frontend From 29a073de6559efad8b41db35fc65930f2ea4da05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Wed, 6 May 2020 09:43:17 +0200 Subject: [PATCH 03/92] [Docs] Prettify text --- .github/CONTRIBUTING.MD | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/.github/CONTRIBUTING.MD b/.github/CONTRIBUTING.MD index dda4525fba..12aa2942e7 100644 --- a/.github/CONTRIBUTING.MD +++ b/.github/CONTRIBUTING.MD @@ -70,7 +70,7 @@ This method may seem cumbersome at first glance, but it ensures that every line A PR without robust tests is automatically rejected. -## Semantic Versionning +## Semantic Versioning The framework follows the semantic versioning specification. @@ -96,14 +96,17 @@ All of major releases are supported for 18 months. ### `@foal/cli` Package Structure -```sh -generate # Handles the commands `foal createapp` and `foal generate` - |- generators # Contains the code which renders the templates or updates the files - |- mocks # Contains some pieces of code used to test the file "updaters" - |- specs # Defines how the generated files should look like in different scenarios (specifications) - |- templates # Contains the actual templates used to generate the files - '- utils # Contains some helpers shared by all the generators -``` +The directory `src/generate/` contains the source code of the commands `foal createapp` and `foal generate`. + +Here is the list of its sub-directories: + +| Directory | Description | +| --- | --- | +| generators | Contains the code which renders the templates or updates the files | +| mocks | Contains some pieces of code used to test the file "updaters" | +| specs | Defines how the generated files should look like in different scenarios (specifications) | +| templates | Contains the actual templates used to generate the files | +| utils | Contains some helpers shared by all the generators | ## Conventions From 5fc836f8bc9d46bb5579ce30a547775c083355b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Wed, 6 May 2020 10:10:41 +0200 Subject: [PATCH 04/92] Fix v1.8 docs --- docs/architecture/services-and-dependency-injection.md | 5 +++-- .../groups-and-permissions.md | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/architecture/services-and-dependency-injection.md b/docs/architecture/services-and-dependency-injection.md index eed4c41d48..01837efdd3 100644 --- a/docs/architecture/services-and-dependency-injection.md +++ b/docs/architecture/services-and-dependency-injection.md @@ -295,9 +295,10 @@ export class Logger implements ILogger { *src/index.ts (example)* ```typescript import { createApp, ServiceManager } from '@foal/core'; -import { Connection, createConnection } from 'typeorm'; +import { createConnection } from 'typeorm'; import { AppController } from './app/app.controller'; +import { Product } from './app/entities'; import { Logger } from './app/services'; async function main() { @@ -326,7 +327,7 @@ import { Repository } from 'typeorm'; import { Product } from '../entities'; import { ILogger } from '../services'; -class ApiController { +export class ApiController { @Dependency('product') productRepository: Repository; diff --git a/docs/authentication-and-access-control/groups-and-permissions.md b/docs/authentication-and-access-control/groups-and-permissions.md index fba79a3f01..ebad69e8f9 100644 --- a/docs/authentication-and-access-control/groups-and-permissions.md +++ b/docs/authentication-and-access-control/groups-and-permissions.md @@ -216,6 +216,7 @@ export class User extends UserWithPermissions { } +// You MUST export Group and Permission so that TypeORM can generate migrations. export { Group, Permission } from '@foal/typeorm'; ``` From 8247f1a5baab05a5ab2ac277506a815e332fe578 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Wed, 6 May 2020 12:18:14 +0200 Subject: [PATCH 05/92] [@foal/typeorm] Add fetchMongoDBUser --- packages/core/src/sessions/token.hook.spec.ts | 1 - packages/typeorm/package-lock.json | 87 +++++++++++++++++++ packages/typeorm/package.json | 1 + .../src/utils/fetch-mongodb-user.util.spec.ts | 73 ++++++++++++++++ .../src/utils/fetch-mongodb-user.util.ts | 9 ++ packages/typeorm/src/utils/index.ts | 1 + 6 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 packages/typeorm/src/utils/fetch-mongodb-user.util.spec.ts create mode 100644 packages/typeorm/src/utils/fetch-mongodb-user.util.ts diff --git a/packages/core/src/sessions/token.hook.spec.ts b/packages/core/src/sessions/token.hook.spec.ts index ebb9627545..6565eedefe 100644 --- a/packages/core/src/sessions/token.hook.spec.ts +++ b/packages/core/src/sessions/token.hook.spec.ts @@ -469,7 +469,6 @@ export function testSuite(Token: typeof TokenRequired|typeof TokenOptional, requ strictEqual(ctx.user, user); }); - // TODO: In versions 2+ of FoalTS, the userID should be of type any. it('with the user retrieved from the database (userId is a MongoDB ObjectID).', async () => { const fetchUser = async (id: number|string) => id === 'xjeldksjqkd' ? user : null; const hook = getHookFunction(Token({ store: Store, user: fetchUser })); diff --git a/packages/typeorm/package-lock.json b/packages/typeorm/package-lock.json index b7c4ea292f..abf90a2031 100644 --- a/packages/typeorm/package-lock.json +++ b/packages/typeorm/package-lock.json @@ -269,6 +269,16 @@ "integrity": "sha512-eJzYkFYy9L4JzXsbymsFn3p54D+llV27oTQ+ziJG7WFRheJcNZilgVXMG0LoZtlQSKBsJdWtLFqOD0u+U0jZKA==", "dev": true }, + "bl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.0.tgz", + "integrity": "sha512-wbgvOpqopSr7uq6fJrLH8EsvYMJf9gzfo2jCsL2eTy75qXPukA4pCgHamOQkZtY5vmfVtjB+P3LNlMHW5CEZXA==", + "dev": true, + "requires": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -291,6 +301,12 @@ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, + "bson": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.4.tgz", + "integrity": "sha512-S/yKGU1syOMzO86+dGpg2qGoDL0zvzcb262G+gqEy6TgP6rt6z6qxSFX/8X6vLC91P7G7C3nLs0+bvDzmvBA3Q==", + "dev": true + }, "buffer": { "version": "5.4.3", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.4.3.tgz", @@ -616,6 +632,12 @@ "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "dev": true }, + "denque": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz", + "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==", + "dev": true + }, "detect-libc": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", @@ -1341,6 +1363,13 @@ "mimic-fn": "^1.0.0" } }, + "memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "dev": true, + "optional": true + }, "mime-db": { "version": "1.42.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.42.0.tgz", @@ -1424,6 +1453,20 @@ "supports-color": "5.4.0" } }, + "mongodb": { + "version": "3.5.7", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.5.7.tgz", + "integrity": "sha512-lMtleRT+vIgY/JhhTn1nyGwnSMmJkJELp+4ZbrjctrnBxuLbj6rmLuJFz8W2xUzUqWmqoyVxJLYuC58ZKpcTYQ==", + "dev": true, + "requires": { + "bl": "^2.2.0", + "bson": "^1.1.4", + "denque": "^1.4.1", + "require_optional": "^1.0.1", + "safe-buffer": "^5.1.2", + "saslprep": "^1.0.0" + } + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -2023,6 +2066,24 @@ "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", "dev": true }, + "require_optional": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", + "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", + "dev": true, + "requires": { + "resolve-from": "^2.0.0", + "semver": "^5.1.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, "resolve": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.13.1.tgz", @@ -2032,6 +2093,12 @@ "path-parse": "^1.0.6" } }, + "resolve-from": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", + "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=", + "dev": true + }, "rimraf": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", @@ -2069,6 +2136,16 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, + "saslprep": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", + "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", + "dev": true, + "optional": true, + "requires": { + "sparse-bitfield": "^3.0.3" + } + }, "sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", @@ -2134,6 +2211,16 @@ "source-map": "^0.5.6" } }, + "sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", + "dev": true, + "optional": true, + "requires": { + "memory-pager": "^1.0.2" + } + }, "split": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", diff --git a/packages/typeorm/package.json b/packages/typeorm/package.json index aae3ad8dff..d3b268b954 100644 --- a/packages/typeorm/package.json +++ b/packages/typeorm/package.json @@ -60,6 +60,7 @@ "mocha": "~5.2.0", "mysql": "~2.16.0", "pg": "~7.7.1", + "mongodb": "~3.5.0", "rimraf": "~2.6.2", "sqlite3": "~4.0.4", "ts-node": "~3.3.0", diff --git a/packages/typeorm/src/utils/fetch-mongodb-user.util.spec.ts b/packages/typeorm/src/utils/fetch-mongodb-user.util.spec.ts new file mode 100644 index 0000000000..72b2f3c709 --- /dev/null +++ b/packages/typeorm/src/utils/fetch-mongodb-user.util.spec.ts @@ -0,0 +1,73 @@ +// std +import { notStrictEqual, strictEqual } from 'assert'; + +// 3p +import { Column, createConnection, Entity, getConnection, getMongoManager, ObjectID, ObjectIdColumn } from 'typeorm'; + +// FoalTS +import { fetchMongoDBUser } from './fetch-mongodb-user.util'; + +describe('fetchMongoDBUser', () => { + + @Entity() + class User { + @ObjectIdColumn() + id: ObjectID; + + @Column() + name: string; + } + + @Entity() + class User2 { + @ObjectIdColumn() + // tslint:disable-next-line:variable-name + _id: ObjectID; + + @Column() + name: string; + } + + let user: User; + let user2: User2; + + before(async () => { + await createConnection({ + database: 'test', + dropSchema: true, + entities: [User, User2], + host: 'localhost', + port: 27017, + synchronize: true, + type: 'mongodb', + }); + + user = new User(); + user.name = 'foobar'; + await getMongoManager().save(user); + + user2 = new User2(); + user2.name = 'foobar2'; + await getMongoManager().save(user2); + }); + + after(() => getConnection().close()); + + it('should return the user fetched from the database (id).', async () => { + const actual = await fetchMongoDBUser(User)(user.id.toString()); + notStrictEqual(actual, undefined); + strictEqual(user.id.equals(actual.id), true); + }); + + it('should return the user fetched from the database (_id).', async () => { + const actual = await fetchMongoDBUser(User2)(user2._id.toString()); + notStrictEqual(actual, undefined); + strictEqual(user2._id.equals(actual._id), true); + }); + + it('should return undefined if no user is found in the database.', async () => { + const actual = await fetchMongoDBUser(User)('5c584690ba14b143235f195d'); + strictEqual(actual, undefined); + }); + +}); diff --git a/packages/typeorm/src/utils/fetch-mongodb-user.util.ts b/packages/typeorm/src/utils/fetch-mongodb-user.util.ts new file mode 100644 index 0000000000..edec82dbe3 --- /dev/null +++ b/packages/typeorm/src/utils/fetch-mongodb-user.util.ts @@ -0,0 +1,9 @@ +// 3p +import { Class } from '@foal/core'; +import { getMongoRepository, ObjectID } from 'typeorm'; + +export function fetchMongoDBUser( + userEntityClass: Class<{ id: ObjectID }|{ _id: ObjectID }> +): (id: number|string) => Promise { + return (id: number|string) => getMongoRepository(userEntityClass).findOne(id); +} diff --git a/packages/typeorm/src/utils/index.ts b/packages/typeorm/src/utils/index.ts index 3ac7a2f278..29704ac32a 100644 --- a/packages/typeorm/src/utils/index.ts +++ b/packages/typeorm/src/utils/index.ts @@ -1,2 +1,3 @@ +export { fetchMongoDBUser } from './fetch-mongodb-user.util'; export { fetchUserWithPermissions } from './fetch-user-with-permissions.util'; export { fetchUser } from './fetch-user.util'; From 2aa269e54d6f7ab266b584f68f6d3bfc2e246ae3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Thu, 7 May 2020 10:20:36 +0200 Subject: [PATCH 06/92] [Docs] Add dependency policy --- .github/CONTRIBUTING.MD | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/.github/CONTRIBUTING.MD b/.github/CONTRIBUTING.MD index 12aa2942e7..07954fa304 100644 --- a/.github/CONTRIBUTING.MD +++ b/.github/CONTRIBUTING.MD @@ -53,7 +53,31 @@ Tests can also be run individually for each package using `npm run test` or `npm **Do not add new dependencies** (unless they have been improved). Do not install `@types` packages. -TODO +FoalTS is based on very few dependencies for all these reasons: +- Adding a new dependency often means installing many other packages on which it depends. This phenomenon is often referred to as a *black hole* in Node's ecosystem. + - The size of the `node_modules` directory grows very fast. This can slow down deployment and cause problems if a size limit is imposed on the directory (e.g. in a serverless architecture). + - Due to the large number of dependencies to load, the application may be slow to start. + - The application is more vulnerable to the release of malicious packages. This is what happened on July 12, 2018 when an [attacker compromised the npm account](https://eslint.org/blog/2018/07/postmortem-for-malicious-package-publishes) of an ESLint maintainer. +- We have no guarantee that the maintainers follow the same Foal safety rules (2FA enabled on both Github and npm). +- When a new version of an external package is released (bug fixes, security updates, new features, etc.), it takes time to review each change made in the new version and time to verify that the framework still works as expected with it. +- Packages may support different versions of TypeScript and Node than those supported by the framework. +- External packages can become unmaintained. +- Semantic versioning is not always respected, which is problematic if we want to integrate a security update without introducing breaking changes. +- If we need a new feature in the external dependency, it may take time for the maintainer(s) to implement it. The feature may also be rejected. +- The `@types` packages very often lead to issues. + - The types may be outdated with respect to the current version. + - Semantic versioning is often not respected, which causes the code to break between two *patch* versions. + - Type choices may be arbitrary and not decided by the official maintainers. + - Two packages using the same `@types` module but with different versions may not work properly together. + - Type packages depend on each other by specifying `*` as the version number which causes incompatibilities and great difficulty in defining a replicable environment. +- The installation is often polluted by messages of indirect dependencies in search of funds. + +Some packages, however, can override this policy and be installed if they meet one of the following criteria: +- Rewriting the entire package would require too much work and would be difficult to maintain in the long term. Examples: `TypeORM`, `Mongoose`. +- The code requires very specific knowledge. Examples: `pump`, `jsonwebtoken`, `TypeORM`, `Mongoose`. +- The packages are base packages of the Express.Js framework and can therefore be considered stable, safe and mature. Examples: `cookie-parser`, `morgan`. + +> Dependencies (except peer ones) should point to *minor* versions (`~1.2.0` instead of `^1.2.0`). ## Testing and Documentation Policy From 094a526068c2d632ca7301bc5b5b45f4efafc6d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Thu, 7 May 2020 10:24:53 +0200 Subject: [PATCH 07/92] [Docs] Add Node and TS versions --- .github/CONTRIBUTING.MD | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/CONTRIBUTING.MD b/.github/CONTRIBUTING.MD index 07954fa304..2546e67df0 100644 --- a/.github/CONTRIBUTING.MD +++ b/.github/CONTRIBUTING.MD @@ -110,11 +110,11 @@ All of major releases are supported for 18 months. - 12 months of *active support* (new features, bug fixes, etc). - 6 months of *maintenance (LTS)* (critical fixes and security patches). -| Release | Status | Active Start | Maintenance LTS Start | End-of-life | -| --- | --- | --- | --- | --- | -| 2.x | *Pending* | Summer 2020 | Summer 2021 | 2021-12-31 | -| 1.x | *Active* | 2019-07-11 | Summer 2020 | 2020-12-31 | -| 0.8 | *End-of-Life* | 2019-02-16 | - | 2019-07-11 | +| Release | Status | Active Start | Maintenance Start | End-of-life | Node min version | TS min version | +| --- | --- | --- | --- | --- | --- | --- | +| 2.x | *Pending* | Summer 2020 | Summer 2021 | 2021-12-31 | 10.x | 3.5 | +| 1.x | *Active* | 2019-07-11 | Summer 2020 | 2020-12-31 | 8.x | 3.5 | +| 0.8 | *End-of-Life* | 2019-02-16 | - | 2019-07-11 | 8.x | 2.9 | ## Project Architecture From 1bc0bd7d6fd2e0f0a71cf1417cc5ce12273dab26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Thu, 7 May 2020 15:57:05 +0200 Subject: [PATCH 08/92] MongoDB: check type in fetchUser --- packages/mongoose/src/utils/fetch-user.util.spec.ts | 9 +++++++++ packages/mongoose/src/utils/fetch-user.util.ts | 3 +++ .../typeorm/src/utils/fetch-mongodb-user.util.spec.ts | 11 ++++++++++- packages/typeorm/src/utils/fetch-mongodb-user.util.ts | 7 ++++++- 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/packages/mongoose/src/utils/fetch-user.util.spec.ts b/packages/mongoose/src/utils/fetch-user.util.spec.ts index ad63246a0b..447b09b46d 100644 --- a/packages/mongoose/src/utils/fetch-user.util.spec.ts +++ b/packages/mongoose/src/utils/fetch-user.util.spec.ts @@ -32,6 +32,15 @@ describe('fetchUser', () => { after(() => disconnect()); + it('should throw an Error if the ID is a number.', async () => { + try { + await fetchUser(User)(46); + throw new Error('An error should have been thrown'); + } catch (error) { + strictEqual(error.message, 'Unexpected type for MongoDB user ID: number.'); + } + }); + it('should return the user fetched from the database.', async () => { const actual = await fetchUser(User)(user._id.toString()); notStrictEqual(actual, undefined); diff --git a/packages/mongoose/src/utils/fetch-user.util.ts b/packages/mongoose/src/utils/fetch-user.util.ts index bc0e7a93c2..a4e6f53df1 100644 --- a/packages/mongoose/src/utils/fetch-user.util.ts +++ b/packages/mongoose/src/utils/fetch-user.util.ts @@ -15,6 +15,9 @@ */ export function fetchUser(userModel: any): (id: number|string) => Promise { return (id: number|string) => { + if (typeof id === 'number') { + throw new Error('Unexpected type for MongoDB user ID: number.'); + } return new Promise((resolve, reject) => { userModel.findOne({ _id: id }, (err: any, res: any) => { if (err) { diff --git a/packages/typeorm/src/utils/fetch-mongodb-user.util.spec.ts b/packages/typeorm/src/utils/fetch-mongodb-user.util.spec.ts index 72b2f3c709..8bccf1fc51 100644 --- a/packages/typeorm/src/utils/fetch-mongodb-user.util.spec.ts +++ b/packages/typeorm/src/utils/fetch-mongodb-user.util.spec.ts @@ -53,6 +53,15 @@ describe('fetchMongoDBUser', () => { after(() => getConnection().close()); + it('should throw an Error if the ID is a number.', async () => { + try { + await fetchMongoDBUser(User)(46); + throw new Error('An error should have been thrown'); + } catch (error) { + strictEqual(error.message, 'Unexpected type for MongoDB user ID: number.'); + } + }); + it('should return the user fetched from the database (id).', async () => { const actual = await fetchMongoDBUser(User)(user.id.toString()); notStrictEqual(actual, undefined); @@ -65,7 +74,7 @@ describe('fetchMongoDBUser', () => { strictEqual(user2._id.equals(actual._id), true); }); - it('should return undefined if no user is found in the database.', async () => { + it('should return undefined if no user is found in the database (string).', async () => { const actual = await fetchMongoDBUser(User)('5c584690ba14b143235f195d'); strictEqual(actual, undefined); }); diff --git a/packages/typeorm/src/utils/fetch-mongodb-user.util.ts b/packages/typeorm/src/utils/fetch-mongodb-user.util.ts index edec82dbe3..b0312a8de0 100644 --- a/packages/typeorm/src/utils/fetch-mongodb-user.util.ts +++ b/packages/typeorm/src/utils/fetch-mongodb-user.util.ts @@ -5,5 +5,10 @@ import { getMongoRepository, ObjectID } from 'typeorm'; export function fetchMongoDBUser( userEntityClass: Class<{ id: ObjectID }|{ _id: ObjectID }> ): (id: number|string) => Promise { - return (id: number|string) => getMongoRepository(userEntityClass).findOne(id); + return (id: number|string) => { + if (typeof id === 'number') { + throw new Error('Unexpected type for MongoDB user ID: number.'); + } + return getMongoRepository(userEntityClass).findOne(id); + }; } From 8867e7796c26532a9bd61645c7dbf989af80302c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Thu, 7 May 2020 16:25:51 +0200 Subject: [PATCH 09/92] fetchMongoDBUser: add acceptance test --- .../src/typeorm.mongodb-store.spec.ts | 232 ++++++++++++++++++ 1 file changed, 232 insertions(+) create mode 100644 packages/acceptance-tests/src/typeorm.mongodb-store.spec.ts diff --git a/packages/acceptance-tests/src/typeorm.mongodb-store.spec.ts b/packages/acceptance-tests/src/typeorm.mongodb-store.spec.ts new file mode 100644 index 0000000000..305229d953 --- /dev/null +++ b/packages/acceptance-tests/src/typeorm.mongodb-store.spec.ts @@ -0,0 +1,232 @@ +// std +import { strictEqual } from 'assert'; + +// 3p +import { + Context, + createApp, + dependency, + ExpressApplication, + Get, + hashPassword, + Hook, + HttpResponseForbidden, + HttpResponseNoContent, + HttpResponseOK, + HttpResponseUnauthorized, + Post, + Session, + TokenRequired, + ValidateBody, + verifyPassword +} from '@foal/core'; +import { MongoDBStore } from '@foal/mongodb'; +import { MongoClient } from 'mongodb'; +import * as request from 'supertest'; + +// FoalTS +import { fetchMongoDBUser } from '@foal/typeorm'; +import { + Column, + Connection, + createConnection, + Entity, + getMongoRepository, + ObjectID, + ObjectIdColumn +} from '@foal/typeorm/node_modules/typeorm'; + +describe('[Sample] TypeORM & MongoDB Store', async () => { + + const MONGODB_URI = 'mongodb://localhost:27017/e2e_db'; + + let app: ExpressApplication; + let token: string; + let mongoClient: MongoClient; + + @Entity() + class User { + @ObjectIdColumn() + id: ObjectID; + + @Column({ unique: true }) + email: string; + + @Column() + password: string; + + @Column() + isAdmin: boolean; + } + + function AdminRequired() { + return Hook((ctx: Context) => { + if (!ctx.user.isAdmin) { + return new HttpResponseForbidden(); + } + }); + } + + @TokenRequired({ user: fetchMongoDBUser(User), store: MongoDBStore }) + class MyController { + @Get('/foo') + foo() { + return new HttpResponseOK(); + } + + @Get('/bar') + @AdminRequired() + bar() { + return new HttpResponseOK(); + } + } + + class AuthController { + @dependency + store: MongoDBStore; + + @Post('/logout') + @TokenRequired({ store: MongoDBStore, extendLifeTimeOrUpdate: false }) + async logout(ctx: Context) { + await this.store.destroy(ctx.session.sessionID); + return new HttpResponseNoContent(); + } + + @Post('/login') + @ValidateBody({ + additionalProperties: false, + properties: { + email: { type: 'string', format: 'email' }, + password: { type: 'string' } + }, + required: ['email', 'password'], + type: 'object', + }) + async login(ctx: Context) { + const user = await getMongoRepository(User).findOne({ email: ctx.request.body.email }); + + if (!user) { + return new HttpResponseUnauthorized(); + } + + if (!await verifyPassword(ctx.request.body.password, user.password)) { + return new HttpResponseUnauthorized(); + } + const session = await this.store.createAndSaveSessionFromUser({ id: user.id.toString() }); + return new HttpResponseOK({ + token: session.getToken() + }); + } + } + + class AppController { + subControllers = [ + MyController, + AuthController + ]; + } + + let connection: Connection; + + before(async () => { + process.env.SETTINGS_SESSION_SECRET = 'session-secret'; + process.env.MONGODB_URI = 'mongodb://localhost:27017/e2e_db'; + connection = await createConnection({ + database: 'e2e_db', + dropSchema: true, + entities: [User], + host: 'localhost', + port: 27017, + type: 'mongodb', + }); + + mongoClient = await MongoClient.connect(MONGODB_URI, { useNewUrlParser: true, useUnifiedTopology: true }); + + await mongoClient.db().collection('foalSessions').deleteMany({}); + + const user = new User(); + user.email = 'john@foalts.org'; + user.password = await hashPassword('password'); + user.isAdmin = false; + await getMongoRepository(User).save(user); + + app = createApp(AppController); + }); + + after(async () => { + delete process.env.SETTINGS_SESSION_SECRET; + delete process.env.MONGODB_URI; + return Promise.all([ + connection.close(), + (await app.foal.services.get(MongoDBStore).getMongoDBInstance()).close(), + mongoClient.close() + ]); + }); + + it('should work.', async () => { + /* Try to access routes that require authentication and a specific permission */ + + await Promise.all([ + request(app).get('/foo').expect(400), + request(app).get('/bar').expect(400), + ]); + + /* Try to login with a wrong email */ + + await request(app) + .post('/login') + .send({ email: 'mary@foalts.org', password: 'password' }) + .expect(401); + + /* Try to login with a wrong password */ + + await request(app) + .post('/login') + .send({ email: 'john@foalts.org', password: 'wrong-password' }) + .expect(401); + + /* Log in */ + + await request(app) + .post('/login') + .send({ email: 'john@foalts.org', password: 'password' }) + .expect(200) + .then(response => { + strictEqual(typeof response.body.token, 'string'); + token = response.body.token; + }); + + /* Access and try to access routes that require authentication and a specific permission */ + + await Promise.all([ + request(app).get('/foo').set('Authorization', `Bearer ${token}`).expect(200), + request(app).get('/bar').set('Authorization', `Bearer ${token}`).expect(403), + ]); + + /* Add the admin group and permission */ + + const user2 = await getMongoRepository(User).findOne({ email: 'john@foalts.org' }); + if (!user2) { + throw new Error('John was not found in the database.'); + } + + user2.isAdmin = true; + await getMongoRepository(User).save(user2); + + /* Access the route that requires a specific permission */ + + await request(app).get('/bar').set('Authorization', `Bearer ${token}`).expect(200); + + /* Log out */ + + await request(app).post('/logout').set('Authorization', `Bearer ${token}`).expect(204); + + /* Try to access routes that require authentication and a specific permission */ + + await Promise.all([ + request(app).get('/foo').set('Authorization', `Bearer ${token}`).expect(401), + request(app).get('/bar').set('Authorization', `Bearer ${token}`).expect(401), + ]); + }); + +}); From 76f740436c881599e44fe538a79adfc2d2061f0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Thu, 7 May 2020 16:54:58 +0200 Subject: [PATCH 10/92] Fix linting --- package-lock.json | 6 +++--- packages/typeorm/src/utils/fetch-mongodb-user.util.ts | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 71186876a5..3b344e2913 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2692,9 +2692,9 @@ "dev": true }, "typescript": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz", - "integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.3.tgz", + "integrity": "sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==", "dev": true }, "uglify-js": { diff --git a/packages/typeorm/src/utils/fetch-mongodb-user.util.ts b/packages/typeorm/src/utils/fetch-mongodb-user.util.ts index b0312a8de0..e1c19cb172 100644 --- a/packages/typeorm/src/utils/fetch-mongodb-user.util.ts +++ b/packages/typeorm/src/utils/fetch-mongodb-user.util.ts @@ -1,5 +1,6 @@ // 3p import { Class } from '@foal/core'; +// tslint:disable-next-line:no-unused-variable import { getMongoRepository, ObjectID } from 'typeorm'; export function fetchMongoDBUser( From f3bdfdc9a703bc7c44b79b401de722b944f87e7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Fri, 8 May 2020 08:34:49 +0200 Subject: [PATCH 11/92] [Docs] Rename mongoose file to mongodb --- docs/SUMMARY.md | 4 ++-- docs/databases/{using-mongoose.md => mongodb.md} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename docs/databases/{using-mongoose.md => mongodb.md} (100%) diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 35f5ea34ec..1ceca0afca 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -38,10 +38,10 @@ * [Hooks](./architecture/hooks.md) * [Initialization](./architecture/initialization.md) * Databases - * [TypeORM (SQL & noSQL)](./databases/typeorm.md) + * [SQL Databases (TypeORM)](./databases/typeorm.md) * [Create Models & Queries](./databases/create-models-and-queries.md) * [Generate & Run Migrations](./databases/generate-and-run-migrations.md) - * [Use Mongoose (MongoDB)](./databases/using-mongoose.md) + * [MongoDB (TypeORM or Mongoose)](./databases/mongodb.md) * [Use Another ORM](./databases/using-another-orm.md) * Authentication & Access Control * [Quick Start](./authentication-and-access-control/quick-start.md) diff --git a/docs/databases/using-mongoose.md b/docs/databases/mongodb.md similarity index 100% rename from docs/databases/using-mongoose.md rename to docs/databases/mongodb.md From 0f6d86cc3b84c202da95fd0325713f5dad0b7dc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Fri, 8 May 2020 09:08:10 +0200 Subject: [PATCH 12/92] [Docs] Use TypeORM with MongoDB --- docs/databases/mongodb.md | 99 +++++++++++++++++++++++++++++++++++---- 1 file changed, 89 insertions(+), 10 deletions(-) diff --git a/docs/databases/mongodb.md b/docs/databases/mongodb.md index e3dc416e3e..7c7c91292b 100644 --- a/docs/databases/mongodb.md +++ b/docs/databases/mongodb.md @@ -1,8 +1,10 @@ -# Using Mongoose (MongoDB) +# MongoDB -The previous sections have shown how to use TypeORM with FoalTS. But Foal provides also a support for using [Mongoose](https://mongoosejs.com/), a popular MongoDB ODM. +FoalTS provides two ways to interact with a MongoDB database in your application: [Mongoose](https://mongoosejs.com/) and [TypeORM](https://typeorm.io/#/). -## Generating a new project with Mongoose/MongoDB +## Usage with Mongoose + +### Generating a new project with Mongoose When creating an application with the `--mongodb` flag, the CLI generates a new project with `mongoose` and `@foal/mongoose` installed. The `User` model is defined using this ODM as well as the `create-user` script. @@ -10,7 +12,7 @@ When creating an application with the `--mongodb` flag, the CLI generates a new foal createapp my-app --mongodb ``` -## Generating a model +### Generating a model You cannot create *entities* in a Mongoose project, as it is specific to TypeORM. Instead, you can use this command to generate a new model: @@ -18,7 +20,7 @@ You cannot create *entities* in a Mongoose project, as it is specific to TypeORM foal g model ``` -## Mongoose configuration +### Configuration The URI of the MongoDB database can be passed through: - the config file `config/default.json` with the `mongodb.uri` key, @@ -34,13 +36,17 @@ The URI of the MongoDB database can be passed through: } ``` -## Running migrations +### Authentication + +#### The `MongoDBStore` -The concept of migrations does not exist in MongoDB. That's why there is no migration commands in a Mongoose project. +``` +npm install @foal/mongodb +``` -## Usage with `JWTRequired` +If you use sessions with `@TokenRequired` or `@TokenOptional`, you must use the `MongoDBStore` from `@foal/mongodb`. -The `@foal/mongoose` package provides a `fetchUser` function to be used with `JWTRequired` or `TokenRequired`. It takes an id as parameter and returns a Mongoose model or undefined if the id does not match any user. +#### The `fetchUser` function *Example with JSON Web Tokens*: ```typescript @@ -53,8 +59,81 @@ import { User } from '../models'; class MyController {} ``` +## Usage with TypeORM + +``` +npm uninstall sqlite3 +npm install mongodb +``` + +### Configuration + +*ormconfig.js* +```js +const { Config } = require('@foal/core'); + +module.exports = { + type: "mongodb", + database: Config.get2('database.database', 'string'), + dropSchema: Config.get2('database.dropSchema', 'boolean', false), + entities: ["build/app/**/*.entity.js"], + host: Config.get2('database.host', 'string'), + port: Config.get2('database.port', 'number'), + synchronize: Config.get2('database.synchronize', 'boolean', false) +} + +``` + + +*config/default.json* +```json +{ + "database": { + "database": "mydb", + "host": "localhost", + "port": 27017 + } +} +``` + +### Authentication + +#### The `MongoDBStore` + +``` +npm install @foal/mongodb +``` + +If you use sessions with `@TokenRequired` or `@TokenOptional`, you must use the `MongoDBStore` from `@foal/mongodb`. **The TypeORMStore does not work with noSQL databases.** + +#### The `fetchMongoDBUser` function + +*user.entity.ts* +```typescript +import { Entity, ObjectID, ObjectIdColumn } from 'typeorm'; + +@Entity() +export class User { + + @ObjectIdColumn() + id: ObjectID; + +} +``` + +*Example with JSON Web Tokens*: +```typescript +import { JWTRequired } from '@foal/jwt'; +import { fetchMongoDBUser } from '@foal/typeorm'; + +import { User } from '../entities'; + +@JWTRequired({ user: fetchMongoDBUser(User) }) +class MyController {} +``` + ## Limitations -When using Mongoose in place of TypeORM, there are some features that are not available: +When using MongoDB, there are some features that are not available: - the `foal g rest-api ` command, - and the *Groups & Permissions* system. \ No newline at end of file From 21b3b2ba65b3657d2730c20836b0325bdda0cbe2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Fri, 8 May 2020 09:09:39 +0200 Subject: [PATCH 13/92] Add unit doc --- .../typeorm/src/utils/fetch-mongodb-user.util.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/typeorm/src/utils/fetch-mongodb-user.util.ts b/packages/typeorm/src/utils/fetch-mongodb-user.util.ts index e1c19cb172..f039963637 100644 --- a/packages/typeorm/src/utils/fetch-mongodb-user.util.ts +++ b/packages/typeorm/src/utils/fetch-mongodb-user.util.ts @@ -3,6 +3,21 @@ import { Class } from '@foal/core'; // tslint:disable-next-line:no-unused-variable import { getMongoRepository, ObjectID } from 'typeorm'; +/** + * Create a function that finds the first MongoDB entity that matches some id. + * + * It returns undefined if no entity can be found. + * + * This function is usually used by: + * - TokenRequired (@foal/core) + * - TokenOptional (@foal/core) + * - JWTRequired (@foal/jwt) + * - JWTOptional (@foal/jwt) + * + * @export + * @param {(Class<{ id: ObjectID }|{ _id: ObjectID }>)} userEntityClass - The entity class. + * @returns {((id: number|string) => Promise)} The returned function expecting an id. + */ export function fetchMongoDBUser( userEntityClass: Class<{ id: ObjectID }|{ _id: ObjectID }> ): (id: number|string) => Promise { From 6a39562762c490620a85f5f633800d7f3d135bda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Fri, 8 May 2020 12:37:13 +0200 Subject: [PATCH 14/92] Make all frontend connect projects build on unix --- packages/cli/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 076864acff..c7bb01345f 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -76,6 +76,7 @@ program console.log(' vue'); }) .action(async (framework: string, path: string) => { + path = path.replace(/\\/g, '/'); switch (framework) { case 'angular': connectAngular(path); From 308d78688feb30458b691a76130a05bda669f968 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Fri, 8 May 2020 12:46:01 +0200 Subject: [PATCH 15/92] Make all frontnd connct projects build on unix (2) --- .../cli/src/generate/generators/angular/connect-angular.ts | 4 +++- packages/cli/src/generate/generators/vue/connect-vue.ts | 4 +++- packages/cli/src/index.ts | 1 - 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/generate/generators/angular/connect-angular.ts b/packages/cli/src/generate/generators/angular/connect-angular.ts index 772c54a95d..2923ce21fc 100644 --- a/packages/cli/src/generate/generators/angular/connect-angular.ts +++ b/packages/cli/src/generate/generators/angular/connect-angular.ts @@ -42,7 +42,9 @@ export function connectAngular(path: string) { config.projects[config.defaultProject].architect.serve.options.proxyConfig = 'src/proxy.conf.json'; // Output build directory - const outputPath = join(relative(path, process.cwd()), 'public'); + const outputPath = join(relative(path, process.cwd()), 'public') + // Make projects generated on Windows build on Unix. + .replace(/\\/g, '/'); config.projects[config.defaultProject].architect.build.options.outputPath = outputPath; return JSON.stringify(config, null, 2); diff --git a/packages/cli/src/generate/generators/vue/connect-vue.ts b/packages/cli/src/generate/generators/vue/connect-vue.ts index b44cb5f93b..75f9207869 100644 --- a/packages/cli/src/generate/generators/vue/connect-vue.ts +++ b/packages/cli/src/generate/generators/vue/connect-vue.ts @@ -30,7 +30,9 @@ export function connectVue(path: string) { pkg.vue.devServer.proxy['^/api'] = { target: 'http://localhost:3001' }; // Output build directory - const outputPath = join(relative(path, process.cwd()), 'public'); + const outputPath = join(relative(path, process.cwd()), 'public') + // Make projects generated on Windows build on Unix. + .replace(/\\/g, '/'); pkg.vue.outputDir = outputPath; return JSON.stringify(pkg, null, 2); diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index c7bb01345f..076864acff 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -76,7 +76,6 @@ program console.log(' vue'); }) .action(async (framework: string, path: string) => { - path = path.replace(/\\/g, '/'); switch (framework) { case 'angular': connectAngular(path); From 019545e1fbac04b5ce241194d2e50497961a9736 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Thu, 14 May 2020 15:48:44 +0200 Subject: [PATCH 16/92] Pass body and path params as args in controllers --- .../core/src/express/create-middleware.spec.ts | 16 +++++++++++----- packages/core/src/express/create-middleware.ts | 2 +- packages/core/src/sessions/session.ts | 2 +- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/core/src/express/create-middleware.spec.ts b/packages/core/src/express/create-middleware.spec.ts index aa0f776920..52fe1fd66f 100644 --- a/packages/core/src/express/create-middleware.spec.ts +++ b/packages/core/src/express/create-middleware.spec.ts @@ -53,10 +53,14 @@ describe('createMiddleware', () => { }); it('should call the controller method with a context created from the request.', async () => { - let body = {}; + let ctxBody = null; + let params = null; + let body = null; const route: Route = { - controller: { bar: (ctx: Context) => { - body = ctx.request.body; + controller: { bar: (ctx: Context, paramsBody: any, requestBody: any) => { + ctxBody = ctx.request.body; + params = paramsBody; + body = requestBody; return new HttpResponseOK(); }}, hooks: [], @@ -64,14 +68,16 @@ describe('createMiddleware', () => { path: '', propertyKey: 'bar' }; - const request = createRequest({ body: { foo: 'bar' } }); + const request = createRequest({ body: { foo: 'bar' }, params: { id: '1' } }); const response = createResponse(); const middleware = createMiddleware(route, new ServiceManager()); await middleware(request, response, () => {}); - deepStrictEqual(body, request.body); + deepStrictEqual(ctxBody, request.body); + deepStrictEqual(params, request.params, 'The request params should be passed as the second argument.'); + deepStrictEqual(body, request.body, 'The request body should be passed as the third argument.'); }); it('should call the sync and async hooks (with the ctx and the given ServiceManager)' diff --git a/packages/core/src/express/create-middleware.ts b/packages/core/src/express/create-middleware.ts index 9539cf62eb..9e50c4a46a 100644 --- a/packages/core/src/express/create-middleware.ts +++ b/packages/core/src/express/create-middleware.ts @@ -38,7 +38,7 @@ export function createMiddleware(route: Route, services: ServiceManager) { } if (!isHttpResponse(response)) { - response = await route.controller[route.propertyKey](ctx); + response = await route.controller[route.propertyKey](ctx, ctx.request.params, ctx.request.body); } if (!isHttpResponse(response)) { diff --git a/packages/core/src/sessions/session.ts b/packages/core/src/sessions/session.ts index bfde9a89ec..466d34ff76 100644 --- a/packages/core/src/sessions/session.ts +++ b/packages/core/src/sessions/session.ts @@ -37,7 +37,7 @@ export class Session { } /** - * Return true if an element was added/replaces in the session + * Return true if an element was added/replaced in the session * * @readonly * @type {boolean} From 6b28f9a1c1c28a1f1938de30be88e37b9adc144c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Thu, 14 May 2020 15:50:14 +0200 Subject: [PATCH 17/92] [Docs] Document controller method arguments --- docs/architecture/controllers.md | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/docs/architecture/controllers.md b/docs/architecture/controllers.md index 87eaa19248..5e8276e635 100644 --- a/docs/architecture/controllers.md +++ b/docs/architecture/controllers.md @@ -120,7 +120,7 @@ import { Context, HttpResponseCreated, Post } from '@foal/core'; class AppController { @Post('/products') createProduct(ctx: Context) { - const requestBody = ctx.request.body; + const body = ctx.request.body; // Do something. return new HttpResponseCreated(); } @@ -140,7 +140,7 @@ import { Context, HttpResponseOK, Post } from '@foal/core'; class AppController { @Get('/products/:id') - createProduct(ctx: Context) { + readProduct(ctx: Context) { const productId = ctx.request.params.id; // Do something. return new HttpResponseOK(/* something */); @@ -161,7 +161,7 @@ import { Context, HttpResponseOK, Post } from '@foal/core'; class AppController { @Get('/products') - createProduct(ctx: Context) { + readProducts(ctx: Context) { const limit = ctx.request.query.limit; // Do something. return new HttpResponseOK(/* something */); @@ -201,6 +201,25 @@ class AppController { } ``` + +#### The Controller Method Arguments + +> Available in Foal v1.9.0 onwards. + +The path paramaters and request body are also passed as second and third arguments to the controller method. + +```typescript +import { Context, HttpResponseCreated, Put } from '@foal/core'; + +class AppController { + @Put('/products/:id') + updateProduct(ctx: Context, { id }, body) { + // Do something. + return new HttpResponseCreated(); + } +} +``` + ## HTTP Responses HTTP responses are defined using `HttpResponse` objects. Each controller method must return an instance of this class (or a *promise* of this instance). From aa945085283989d7bede0f5f175a2807748413e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Thu, 14 May 2020 16:18:01 +0200 Subject: [PATCH 18/92] Specify a minor version of typescript-eslint. --- packages/cli/src/generate/specs/app/package.json | 4 ++-- packages/cli/src/generate/specs/app/package.mongodb.json | 4 ++-- packages/cli/src/generate/specs/app/package.mongodb.yaml.json | 4 ++-- packages/cli/src/generate/specs/app/package.yaml.json | 4 ++-- packages/cli/src/generate/templates/app/package.json | 4 ++-- packages/cli/src/generate/templates/app/package.mongodb.json | 4 ++-- .../cli/src/generate/templates/app/package.mongodb.yaml.json | 4 ++-- packages/cli/src/generate/templates/app/package.yaml.json | 4 ++-- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/cli/src/generate/specs/app/package.json b/packages/cli/src/generate/specs/app/package.json index 24e5373106..cbde81f642 100644 --- a/packages/cli/src/generate/specs/app/package.json +++ b/packages/cli/src/generate/specs/app/package.json @@ -46,8 +46,8 @@ "supertest": "^3.3.0", "supervisor": "^0.12.0", "eslint": "^6.7.0", - "@typescript-eslint/eslint-plugin": "^2.7.0", - "@typescript-eslint/parser": "^2.7.0", + "@typescript-eslint/eslint-plugin": "~2.7.0", + "@typescript-eslint/parser": "~2.7.0", "typescript": "~3.5.3" } } diff --git a/packages/cli/src/generate/specs/app/package.mongodb.json b/packages/cli/src/generate/specs/app/package.mongodb.json index 1be88bcee2..46cd57ed86 100644 --- a/packages/cli/src/generate/specs/app/package.mongodb.json +++ b/packages/cli/src/generate/specs/app/package.mongodb.json @@ -41,8 +41,8 @@ "supertest": "^3.3.0", "supervisor": "^0.12.0", "eslint": "^6.7.0", - "@typescript-eslint/eslint-plugin": "^2.7.0", - "@typescript-eslint/parser": "^2.7.0", + "@typescript-eslint/eslint-plugin": "~2.7.0", + "@typescript-eslint/parser": "~2.7.0", "typescript": "~3.5.3" } } diff --git a/packages/cli/src/generate/specs/app/package.mongodb.yaml.json b/packages/cli/src/generate/specs/app/package.mongodb.yaml.json index f1caf7d05f..f60c59df30 100644 --- a/packages/cli/src/generate/specs/app/package.mongodb.yaml.json +++ b/packages/cli/src/generate/specs/app/package.mongodb.yaml.json @@ -42,8 +42,8 @@ "supertest": "^3.3.0", "supervisor": "^0.12.0", "eslint": "^6.7.0", - "@typescript-eslint/eslint-plugin": "^2.7.0", - "@typescript-eslint/parser": "^2.7.0", + "@typescript-eslint/eslint-plugin": "~2.7.0", + "@typescript-eslint/parser": "~2.7.0", "typescript": "~3.5.3" } } diff --git a/packages/cli/src/generate/specs/app/package.yaml.json b/packages/cli/src/generate/specs/app/package.yaml.json index 21aa656e38..25f17818c1 100644 --- a/packages/cli/src/generate/specs/app/package.yaml.json +++ b/packages/cli/src/generate/specs/app/package.yaml.json @@ -47,8 +47,8 @@ "supertest": "^3.3.0", "supervisor": "^0.12.0", "eslint": "^6.7.0", - "@typescript-eslint/eslint-plugin": "^2.7.0", - "@typescript-eslint/parser": "^2.7.0", + "@typescript-eslint/eslint-plugin": "~2.7.0", + "@typescript-eslint/parser": "~2.7.0", "typescript": "~3.5.3" } } diff --git a/packages/cli/src/generate/templates/app/package.json b/packages/cli/src/generate/templates/app/package.json index c5fecfa7c9..0c1b0ecd37 100644 --- a/packages/cli/src/generate/templates/app/package.json +++ b/packages/cli/src/generate/templates/app/package.json @@ -46,8 +46,8 @@ "supertest": "^3.3.0", "supervisor": "^0.12.0", "eslint": "^6.7.0", - "@typescript-eslint/eslint-plugin": "^2.7.0", - "@typescript-eslint/parser": "^2.7.0", + "@typescript-eslint/eslint-plugin": "~2.7.0", + "@typescript-eslint/parser": "~2.7.0", "typescript": "~3.5.3" } } diff --git a/packages/cli/src/generate/templates/app/package.mongodb.json b/packages/cli/src/generate/templates/app/package.mongodb.json index ab26b2ad6e..653fe6bd7e 100644 --- a/packages/cli/src/generate/templates/app/package.mongodb.json +++ b/packages/cli/src/generate/templates/app/package.mongodb.json @@ -41,8 +41,8 @@ "supertest": "^3.3.0", "supervisor": "^0.12.0", "eslint": "^6.7.0", - "@typescript-eslint/eslint-plugin": "^2.7.0", - "@typescript-eslint/parser": "^2.7.0", + "@typescript-eslint/eslint-plugin": "~2.7.0", + "@typescript-eslint/parser": "~2.7.0", "typescript": "~3.5.3" } } diff --git a/packages/cli/src/generate/templates/app/package.mongodb.yaml.json b/packages/cli/src/generate/templates/app/package.mongodb.yaml.json index 761d65435f..7f0a129556 100644 --- a/packages/cli/src/generate/templates/app/package.mongodb.yaml.json +++ b/packages/cli/src/generate/templates/app/package.mongodb.yaml.json @@ -42,8 +42,8 @@ "supertest": "^3.3.0", "supervisor": "^0.12.0", "eslint": "^6.7.0", - "@typescript-eslint/eslint-plugin": "^2.7.0", - "@typescript-eslint/parser": "^2.7.0", + "@typescript-eslint/eslint-plugin": "~2.7.0", + "@typescript-eslint/parser": "~2.7.0", "typescript": "~3.5.3" } } diff --git a/packages/cli/src/generate/templates/app/package.yaml.json b/packages/cli/src/generate/templates/app/package.yaml.json index 41b6227317..d304060d54 100644 --- a/packages/cli/src/generate/templates/app/package.yaml.json +++ b/packages/cli/src/generate/templates/app/package.yaml.json @@ -47,8 +47,8 @@ "supertest": "^3.3.0", "supervisor": "^0.12.0", "eslint": "^6.7.0", - "@typescript-eslint/eslint-plugin": "^2.7.0", - "@typescript-eslint/parser": "^2.7.0", + "@typescript-eslint/eslint-plugin": "~2.7.0", + "@typescript-eslint/parser": "~2.7.0", "typescript": "~3.5.3" } } From 02ff015d0ced218fb59d1c31522999aa590fae9c Mon Sep 17 00:00:00 2001 From: damlys Date: Thu, 14 May 2020 18:22:22 +0200 Subject: [PATCH 19/92] Any command failure ends shell script execution --- benchmarks/run.sh | 3 ++- e2e_test.sh | 69 ++++++++++++++++++++++++----------------------- unit_test.sh | 43 +++++++++++++++-------------- 3 files changed, 61 insertions(+), 54 deletions(-) diff --git a/benchmarks/run.sh b/benchmarks/run.sh index ddef1c64cb..8c98a0fc82 100755 --- a/benchmarks/run.sh +++ b/benchmarks/run.sh @@ -1,4 +1,5 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh +set -e # Empty benchmark.txt if it exists. :> benchmark.txt diff --git a/e2e_test.sh b/e2e_test.sh index d9b181d050..807fbe36e0 100755 --- a/e2e_test.sh +++ b/e2e_test.sh @@ -1,8 +1,11 @@ +#!/usr/bin/env sh +set -e + mkdir e2e-test-temp cd e2e-test-temp # Test app creation -foal createapp my-app || exit 1 +foal createapp my-app cd my-app # Check some compilation errors @@ -12,35 +15,35 @@ if grep -Ril "../../Users/loicp" .; then fi # Test the generators -foal g entity flight || exit 1 -foal g hook foo-bar || exit 1 -foal g service foo || exit 1 -foal g controller bar --register || exit 1 -foal g rest-api product --register || exit 1 -foal g sub-app bar-foo || exit 1 -foal g script bar-script || exit 1 +foal g entity flight +foal g hook foo-bar +foal g service foo +foal g controller bar --register +foal g rest-api product --register +foal g sub-app bar-foo +foal g script bar-script # Test linting -npm run lint || exit 1 +npm run lint # Build and run the unit tests -npm run build:test || exit 1 -npm run start:test || exit 1 +npm run build:test +npm run start:test # Build the app -npm run build:app || exit 1 +npm run build:app # Build and run the migrations -npm run migration:generate -- -n my-migration || exit 1 -npm run build:migrations || exit 1 -npm run migration:run || exit 1 +npm run migration:generate -- -n my-migration +npm run build:migrations +npm run migration:run # Build and run the e2e tests -npm run build:e2e || exit 1 -npm run start:e2e || exit 1 +npm run build:e2e +npm run start:e2e # Test the application when it is started -pm2 start build/index.js || exit 1 +pm2 start build/index.js sleep 1 response=$( curl http://localhost:3001 \ @@ -114,12 +117,12 @@ test_rest_api DELETE "http://localhost:3001/products/1" 204 test_rest_api DELETE "http://localhost:3001/products/1" 404 test_rest_api DELETE "http://localhost:3001/products/ab" 400 -pm2 delete index || exit 1 +pm2 delete index # Test the default shell scripts to create permissions, groups and users. -npm run build:scripts || exit 1 +npm run build:scripts -foal run create-user || exit 1 +foal run create-user ################################################################# # Repeat (almost) the same tests with a Mongoose and YAML project @@ -128,7 +131,7 @@ foal run create-user || exit 1 cd .. # Test app creation -foal createapp my-mongodb-app --mongodb --yaml || exit 1 +foal createapp my-mongodb-app --mongodb --yaml cd my-mongodb-app # Check some compilation errors @@ -138,24 +141,24 @@ if grep -Ril "../../Users/loicp" .; then fi # Test the generators -foal g model flight || exit 1 +foal g model flight # Test linting -npm run lint || exit 1 +npm run lint # Build and run the unit tests -npm run build:test || exit 1 -npm run start:test || exit 1 +npm run build:test +npm run start:test # Build the app -npm run build:app || exit 1 +npm run build:app # Build and run the e2e tests -npm run build:e2e || exit 1 -npm run start:e2e || exit 1 +npm run build:e2e +npm run start:e2e # Test the application when it is started -PORT=3001 pm2 start build/index.js || exit 1 +PORT=3001 pm2 start build/index.js sleep 1 response=$( curl http://localhost:3001 \ @@ -165,8 +168,8 @@ response=$( ) test "$response" -ge 200 && test "$response" -le 299 -pm2 delete index || exit 1 +pm2 delete index # Test the default shell scripts to create users. -npm run build:scripts || exit 1 -foal run create-user || exit 1 \ No newline at end of file +npm run build:scripts +foal run create-user \ No newline at end of file diff --git a/unit_test.sh b/unit_test.sh index 8de3591368..31751468c1 100755 --- a/unit_test.sh +++ b/unit_test.sh @@ -1,23 +1,26 @@ +#!/usr/bin/env sh +set -e + cd packages -cd acceptance-tests && npm run test || exit 1 -cd ../aws-s3 && npm run test || exit 1 -cd ../cli && npm run test || exit 1 -cd ../csrf && npm run test || exit 1 -cd ../ejs && npm run test || exit 1 -cd ../examples && npm run test || exit 1 -cd ../formidable && npm run test || exit 1 -cd ../graphql && npm run test || exit 1 -cd ../jwks-rsa && npm run test || exit 1 -cd ../jwt && npm run test || exit 1 -cd ../mongodb && npm run test || exit 1 -cd ../mongoose && npm run test || exit 1 -cd ../password && npm run test || exit 1 -cd ../redis && npm run test || exit 1 -cd ../social && npm run test || exit 1 -cd ../storage && npm run test || exit 1 -cd ../swagger && npm run test || exit 1 -cd ../typeorm && npm run test || exit 1 -cd ../typestack && npm run test || exit 1 +cd acceptance-tests && npm run test +cd ../aws-s3 && npm run test +cd ../cli && npm run test +cd ../csrf && npm run test +cd ../ejs && npm run test +cd ../examples && npm run test +cd ../formidable && npm run test +cd ../graphql && npm run test +cd ../jwks-rsa && npm run test +cd ../jwt && npm run test +cd ../mongodb && npm run test +cd ../mongoose && npm run test +cd ../password && npm run test +cd ../redis && npm run test +cd ../social && npm run test +cd ../storage && npm run test +cd ../swagger && npm run test +cd ../typeorm && npm run test +cd ../typestack && npm run test # @foal/core at the end because code coverage takes time. -cd ../core && npm run test || exit 1 +cd ../core && npm run test cd ../.. \ No newline at end of file From 6fa0fa96d70d7a721b72a1a3ded98058858530e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Fri, 15 May 2020 22:25:58 +0200 Subject: [PATCH 20/92] [CLI] Add FileSystem --- packages/cli/package.json | 4 +- packages/cli/src/generate/file-system.spec.ts | 620 ++++++++++++++++++ packages/cli/src/generate/file-system.ts | 335 ++++++++++ packages/cli/src/test.ts | 2 +- 4 files changed, 959 insertions(+), 2 deletions(-) create mode 100644 packages/cli/src/generate/file-system.spec.ts create mode 100644 packages/cli/src/generate/file-system.ts diff --git a/packages/cli/package.json b/packages/cli/package.json index 93bce34201..d702baa1e2 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -5,7 +5,9 @@ "main": "./lib/index.js", "types": "./lib/index.d.ts", "scripts": { - "test": "npm run test:generators && npm run test:run-script && npm run test:create-secret && npm run test:rmdir", + "test": "npm run test:generators && npm run test:run-script && npm run test:create-secret && npm run test:rmdir && npm run test:fs", + "test:fs": "mocha --file \"./src/test.ts\" --require ts-node/register \"./src/generate/file-system.spec.ts\"", + "dev:test:fs": "mocha --file \"./src/test.ts\" --require ts-node/register --watch --watch-extensions ts \"./src/generate/file-system.spec.ts\"", "test:generators": "mocha --file \"./src/test.ts\" --require ts-node/register \"./src/generate/generators/**/*.spec.ts\"", "dev:test:generators": "mocha --file \"./src/test.ts\" --require ts-node/register --watch --watch-extensions ts \"./src/generate/generators/**/*.spec.ts\"", "test:rmdir": "mocha --file \"./src/test.ts\" --require ts-node/register \"./src/rmdir/**/*.spec.ts\"", diff --git a/packages/cli/src/generate/file-system.spec.ts b/packages/cli/src/generate/file-system.spec.ts new file mode 100644 index 0000000000..6f161a1471 --- /dev/null +++ b/packages/cli/src/generate/file-system.spec.ts @@ -0,0 +1,620 @@ +import { notStrictEqual, strictEqual } from 'assert'; +import { existsSync, mkdirSync, readFileSync, rmdirSync, unlinkSync, writeFileSync } from 'fs'; +import { join } from 'path'; +import { FileSystem } from './file-system'; + +function rmdir(path: string) { + if (existsSync(path)) { + rmdirSync(path); + } +} + +function rmfile(path: string) { + if (existsSync(path)) { + unlinkSync(path); + } +} + +function mkdir(path: string) { + if (!existsSync(path)) { + mkdirSync(path); + } +} + +describe('FileSystem', () => { + + let fs: FileSystem; + + beforeEach(() => fs = new FileSystem()); + + describe('has a "cd" method that', () => { + + it('should change the current directory.', () => { + strictEqual(fs.currentDir, ''); + fs.cd('foobar/foo'); + strictEqual(fs.currentDir, 'foobar/foo'); + fs.cd('../bar'); + strictEqual(fs.currentDir, 'foobar/bar'); + }); + + }); + + describe('has a "exists" method that', () => { + + beforeEach(() => { + mkdir('test-generators'); + writeFileSync('test-generators/foo.txt', Buffer.alloc(3)); + }); + + afterEach(() => { + rmfile('test-generators/foo.txt'); + rmdir('test-generators'); + }); + + it('should return true if the file or directory exists.', () => { + strictEqual(fs.exists('foo.txt'), true); + }); + + it('should return true if the file or directory does not exist.', () => { + strictEqual(fs.exists('bar.txt'), false); + }); + + }); + + describe('has a "ensureDir" method that', () => { + + beforeEach(() => { + mkdir('test-generators'); + mkdir('test-generators/foo'); + }); + + afterEach(() => { + rmdir('test-generators/foo'); + rmdir('test-generators/bar/foo/foobar'); + rmdir('test-generators/bar/foo'); + rmdir('test-generators/bar'); + rmdir('test-generators'); + }); + + it('should create the directory if it does not exist.', () => { + fs.ensureDir('bar'); + if (!existsSync('test-generators/bar')) { + throw new Error('The directory "bar" does not exist.'); + } + }); + + it('should not throw if the directory already exists.', () => { + fs.ensureDir('foo'); + }); + + it('should create all intermediate directories.', () => { + fs.ensureDir('bar/foo/foobar'); + if (!existsSync('test-generators/bar/foo/foobar')) { + throw new Error('The directory "bar/foo/foobar" does not exist.'); + } + }); + + }); + + describe('has a "ensureDirOnlyIf" method that', () => { + + beforeEach(() => { + mkdir('test-generators'); + }); + + afterEach(() => { + rmdir('test-generators/foo'); + rmdir('test-generators'); + }); + + it('should create the directory if the condition is true.', () => { + fs.ensureDirOnlyIf(true, 'foo'); + if (!existsSync('test-generators/foo')) { + throw new Error('The directory "foo" does not exist.'); + } + }); + + it('should not create the directory if the condition is false.', () => { + fs.ensureDirOnlyIf(false, 'foo'); + if (existsSync('test-generators/foo')) { + throw new Error('The directory "foo" should not exist.'); + } + }); + + }); + + describe('has a "ensureFile" method that', () => { + + beforeEach(() => { + mkdir('test-generators'); + writeFileSync('test-generators/foo.txt', 'hello', 'utf8'); + }); + + afterEach(() => { + rmfile('test-generators/bar.txt'); + rmfile('test-generators/foo.txt'); + rmdir('test-generators'); + }); + + it('should create the file if it does not exist.', () => { + fs.ensureFile('bar.txt'); + if (!existsSync('test-generators/bar.txt')) { + throw new Error('The file "bar.txt" does not exist.'); + } + }); + + it('should not erase the file if it exists.', () => { + fs.ensureFile('foo.txt'); + strictEqual( + readFileSync('test-generators/foo.txt', 'utf8'), + 'hello' + ); + }); + + }); + + describe('has a "copy" method that', () => { + + const templateDir = join(__dirname, 'templates/test-file-system'); + const templatePath = join(__dirname, 'templates/test-file-system/tpl.txt'); + + beforeEach(() => { + mkdir('test-generators'); + mkdir(templateDir); + writeFileSync(templatePath, 'hello', 'utf8'); + }); + + afterEach(() => { + rmfile(templatePath); + rmdir(templateDir); + + rmfile('test-generators/hello.txt'); + rmdir('test-generators'); + }); + + it('should copy the file from the `templates` directory.', () => { + fs.copy('test-file-system/tpl.txt', 'hello.txt'); + if (!existsSync('test-generators/hello.txt')) { + throw new Error('The file "test-generators/hello.txt" does not exist.'); + } + strictEqual( + readFileSync('test-generators/hello.txt', 'utf8'), + 'hello' + ); + }); + + it('should throw an error if the file does not exist.', () => { + try { + fs.copy('test-file-system/foobar.txt', 'hello.txt'); + throw new Error('An error should have been thrown'); + } catch (error) { + strictEqual(error.message, 'The template "test-file-system/foobar.txt" does not exist.'); + } + }); + + }); + + describe('has a "copyOnlyIf" method that', () => { + + const templateDir = join(__dirname, 'templates/test-file-system'); + const templatePath = join(__dirname, 'templates/test-file-system/tpl.txt'); + + beforeEach(() => { + mkdir('test-generators'); + mkdir(templateDir); + writeFileSync(templatePath, 'hello', 'utf8'); + }); + + afterEach(() => { + rmfile(templatePath); + rmdir(templateDir); + + rmfile('test-generators/hello.txt'); + rmdir('test-generators'); + }); + + it('should copy the file if the condition is true.', () => { + fs.copyOnlyIf(true, 'test-file-system/tpl.txt', 'hello.txt'); + if (!existsSync('test-generators/hello.txt')) { + throw new Error('The file "test-generators/hello.txt" does not exist.'); + } + }); + + it('should not copy the file if the condition is false.', () => { + fs.copyOnlyIf(false, 'test-file-system/tpl.txt', 'hello.txt'); + if (existsSync('test-generators/hello.txt')) { + throw new Error('The file "test-generators/hello.txt" should not exist.'); + } + }); + + }); + + describe('has a "render" method that', () => { + + const templateDir = join(__dirname, 'templates/test-file-system'); + const templatePath = join(__dirname, 'templates/test-file-system/tpl.txt'); + + beforeEach(() => { + mkdir('test-generators'); + mkdir(templateDir); + writeFileSync(templatePath, '/* foobar */ /* foobar */ /* barfoo */!', 'utf8'); + }); + + afterEach(() => { + rmfile(templatePath); + rmdir(templateDir); + + rmfile('test-generators/hello.txt'); + rmdir('test-generators'); + }); + + it('should copy and render the template from the `templates` directory.', () => { + fs.render('test-file-system/tpl.txt', 'hello.txt', { + barfoo: 'world', + foobar: 'hello', + }); + if (!existsSync('test-generators/hello.txt')) { + throw new Error('The file "test-generators/hello.txt" does not exist.'); + } + strictEqual( + readFileSync('test-generators/hello.txt', 'utf8'), + 'hello hello world!' + ); + }); + + it('should throw an error if the template does not exist.', () => { + try { + fs.render('test-file-system/foobar.txt', 'hello.txt', {}); + throw new Error('An error should have been thrown'); + } catch (error) { + strictEqual(error.message, 'The template "test-file-system/foobar.txt" does not exist.'); + } + }); + + }); + + describe('has a "renderOnlyIf" method that', () => { + + const templateDir = join(__dirname, 'templates/test-file-system'); + const templatePath = join(__dirname, 'templates/test-file-system/tpl.txt'); + + beforeEach(() => { + mkdir('test-generators'); + mkdir(templateDir); + writeFileSync(templatePath, 'hello', 'utf8'); + }); + + afterEach(() => { + rmfile(templatePath); + rmdir(templateDir); + + rmfile('test-generators/hello.txt'); + rmdir('test-generators'); + }); + + it('should copy the file if the condition is true.', () => { + fs.renderOnlyIf(true, 'test-file-system/tpl.txt', 'hello.txt', {}); + if (!existsSync('test-generators/hello.txt')) { + throw new Error('The file "test-generators/hello.txt" does not exist.'); + } + }); + + it('should not copy the file if the condition is false.', () => { + fs.renderOnlyIf(false, 'test-file-system/tpl.txt', 'hello.txt', {}); + if (existsSync('test-generators/hello.txt')) { + throw new Error('The file "test-generators/hello.txt" should not exist.'); + } + }); + + }); + + describe('has a "modify" method that', () => { + + beforeEach(() => { + mkdir('test-generators'); + writeFileSync('test-generators/hello.txt', 'hello', 'utf8'); + }); + + afterEach(() => { + rmfile('test-generators/hello.txt'); + rmdir('test-generators'); + }); + + it('should modify the file with the given callback.', () => { + fs.modify('hello.txt', content => content + ' world!'); + strictEqual( + readFileSync('test-generators/hello.txt', 'utf8'), + 'hello world!' + ); + }); + + }); + + describe('has a "modifyOnlyIf" method that should', () => { + + beforeEach(() => { + mkdir('test-generators'); + writeFileSync('test-generators/hello.txt', 'hello', 'utf8'); + }); + + afterEach(() => { + rmfile('test-generators/hello.txt'); + rmdir('test-generators'); + }); + + it('should modify the file with the given callback if the condition is true.', () => { + fs.modifyOnlyfIf(true, 'hello.txt', content => content + ' world!'); + strictEqual( + readFileSync('test-generators/hello.txt', 'utf8'), + 'hello world!' + ); + }); + + it('should not modify the file with the given callback if the condition is false.', () => { + fs.modifyOnlyfIf(false, 'hello.txt', content => content + ' world!'); + strictEqual( + readFileSync('test-generators/hello.txt', 'utf8'), + 'hello' + ); + }); + + }); + + describe('has a "addNamedExportIn" method that should', () => { + + beforeEach(() => { + mkdir('test-generators'); + writeFileSync('test-generators/hello.txt', 'export { foo } from \'foo.txt\';\n', 'utf8'); + }); + + afterEach(() => { + rmfile('test-generators/hello.txt'); + rmdir('test-generators'); + }); + + it('should add a named import at the bottom of the file.', () => { + fs.addNamedExportIn('hello.txt', 'bar', 'bar.txt'); + strictEqual( + readFileSync('test-generators/hello.txt', 'utf8'), + 'export { foo } from \'foo.txt\';\nexport { bar } from \'bar.txt\';\n' + ); + }); + + }); + + describe('has a "setUp" method that', () => { + + afterEach(() => { + rmdir('test-generators'); + }); + + it('should create the test client directory.', () => { + fs.setUp(); + if (!existsSync('test-generators')) { + throw new Error('The directory "test-generators" does not exist.'); + } + }); + + it('should set the current directory to none.', () => { + fs.cd('foobar'); + fs.setUp(); + strictEqual(fs.currentDir, ''); + }); + + }); + + describe('has a "tearDown" method that', () => { + + beforeEach(() => { + mkdir('test-generators'); + mkdir('test-generators/foo'); + writeFileSync('test-generators/foo/bar', Buffer.alloc(2)); + }); + + afterEach(() => { + rmfile('test-generators/foo/bar'); + rmdir('test-generators/foo'); + rmdir('test-generators'); + }); + + it('should remove the test client directory and all its contents.', () => { + fs.tearDown(); + if (existsSync('test-generators')) { + throw new Error('The directory "test-generators" should not exist.'); + } + }); + + it('should set the current directory to none.', () => { + fs.cd('foobar'); + fs.tearDown(); + strictEqual(fs.currentDir, ''); + }); + + }); + + describe('has a "assetExists" that', () => { + + beforeEach(() => { + mkdir('test-generators'); + writeFileSync('test-generators/foo', Buffer.alloc(2)); + }); + + afterEach(() => { + rmfile('test-generators/foo'); + rmdir('test-generators'); + }); + + it('should throw an error if the file does not exist.', () => { + try { + fs.assertExists('bar'); + throw new Error('An error should have been thrown.'); + } catch (error) { + strictEqual(error.message, 'The file "bar" does not exist.'); + } + }); + + it('should not throw an error if the file exits.', () => { + fs.assertExists('foo'); + }); + + }); + + describe('has a "assetNotExists" that', () => { + + beforeEach(() => { + mkdir('test-generators'); + writeFileSync('test-generators/foo', Buffer.alloc(2)); + }); + + afterEach(() => { + rmfile('test-generators/foo'); + rmdir('test-generators'); + }); + + it('should throw an error if the file exits.', () => { + try { + fs.assertNotExists('foo'); + throw new Error('An error should have been thrown.'); + } catch (error) { + strictEqual(error.message, 'The file "foo" should not exist.'); + } + }); + + it('should not throw an error if the file does not exist.', () => { + fs.assertNotExists('bar'); + }); + + }); + + describe('has a "assertEqual" that', () => { + + const specDir = join(__dirname, 'specs/test-file-system'); + const specPath = join(__dirname, 'specs/test-file-system/foo.spec'); + const stringSpecPath = join(__dirname, 'specs/test-file-system/foo.spec.txt'); + + beforeEach(() => { + mkdir(specDir); + writeFileSync(specPath, Buffer.alloc(3)); + writeFileSync(stringSpecPath, 'hello\nmy\nworld', 'utf8'); + + mkdir('test-generators'); + writeFileSync('test-generators/foo', Buffer.alloc(3)); + writeFileSync('test-generators/bar', Buffer.alloc(2)); + writeFileSync('test-generators/foo.txt', 'hello\nmy\nworld', 'utf8'); + writeFileSync('test-generators/bar.txt', 'hi\nmy\nearth\n!', 'utf8'); + }); + + afterEach(() => { + rmfile(stringSpecPath); + rmfile(specPath); + rmdir(specDir); + + rmfile('test-generators/foo.txt'); + rmfile('test-generators/bar.txt'); + rmfile('test-generators/foo'); + rmfile('test-generators/bar'); + rmdir('test-generators'); + }); + + it('should throw an error if the two files are different (binary).', () => { + try { + fs.assertEqual('bar', 'test-file-system/foo.spec'); + throw new Error('An error should have been thrown.'); + } catch (error) { + notStrictEqual(error.message, 'An error should have been thrown.'); + } + }); + + it('should not throw an error if the two files are equal (binary).', () => { + fs.assertEqual('foo', 'test-file-system/foo.spec'); + }); + + it('should throw an error if the two files are different (string).', () => { + try { + fs.assertEqual('bar.txt', 'test-file-system/foo.spec.txt'); + throw new Error('An error should have been thrown.'); + } catch (error) { + notStrictEqual(error.message, 'An error should have been thrown.'); + } + }); + + it('should not throw an error if the two files are equal (string).', () => { + fs.assertEqual('foo.txt', 'test-file-system/foo.spec.txt'); + }); + + it('should throw an error if the spec file does not exist.', () => { + try { + fs.assertEqual('foobar', 'test-file-system/hello.txt'); + throw new Error('An error should have been thrown'); + } catch (error) { + strictEqual(error.message, 'The spec file "test-file-system/hello.txt" does not exist.'); + } + }); + + it('should throw understandable errors.', () => { + try { + fs.assertEqual('bar.txt', 'test-file-system/foo.spec.txt'); + throw new Error('An error should have been thrown.'); + } catch (error) { + strictEqual( + error.message, + `The two files "bar.txt" and "test-file-system/foo.spec.txt" are not equal.\n\n` + + 'Line 1\n' + + ' Expected: hello\n' + + ' Actual: hi\n' + + '\n' + + 'Line 3\n' + + ' Expected: world\n' + + ' Actual: earth\n' + + '\n' + + 'Line 4\n' + + ' Expected: undefined\n' + + ' Actual: !' + ); + } + }); + + }); + + describe('has a "copyMock" method that', () => { + + const mockDir = join(__dirname, 'mocks/test-file-system'); + const mockPath = join(__dirname, 'mocks/test-file-system/tpl.txt'); + + beforeEach(() => { + mkdir('test-generators'); + mkdir(mockDir); + writeFileSync(mockPath, 'hello', 'utf8'); + }); + + afterEach(() => { + rmfile(mockPath); + rmdir(mockDir); + + rmfile('test-generators/hello.txt'); + rmdir('test-generators'); + }); + + it('should copy the file from the `mocks` directory.', () => { + fs.copyMock('test-file-system/tpl.txt', 'hello.txt'); + if (!existsSync('test-generators/hello.txt')) { + throw new Error('The file "test-generators/hello.txt" does not exist.'); + } + strictEqual( + readFileSync('test-generators/hello.txt', 'utf8'), + 'hello' + ); + }); + + it('should throw an error if the file does not exist.', () => { + try { + fs.copyMock('test-file-system/foobar.txt', 'hello.txt'); + throw new Error('An error should have been thrown'); + } catch (error) { + strictEqual(error.message, 'The mock file "test-file-system/foobar.txt" does not exist.'); + } + }); + + }); + +}); diff --git a/packages/cli/src/generate/file-system.ts b/packages/cli/src/generate/file-system.ts new file mode 100644 index 0000000000..c4a1217759 --- /dev/null +++ b/packages/cli/src/generate/file-system.ts @@ -0,0 +1,335 @@ +import { deepStrictEqual } from 'assert'; +import { + copyFileSync, + existsSync, + mkdirSync, + readdirSync, + readFileSync, + rmdirSync, + statSync, + unlinkSync, + writeFileSync +} from 'fs'; +import { dirname, join } from 'path'; + +function rmDirAndFiles(path: string) { + const files = readdirSync(path); + for (const file of files) { + const stats = statSync(join(path, file)); + + if (stats.isDirectory()) { + rmDirAndFiles(join(path, file)); + } else { + unlinkSync(join(path, file)); + } + } + + rmdirSync(path); +} + +export class FileSystem { + + currentDir = ''; + + private readonly testDir = 'test-generators'; + + /** + * Change the current working directory. + * + * @param {string} path - Relative path of the directory. + * @returns {this} + * @memberof FileSystem + */ + cd(path: string): this { + this.currentDir = join(this.currentDir, path); + return this; + } + + /** + * Checks if a file or directory exists. + * + * @param {string} path - The path relative to the client directory. + * @returns {boolean} + * @memberof FileSystem + */ + exists(path: string): boolean { + return existsSync(this.parse(path)); + } + + /** + * Recursively ensures that a directory exists. If the directory structure does not + * exist, it is created. + * + * @param {string} path - The path relative to the client directory. + * @returns {this} + * @memberof FileSystem + */ + ensureDir(path: string): this { + const dir = dirname(path); + if (dir !== '.') { + this.ensureDir(dir); + } + if (!existsSync(this.parse(path))) { + mkdirSync(this.parse(path)); + } + return this; + } + + /** + * Recursively ensures that a directory exists if the condition is true. + * If the directory structure does not exist, it is created. + * + * @param {boolean} condition - The condition. + * @param {string} path - The path relative to the client directory. + * @returns {this} + * @memberof FileSystem + */ + ensureDirOnlyIf(condition: boolean, path: string): this { + if (condition) { + this.ensureDir(path); + } + return this; + } + + /** + * Ensures that the file exists. If the file does not exist, it is created. + * + * @param {string} path - The path relative to the client directory. + * @returns {this} + * @memberof FileSystem + */ + ensureFile(path: string): this { + if (!existsSync(this.parse(path))) { + writeFileSync(this.parse(path), '', 'utf8'); + } + return this; + } + + /** + * Copies a file from the `templates` directory. + * + * @param {string} src - The source path relative to the `templates/` directory. + * @param {string} dest - The destination path relative to the client directory. + * @returns {this} + * @memberof FileSystem + */ + copy(src: string, dest: string): this { + const templatePath = join(__dirname, 'templates', src); + if (!existsSync(templatePath)) { + throw new Error(`The template "${src}" does not exist.`); + } + copyFileSync( + templatePath, + this.parse(dest) + ); + return this; + } + + /** + * Copies a file from the `templates` directory if the condition is true. + * + * @param {boolean} condition - The condition. + * @param {string} src - The source path relative to the `templates/` directory. + * @param {string} dest - The destination path relative to the client directory. + * @returns {this} + * @memberof FileSystem + */ + copyOnlyIf(condition: boolean, src: string, dest: string): this { + if (condition) { + this.copy(src, dest); + } + return this; + } + + /** + * Copies and renders a template from the `templates` directory. + * + * @param {string} src - The source path relative to the `templates/` directory. + * @param {string} dest - The destination path relative to the client directory. + * @param {*} locals - The template variables. + * @returns {this} + * @memberof FileSystem + */ + render(src: string, dest: string, locals: any): this { + const templatePath = join(__dirname, 'templates', src); + if (!existsSync(templatePath)) { + throw new Error(`The template "${src}" does not exist.`); + } + let content = readFileSync(templatePath, 'utf8'); + for (const key in locals) { + content = content.split(`/* ${key} */`).join(locals[key]); + } + writeFileSync(this.parse(dest), content, 'utf8'); + return this; + } + + /** + * Copies and renders a template from the `templates` directory if the condition is true. + * + * @param {boolean} condition - The condition. + * @param {string} src - The source path relative to the `templates/` directory. + * @param {string} dest - The destination path relative to the client directory. + * @param {*} locals - The template variables. + * @returns {this} + * @memberof FileSystem + */ + renderOnlyIf(condition: boolean, src: string, dest: string, locals: any): this { + if (condition) { + this.render(src, dest, locals); + } + return this; + } + + /** + * Reads and modifies the content of a file. + * + * @param {string} path - The path relative to the client directory. + * @param {(content: string) => string} callback - The callback that modifies the content. + * @returns {this} + * @memberof FileSystem + */ + modify(path: string, callback: (content: string) => string): this { + const content = readFileSync(this.parse(path), 'utf8'); + writeFileSync(this.parse(path), callback(content), 'utf8'); + return this; + } + + /** + * Reads and modifies the content of a file if the condition is true. + * + * @param {boolean} condition - The condition. + * @param {string} path - The path relative to the client directory. + * @param {(content: string) => string} callback - The callback that modifies the content. + * @returns {this} + * @memberof FileSystem + */ + modifyOnlyfIf(condition: boolean, path: string, callback: (content: string) => string): this { + if (condition) { + this.modify(path, callback); + } + return this; + } + + /** + * Add a named import at the bottom of the file + * + * @param {string} path - The file path relative to the client directory. + * @param {string} specifier - The import specifier. + * @param {string} source - The import source. + * @returns {this} + * @memberof FileSystem + */ + addNamedExportIn(path: string, specifier: string, source: string): this { + this.modify(path, content => `${content}export { ${specifier} } from '${source}';\n`); + return this; + } + + /************************ + Testing Methods + ************************/ + + /** + * Creates the test client directory. Sets current directory to none. + * + * @memberof FileSystem + */ + setUp(): void { + mkdirSync(this.testDir); + this.currentDir = ''; + } + + /** + * Empties and removes the test client directory. Sets current directory to none. + * + * @memberof FileSystem + */ + tearDown(): void { + rmDirAndFiles(this.testDir); + this.currentDir = ''; + } + + /** + * Throws an error if the file or directory does not exist. + * + * @param {string} path - The path relative to the client directory. + * @memberof FileSystem + */ + assertExists(path: string): void { + if (!existsSync(this.parse(path))) { + throw new Error(`The file "${path}" does not exist.`); + } + } + + /** + * Throws an error if the file or directory exists. + * + * @param {string} path - The path relative to the client directory. + * @memberof FileSystem + */ + assertNotExists(path: string): void { + if (existsSync(this.parse(path))) { + throw new Error(`The file "${path}" should not exist.`); + } + } + + /** + * Throws an error if the two files are different. + * + * @param {string} actual - The path relative to the client directory. + * @param {string} expected - The path relative to the `specs/` directory. + * @param {{ binary: boolean }} [{ binary }={ binary: true }] - Specify if the file is binary. + * @memberof FileSystem + */ + assertEqual(actual: string, expected: string, { binary }: { binary: boolean } = { binary: false }): void { + const specPath = join(__dirname, 'specs', expected); + if (!existsSync(specPath)) { + throw new Error(`The spec file "${expected}" does not exist.`); + } + if (binary) { + deepStrictEqual( + readFileSync(this.parse(actual)), + readFileSync(specPath) + ); + } else { + const expectedContent = readFileSync(specPath, 'utf8'); + const actualContent = readFileSync(this.parse(actual), 'utf8'); + if (expectedContent !== actualContent) { + const expectedLines = expectedContent.split('\n'); + const actualLines = actualContent.split('\n'); + let message = `The two files "${actual}" and "${expected}" are not equal.`; + for (let i = 0; i < Math.max(expectedLines.length, actualLines.length); i++) { + if (expectedLines[i] !== actualLines[i]) { + message += `\n\nLine ${i + 1}\n Expected: ${expectedLines[i]}\n Actual: ${actualLines[i]}`; + } + } + throw new Error(message); + } + } + } + + /** + * Copies a file from the `mocks` directory. + * + * @param {string} src - The source path relative to the `mocks/` directory. + * @param {string} dest - The destination path relative to the client directory. + * @memberof FileSystem + */ + copyMock(src: string, dest: string): void { + const mockPath = join(__dirname, 'mocks', src); + if (!existsSync(mockPath)) { + throw new Error(`The mock file "${src}" does not exist.`); + } + copyFileSync( + mockPath, + this.parse(dest) + ); + } + + private parse(path: string) { + if (process.env.P1Z7kEbSUUPMxF8GqPwD8Gx_FOAL_CLI_TEST === 'true') { + return join(this.testDir, this.currentDir, path); + } + return join(this.currentDir, path); + } + +} diff --git a/packages/cli/src/test.ts b/packages/cli/src/test.ts index f7534b31d9..1a4d2d4a33 100644 --- a/packages/cli/src/test.ts +++ b/packages/cli/src/test.ts @@ -1 +1 @@ -process.env.NODE_ENV = 'test'; +process.env.P1Z7kEbSUUPMxF8GqPwD8Gx_FOAL_CLI_TEST = 'true'; From c9180e4efeaacfe6cab3e8bc488845fa619750ff Mon Sep 17 00:00:00 2001 From: Matthew Harvey Date: Sat, 16 May 2020 10:11:11 +1000 Subject: [PATCH 21/92] Add HttpResponseTooManyRequests and isHttpResponseTooManyRequests Fixes #664 --- .../core/src/core/http/http-responses.spec.ts | 57 +++++++++++++++++++ packages/core/src/core/http/http-responses.ts | 46 +++++++++++++++ 2 files changed, 103 insertions(+) diff --git a/packages/core/src/core/http/http-responses.spec.ts b/packages/core/src/core/http/http-responses.spec.ts index a9c67c0da5..196ab1e39c 100644 --- a/packages/core/src/core/http/http-responses.spec.ts +++ b/packages/core/src/core/http/http-responses.spec.ts @@ -23,6 +23,7 @@ import { HttpResponseRedirection, HttpResponseServerError, HttpResponseSuccess, + HttpResponseTooManyRequests, HttpResponseUnauthorized, isHttpResponse, isHttpResponseBadRequest, @@ -41,6 +42,7 @@ import { isHttpResponseRedirection, isHttpResponseServerError, isHttpResponseSuccess, + isHttpResponseTooManyRequests, isHttpResponseUnauthorized } from './http-responses'; @@ -906,6 +908,61 @@ describe('isHttpResponseConflict', () => { }); +describe('HttpResponseTooManyRequests', () => { + + it('should inherit from HttpResponseClientError and HttpResponse', () => { + const httpResponse = new HttpResponseTooManyRequests(); + ok(httpResponse instanceof HttpResponse); + ok(httpResponse instanceof HttpResponseClientError); + }); + + it('should have the correct status.', () => { + const httpResponse = new HttpResponseTooManyRequests(); + strictEqual(httpResponse.statusCode, 429); + strictEqual(httpResponse.statusMessage, 'TOO MANY REQUESTS'); + }); + + it('should accept an optional body.', () => { + let httpResponse = new HttpResponseTooManyRequests(); + strictEqual(httpResponse.body, undefined); + + const body = { foo: 'bar' }; + httpResponse = new HttpResponseTooManyRequests(body); + strictEqual(httpResponse.body, body); + }); + + it('should accept optional options.', () => { + let httpResponse = new HttpResponseTooManyRequests(); + strictEqual(httpResponse.stream, false); + + httpResponse = new HttpResponseTooManyRequests({}, { stream: true }); + strictEqual(httpResponse.stream, true); + }); + +}); + +describe('isHttpResponseTooManyRequests', () => { + + it('should return true if the given object is an instance of HttpResponseTooManyRequests.', () => { + const response = new HttpResponseTooManyRequests(); + strictEqual(isHttpResponseTooManyRequests(response), true); + }); + + it('should return true if the given object has an isHttpResponseTooManyRequests property equal to true.', () => { + const response = { isHttpResponseTooManyRequests: true }; + strictEqual(isHttpResponseTooManyRequests(response), true); + }); + + it('should return false if the given object is not an instance of HttpResponseTooManyRequests and if it ' + + 'has no property isHttpResponseTooManyRequests.', () => { + const response = {}; + strictEqual(isHttpResponseTooManyRequests(response), false); + strictEqual(isHttpResponseTooManyRequests(undefined), false); + strictEqual(isHttpResponseTooManyRequests(null), false); + }); + +}); + describe('isHttpResponseServerError', () => { it('should return true if the given object is an instance of HttpResponseServerError.', () => { diff --git a/packages/core/src/core/http/http-responses.ts b/packages/core/src/core/http/http-responses.ts index 13afc11066..c349dda7f2 100644 --- a/packages/core/src/core/http/http-responses.ts +++ b/packages/core/src/core/http/http-responses.ts @@ -865,6 +865,52 @@ export function isHttpResponseConflict(obj: any): obj is HttpResponseConflict { (typeof obj === 'object' && obj !== null && obj.isHttpResponseConflict === true); } +/** + * Represent an HTTP response with the status 429 - TOO MANY REQUESTS. + * + * @export + * @class HttpResponseTooManyRequests + * @extends {HttpResponseClientError} + */ +export class HttpResponseTooManyRequests extends HttpResponseClientError { + /** + * Property used internally by isHttpResponseTooManyRequests. + * + * @memberof HttpResponseTooManyRequests + */ + readonly isHttpResponseTooManyRequests = true; + statusCode = 429; + statusMessage = 'TOO MANY REQUESTS'; + + /** + * Create an instance of HttpResponseTooManyRequests. + * @param {*} [body] - Optional body of the response. + * @memberof HttpResponseTooManyRequests + */ + constructor(body?: any, options: { stream?: boolean } = {}) { + super(body, options); + } +} + +/** + * Check if an object is an instance of HttpResponseTooManyRequests. + * + * This function is a help when you have several packages using @foal/core. + * Npm can install the package several times, which leads to duplicate class + * definitions. If this is the case, the keyword `instanceof` may return false + * while the object is an instance of the class. This function fixes this + * problem. + * + * @export + * @param {*} obj - The object to check. + * @returns {obj is HttpResponseTooManyRequests} - True if the error is an instance of HttpResponseTooManyRequests. + * False otherwise. + */ +export function isHttpResponseTooManyRequests(obj: any): obj is HttpResponseTooManyRequests { + return obj instanceof HttpResponseTooManyRequests || + (typeof obj === 'object' && obj !== null && obj.isHttpResponseTooManyRequests === true); +} + /* 5xx Server Error */ /** From 8559c6c49d2297e91d96f2572a51f9011b6592c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Sat, 16 May 2020 09:43:43 +0200 Subject: [PATCH 22/92] Trigger test CI on pull requests from forks --- .github/workflows/test.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ba936abc84..d6ca60b536 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,6 +1,10 @@ name: Test -on: [push] +on: + push: + branches: + - master + pull_request: jobs: build: From a8497d4641e39aaf0948f59c22f3f3d68448913a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Sat, 16 May 2020 10:03:38 +0200 Subject: [PATCH 23/92] [CLI] Remove legacy templates --- .../controller/create-controller.spec.ts | 16 ---------------- .../generators/controller/create-controller.ts | 2 +- .../test-foo-bar.controller.graphql.ts | 5 ----- .../controller/test-foo-bar.controller.login.ts | 13 ------------- .../templates/controller/controller.graphql.ts | 5 ----- .../templates/controller/controller.login.ts | 13 ------------- 6 files changed, 1 insertion(+), 53 deletions(-) delete mode 100644 packages/cli/src/generate/specs/controller/test-foo-bar.controller.graphql.ts delete mode 100644 packages/cli/src/generate/specs/controller/test-foo-bar.controller.login.ts delete mode 100644 packages/cli/src/generate/templates/controller/controller.graphql.ts delete mode 100644 packages/cli/src/generate/templates/controller/controller.login.ts diff --git a/packages/cli/src/generate/generators/controller/create-controller.spec.ts b/packages/cli/src/generate/generators/controller/create-controller.spec.ts index 220e7fc3b2..0d40aa487d 100644 --- a/packages/cli/src/generate/generators/controller/create-controller.spec.ts +++ b/packages/cli/src/generate/generators/controller/create-controller.spec.ts @@ -46,22 +46,6 @@ describe('createController', () => { .validateSpec('index.ts', 'index.ts'); }); - it('should render the GraphQL templates in the proper directory.', () => { - createController({ name: 'test-fooBar', type: 'GraphQL', register: false }); - - testEnv - .validateSpec('test-foo-bar.controller.graphql.ts', 'test-foo-bar.controller.ts') - .validateSpec('index.ts', 'index.ts'); - }); - - it('should render the Login templates in the proper directory.', () => { - createController({ name: 'test-fooBar', type: 'Login', register: false }); - - testEnv - .validateSpec('test-foo-bar.controller.login.ts', 'test-foo-bar.controller.ts') - .validateSpec('index.ts', 'index.ts'); - }); - it('should not throw an error if index.ts does not exist.', () => { testEnv.rmfileIfExists('index.ts'); createController({ name: 'test-fooBar', type: 'Empty', register: false }); diff --git a/packages/cli/src/generate/generators/controller/create-controller.ts b/packages/cli/src/generate/generators/controller/create-controller.ts index 3d8cc3ba1c..8fe76a1dc8 100644 --- a/packages/cli/src/generate/generators/controller/create-controller.ts +++ b/packages/cli/src/generate/generators/controller/create-controller.ts @@ -5,7 +5,7 @@ import { existsSync } from 'fs'; import { Generator, getNames } from '../../utils'; import { registerController } from './register-controller'; -export type ControllerType = 'Empty'|'REST'|'GraphQL'|'Login'; +export type ControllerType = 'Empty'|'REST'; export function createController({ name, type, register }: { name: string, type: ControllerType, register: boolean }) { const names = getNames(name); diff --git a/packages/cli/src/generate/specs/controller/test-foo-bar.controller.graphql.ts b/packages/cli/src/generate/specs/controller/test-foo-bar.controller.graphql.ts deleted file mode 100644 index 7e7a1df05d..0000000000 --- a/packages/cli/src/generate/specs/controller/test-foo-bar.controller.graphql.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { GraphQLController } from '@foal/core'; - -export class TestFooBarController extends GraphQLController { - -} diff --git a/packages/cli/src/generate/specs/controller/test-foo-bar.controller.login.ts b/packages/cli/src/generate/specs/controller/test-foo-bar.controller.login.ts deleted file mode 100644 index 8a7aa26894..0000000000 --- a/packages/cli/src/generate/specs/controller/test-foo-bar.controller.login.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { LoginController } from '@foal/core'; - -export class TestFooBarController extends LoginController { - strategies = [ - // strategy('login', MyAuthenticator, mySchema) - ]; - - redirect = { - // failure: '/login', - // logout: '/login', - // success: '/home', - }; -} diff --git a/packages/cli/src/generate/templates/controller/controller.graphql.ts b/packages/cli/src/generate/templates/controller/controller.graphql.ts deleted file mode 100644 index dd6663d097..0000000000 --- a/packages/cli/src/generate/templates/controller/controller.graphql.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { GraphQLController } from '@foal/core'; - -export class /* upperFirstCamelName */Controller extends GraphQLController { - -} diff --git a/packages/cli/src/generate/templates/controller/controller.login.ts b/packages/cli/src/generate/templates/controller/controller.login.ts deleted file mode 100644 index 3b67643ecd..0000000000 --- a/packages/cli/src/generate/templates/controller/controller.login.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { LoginController } from '@foal/core'; - -export class /* upperFirstCamelName */Controller extends LoginController { - strategies = [ - // strategy('login', MyAuthenticator, mySchema) - ]; - - redirect = { - // failure: '/login', - // logout: '/login', - // success: '/home', - }; -} From 5c016dea18754bc7a8e69444ae6113f04e4dd55e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Sat, 16 May 2020 10:15:11 +0200 Subject: [PATCH 24/92] [CLI] Add colors to assertEqual --- packages/cli/src/generate/file-system.spec.ts | 22 ++++++++++++------- packages/cli/src/generate/file-system.ts | 7 +++++- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/packages/cli/src/generate/file-system.spec.ts b/packages/cli/src/generate/file-system.spec.ts index 6f161a1471..92f8bff6a0 100644 --- a/packages/cli/src/generate/file-system.spec.ts +++ b/packages/cli/src/generate/file-system.spec.ts @@ -1,6 +1,12 @@ +// std import { notStrictEqual, strictEqual } from 'assert'; import { existsSync, mkdirSync, readFileSync, rmdirSync, unlinkSync, writeFileSync } from 'fs'; import { join } from 'path'; + +// 3p +import { green, red } from 'colors/safe'; + +// FoalTS import { FileSystem } from './file-system'; function rmdir(path: string) { @@ -560,16 +566,16 @@ describe('FileSystem', () => { error.message, `The two files "bar.txt" and "test-file-system/foo.spec.txt" are not equal.\n\n` + 'Line 1\n' - + ' Expected: hello\n' - + ' Actual: hi\n' - + '\n' + + green(' Expected: hello\n') + + red(' Actual: hi') + + '\n\n' + 'Line 3\n' - + ' Expected: world\n' - + ' Actual: earth\n' - + '\n' + + green(' Expected: world\n') + + red(' Actual: earth') + + '\n\n' + 'Line 4\n' - + ' Expected: undefined\n' - + ' Actual: !' + + green(' Expected: undefined\n') + + red(' Actual: !') ); } }); diff --git a/packages/cli/src/generate/file-system.ts b/packages/cli/src/generate/file-system.ts index c4a1217759..3b84d84df4 100644 --- a/packages/cli/src/generate/file-system.ts +++ b/packages/cli/src/generate/file-system.ts @@ -1,3 +1,4 @@ +// std import { deepStrictEqual } from 'assert'; import { copyFileSync, @@ -12,6 +13,9 @@ import { } from 'fs'; import { dirname, join } from 'path'; +// 3p +import { green, red } from 'colors/safe'; + function rmDirAndFiles(path: string) { const files = readdirSync(path); for (const file of files) { @@ -299,7 +303,8 @@ export class FileSystem { let message = `The two files "${actual}" and "${expected}" are not equal.`; for (let i = 0; i < Math.max(expectedLines.length, actualLines.length); i++) { if (expectedLines[i] !== actualLines[i]) { - message += `\n\nLine ${i + 1}\n Expected: ${expectedLines[i]}\n Actual: ${actualLines[i]}`; + message += `\n\nLine ${i + 1}\n` + + `${green(` Expected: ${expectedLines[i]}\n`)}${red(` Actual: ${actualLines[i]}`)}`; } } throw new Error(message); From 2ae5f8cb99a93a167fb9abb83af551d6df2b48d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Sat, 16 May 2020 10:27:29 +0200 Subject: [PATCH 25/92] [CLI] Add fs.rmfile --- packages/cli/src/generate/file-system.spec.ts | 20 +++++++++++++++++++ packages/cli/src/generate/file-system.ts | 13 +++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/generate/file-system.spec.ts b/packages/cli/src/generate/file-system.spec.ts index 92f8bff6a0..251bdfcf72 100644 --- a/packages/cli/src/generate/file-system.spec.ts +++ b/packages/cli/src/generate/file-system.spec.ts @@ -623,4 +623,24 @@ describe('FileSystem', () => { }); + describe('has a "rmfile" method that', () => { + + beforeEach(() => { + mkdir('test-generators'); + writeFileSync('test-generators/hello.txt', 'hello', 'utf8'); + }); + + afterEach(() => { + rmfile('test-generators/hello.txt'); + rmdir('test-generators'); + }); + + it('should remove the file.', () => { + fs.rmfile('hello.txt'); + if (existsSync('test-generators/hello.txt')) { + throw new Error('The file "hello.txt" should have been removed.'); + } + }); + }); + }); diff --git a/packages/cli/src/generate/file-system.ts b/packages/cli/src/generate/file-system.ts index 3b84d84df4..fa7786446a 100644 --- a/packages/cli/src/generate/file-system.ts +++ b/packages/cli/src/generate/file-system.ts @@ -284,7 +284,7 @@ export class FileSystem { * @param {{ binary: boolean }} [{ binary }={ binary: true }] - Specify if the file is binary. * @memberof FileSystem */ - assertEqual(actual: string, expected: string, { binary }: { binary: boolean } = { binary: false }): void { + assertEqual(actual: string, expected: string, { binary }: { binary: boolean } = { binary: false }): this { const specPath = join(__dirname, 'specs', expected); if (!existsSync(specPath)) { throw new Error(`The spec file "${expected}" does not exist.`); @@ -310,6 +310,7 @@ export class FileSystem { throw new Error(message); } } + return this; } /** @@ -330,6 +331,16 @@ export class FileSystem { ); } + /** + * Removes a file. + * + * @param {string} path - The path relative to the client directory. + * @memberof FileSystem + */ + rmfile(path: string): void { + unlinkSync(this.parse(path)); + } + private parse(path: string) { if (process.env.P1Z7kEbSUUPMxF8GqPwD8Gx_FOAL_CLI_TEST === 'true') { return join(this.testDir, this.currentDir, path); From cd086d102bc5bac5eea10962d4feef3d6ee1cd4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Sat, 16 May 2020 10:51:14 +0200 Subject: [PATCH 26/92] [CLI] Refactor create-controller with FileSystem --- packages/cli/src/generate/file-system.ts | 3 +- .../controller/create-controller.spec.ts | 110 +++++++++--------- .../controller/create-controller.ts | 55 +++++---- 3 files changed, 84 insertions(+), 84 deletions(-) diff --git a/packages/cli/src/generate/file-system.ts b/packages/cli/src/generate/file-system.ts index fa7786446a..9853b88b74 100644 --- a/packages/cli/src/generate/file-system.ts +++ b/packages/cli/src/generate/file-system.ts @@ -320,7 +320,7 @@ export class FileSystem { * @param {string} dest - The destination path relative to the client directory. * @memberof FileSystem */ - copyMock(src: string, dest: string): void { + copyMock(src: string, dest: string): this { const mockPath = join(__dirname, 'mocks', src); if (!existsSync(mockPath)) { throw new Error(`The mock file "${src}" does not exist.`); @@ -329,6 +329,7 @@ export class FileSystem { mockPath, this.parse(dest) ); + return this; } /** diff --git a/packages/cli/src/generate/generators/controller/create-controller.spec.ts b/packages/cli/src/generate/generators/controller/create-controller.spec.ts index 0d40aa487d..71d7c92e61 100644 --- a/packages/cli/src/generate/generators/controller/create-controller.spec.ts +++ b/packages/cli/src/generate/generators/controller/create-controller.spec.ts @@ -1,53 +1,46 @@ // FoalTS -import { - rmDirAndFilesIfExist, - rmfileIfExists, - TestEnvironment -} from '../../utils'; +import { FileSystem } from '../../file-system'; import { createController } from './create-controller'; describe('createController', () => { - afterEach(() => { - rmDirAndFilesIfExist('src/app'); - // We cannot remove src/ since the generator code lives within. This is bad testing - // approach. - rmDirAndFilesIfExist('controllers'); - rmfileIfExists('test-foo-bar.controller.ts'); - rmfileIfExists('test-foo-bar.controller.spec.ts'); - rmfileIfExists('index.ts'); - }); + const fs = new FileSystem(); + + beforeEach(() => fs.setUp()); + + afterEach(() => fs.tearDown()); function test(root: string) { describe(`when the directory ${root}/ exists`, () => { - const testEnv = new TestEnvironment('controller', root); - beforeEach(() => { - testEnv.mkRootDirIfDoesNotExist(); - testEnv.copyFileFromMocks('index.ts'); + fs + .ensureDir(root) + .cd(root) + .copyMock('controller/index.ts', 'index.ts'); }); it('should render the empty templates in the proper directory.', () => { createController({ name: 'test-fooBar', type: 'Empty', register: false }); - testEnv - .validateSpec('test-foo-bar.controller.empty.ts', 'test-foo-bar.controller.ts') - .validateSpec('test-foo-bar.controller.spec.empty.ts', 'test-foo-bar.controller.spec.ts') - .validateSpec('index.ts', 'index.ts'); + fs + .assertEqual('test-foo-bar.controller.ts', 'controller/test-foo-bar.controller.empty.ts') + .assertEqual('test-foo-bar.controller.spec.ts', 'controller/test-foo-bar.controller.spec.empty.ts') + .assertEqual('index.ts', 'controller/index.ts'); }); it('should render the REST templates in the proper directory.', () => { createController({ name: 'test-fooBar', type: 'REST', register: false }); - testEnv - .validateSpec('test-foo-bar.controller.rest.ts', 'test-foo-bar.controller.ts') - .validateSpec('index.ts', 'index.ts'); + fs + .assertEqual('test-foo-bar.controller.ts', 'controller/test-foo-bar.controller.rest.ts') + .assertEqual('index.ts', 'controller/index.ts'); }); it('should not throw an error if index.ts does not exist.', () => { - testEnv.rmfileIfExists('index.ts'); + // TODO: replace with "should create index.ts if it does not exist." + fs.rmfile('index.ts'); createController({ name: 'test-fooBar', type: 'Empty', register: false }); }); @@ -61,86 +54,95 @@ describe('createController', () => { describe('when the directory src/app/controllers exists and if register is true', () => { - const testEnv = new TestEnvironment('controller', 'src/app/controllers'); - beforeEach(() => { - testEnv.mkRootDirIfDoesNotExist(); - testEnv.copyFileFromMocks('index.ts'); + fs + .ensureDir('src/app/controllers') + .cd('src/app/controllers') + .copyMock('controller/index.ts', 'index.ts') + .cd('..'); }); // TODO: refactor these tests and their mock and spec files. it('should add all the imports if none exists.', () => { - testEnv.copyFileFromMocks('app.controller.no-import.ts', '../app.controller.ts'); + fs + .copyMock('controller/app.controller.no-import.ts', 'app.controller.ts'); createController({ name: 'test-fooBar', type: 'Empty', register: true }); - testEnv - .validateSpec('app.controller.no-import.ts', '../app.controller.ts'); + fs + .assertEqual('app.controller.ts', 'controller/app.controller.no-import.ts'); }); it('should update the "subControllers" import in src/app/app.controller.ts if it exists.', () => { - testEnv.copyFileFromMocks('app.controller.controller-import.ts', '../app.controller.ts'); + fs + .copyMock('controller/app.controller.controller-import.ts', 'app.controller.ts'); createController({ name: 'test-fooBar', type: 'Empty', register: true }); - testEnv - .validateSpec('app.controller.controller-import.ts', '../app.controller.ts'); + fs + .assertEqual('app.controller.ts', 'controller/app.controller.controller-import.ts'); }); it('should add a "subControllers" import in src/app/app.controller.ts if none already exists.', () => { - testEnv.copyFileFromMocks('app.controller.no-controller-import.ts', '../app.controller.ts'); + fs + .copyMock('controller/app.controller.no-controller-import.ts', 'app.controller.ts'); createController({ name: 'test-fooBar', type: 'Empty', register: true }); - testEnv - .validateSpec('app.controller.no-controller-import.ts', '../app.controller.ts'); + fs + .assertEqual('app.controller.ts', 'controller/app.controller.no-controller-import.ts'); }); it('should update the "@foal/core" import in src/app/app.controller.ts if it exists.', () => { - testEnv.copyFileFromMocks('app.controller.core-import.ts', '../app.controller.ts'); + fs + .copyMock('controller/app.controller.core-import.ts', 'app.controller.ts'); createController({ name: 'test-fooBar', type: 'Empty', register: true }); - testEnv - .validateSpec('app.controller.core-import.ts', '../app.controller.ts'); + fs + .assertEqual('app.controller.ts', 'controller/app.controller.core-import.ts'); }); it('should update the "subControllers = []" property in src/app/app.controller.ts if it exists.', () => { - testEnv.copyFileFromMocks('app.controller.empty-property.ts', '../app.controller.ts'); + fs + .copyMock('controller/app.controller.empty-property.ts', 'app.controller.ts'); createController({ name: 'test-fooBar', type: 'Empty', register: true }); - testEnv - .validateSpec('app.controller.empty-property.ts', '../app.controller.ts'); + fs + .assertEqual('app.controller.ts', 'controller/app.controller.empty-property.ts'); }); it('should update the "subControllers = [ \\n \\n ]" property in src/app/app.controller.ts if it exists.', () => { - testEnv.copyFileFromMocks('app.controller.empty-spaced-property.ts', '../app.controller.ts'); + fs + .copyMock('controller/app.controller.empty-spaced-property.ts', 'app.controller.ts'); createController({ name: 'test-fooBar', type: 'Empty', register: true }); - testEnv - .validateSpec('app.controller.empty-spaced-property.ts', '../app.controller.ts'); + fs + .assertEqual('app.controller.ts', 'controller/app.controller.empty-spaced-property.ts'); }); it('should update the "subControllers = [ \\n (.*) \\n ]" property in' + ' src/app/app.controller.ts if it exists.', () => { - testEnv.copyFileFromMocks('app.controller.no-empty-property.ts', '../app.controller.ts'); + fs + .copyMock('controller/app.controller.no-empty-property.ts', 'app.controller.ts'); createController({ name: 'test-fooBar', type: 'Empty', register: true }); - testEnv - .validateSpec('app.controller.no-empty-property.ts', '../app.controller.ts'); + fs + .assertEqual('app.controller.ts', 'controller/app.controller.no-empty-property.ts'); }); it('should update the "subControllers" property with a special URL if the controller is a REST controller.', () => { - testEnv.copyFileFromMocks('app.controller.rest.ts', '../app.controller.ts'); + fs + .copyMock('controller/app.controller.rest.ts', 'app.controller.ts'); createController({ name: 'test-fooBar', type: 'REST', register: true }); - testEnv - .validateSpec('app.controller.rest.ts', '../app.controller.ts'); + fs + .assertEqual('app.controller.ts', 'controller/app.controller.rest.ts'); }); }); diff --git a/packages/cli/src/generate/generators/controller/create-controller.ts b/packages/cli/src/generate/generators/controller/create-controller.ts index 8fe76a1dc8..73aa2d8030 100644 --- a/packages/cli/src/generate/generators/controller/create-controller.ts +++ b/packages/cli/src/generate/generators/controller/create-controller.ts @@ -1,43 +1,40 @@ -// std -import { existsSync } from 'fs'; - // FoalTS -import { Generator, getNames } from '../../utils'; +import { FileSystem } from '../../file-system'; +import { getNames } from '../../utils'; import { registerController } from './register-controller'; export type ControllerType = 'Empty'|'REST'; export function createController({ name, type, register }: { name: string, type: ControllerType, register: boolean }) { - const names = getNames(name); - - const fileName = `${names.kebabName}.controller.ts`; - const specFileName = `${names.kebabName}.controller.spec.ts`; + const fs = new FileSystem(); let root = ''; - - if (existsSync('src/app/controllers')) { + if (fs.exists('src/app/controllers')) { root = 'src/app/controllers'; - } else if (existsSync('controllers')) { + } else if (fs.exists('controllers')) { root = 'controllers'; } - const generator = new Generator('controller', root) - .renderTemplate(`controller.${type.toLowerCase()}.ts`, names, fileName) - .updateFile('index.ts', content => { - content += `export { ${names.upperFirstCamelName}Controller } from './${names.kebabName}.controller';\n`; - return content; - }, { allowFailure: true }); - - if (register) { - const path = `/${names.kebabName}${type === 'REST' ? 's' : ''}`; - generator - .updateFile('../app.controller.ts', content => { - return registerController(content, `${names.upperFirstCamelName}Controller`, path); - }, { allowFailure: true }); - } + const names = getNames(name); - if (type === 'Empty') { - generator - .renderTemplate('controller.spec.empty.ts', names, specFileName); - } + const templatePath = `controller/controller.${type.toLowerCase()}.ts`; + const specTemplatePath = `controller/controller.spec.empty.ts`; + + const fileName = `${names.kebabName}.controller.ts`; + const specFileName = `${names.kebabName}.controller.spec.ts`; + + const className = `${names.upperFirstCamelName}Controller`; + + fs + .cd(root) + .render(templatePath, fileName, names) + // TODO: the condition "Empty" is not tested. + .renderOnlyIf(type === 'Empty', specTemplatePath, specFileName, names) + .ensureFile('index.ts') + .addNamedExportIn('index.ts', className, `./${names.kebabName}.controller`) + .cd('..') + .modifyOnlyfIf(register, 'app.controller.ts', content => { + const path = `/${names.kebabName}${type === 'REST' ? 's' : ''}`; + return registerController(content, className, path); + }); } From a9efe57c809df85a8e97ff2026b9ab9c7e8b4b8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Sat, 16 May 2020 11:08:16 +0200 Subject: [PATCH 27/92] CLI] Refactor create-entity with FileSystem --- .../generators/entity/create-entity.spec.ts | 37 ++++++++----------- .../generators/entity/create-entity.ts | 26 ++++++------- 2 files changed, 28 insertions(+), 35 deletions(-) diff --git a/packages/cli/src/generate/generators/entity/create-entity.spec.ts b/packages/cli/src/generate/generators/entity/create-entity.spec.ts index 996558755a..cd8d8507b9 100644 --- a/packages/cli/src/generate/generators/entity/create-entity.spec.ts +++ b/packages/cli/src/generate/generators/entity/create-entity.spec.ts @@ -1,43 +1,38 @@ // FoalTS -import { - rmDirAndFilesIfExist, - rmfileIfExists, - TestEnvironment, -} from '../../utils'; +import { FileSystem } from '../../file-system'; import { createEntity } from './create-entity'; describe('createEntity', () => { - afterEach(() => { - rmDirAndFilesIfExist('src/app'); - // We cannot remove src/ since the generator code lives within. This is bad testing - // approach. - rmDirAndFilesIfExist('entities'); - rmfileIfExists('test-foo-bar.entity.ts'); - rmfileIfExists('index.ts'); - }); + const fs = new FileSystem(); + + beforeEach(() => fs.setUp()); + + afterEach(() => fs.tearDown()); function test(root: string) { describe(`when the directory ${root}/ exists`, () => { - const testEnv = new TestEnvironment('entity', root); - beforeEach(() => { - testEnv.mkRootDirIfDoesNotExist(); - testEnv.copyFileFromMocks('index.ts'); + fs + .ensureDir(root) + .cd(root) + .copyMock('entity/index.ts', 'index.ts'); }); it('should render the templates in the proper directory.', () => { createEntity({ name: 'test-fooBar' }); - testEnv - .validateSpec('test-foo-bar.entity.ts') - .validateSpec('index.ts', 'index.ts'); + fs + .assertEqual('test-foo-bar.entity.ts', 'entity/test-foo-bar.entity.ts') + .assertEqual('index.ts', 'entity/index.ts'); }); it('should not throw an error if index.ts does not exist.', () => { - testEnv.rmfileIfExists('index.ts'); + // TODO: replace with "should create index.ts if it does not exist." + fs + .rmfile('index.ts'); createEntity({ name: 'test-fooBar' }); }); diff --git a/packages/cli/src/generate/generators/entity/create-entity.ts b/packages/cli/src/generate/generators/entity/create-entity.ts index 19f81b9548..707f6024d5 100644 --- a/packages/cli/src/generate/generators/entity/create-entity.ts +++ b/packages/cli/src/generate/generators/entity/create-entity.ts @@ -1,24 +1,22 @@ -// std -import { existsSync } from 'fs'; - // FoalTS -import { Generator, getNames } from '../../utils'; +import { FileSystem } from '../../file-system'; +import { getNames } from '../../utils'; export function createEntity({ name }: { name: string }) { - const names = getNames(name); + const fs = new FileSystem(); let root = ''; - - if (existsSync('src/app/entities')) { + if (fs.exists('src/app/entities')) { root = 'src/app/entities'; - } else if (existsSync('entities')) { + } else if (fs.exists('entities')) { root = 'entities'; } - new Generator('entity', root) - .renderTemplate('entity.ts', names, `${names.kebabName}.entity.ts`) - .updateFile('index.ts', content => { - content += `export { ${names.upperFirstCamelName} } from './${names.kebabName}.entity';\n`; - return content; - }, { allowFailure: true }); + const names = getNames(name); + + fs + .cd(root) + .render('entity/entity.ts', `${names.kebabName}.entity.ts`, names) + .ensureFile('index.ts') + .addNamedExportIn('index.ts', names.upperFirstCamelName, `./${names.kebabName}.entity`); } From ef9a21b35026a8b86ab94c889ffcbd746ebc271e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Sat, 16 May 2020 11:16:17 +0200 Subject: [PATCH 28/92] [CLI] Refactor create-hook with FileSystem --- .../generators/hook/create-hook.spec.ts | 37 ++++++++----------- .../generate/generators/hook/create-hook.ts | 26 ++++++------- 2 files changed, 28 insertions(+), 35 deletions(-) diff --git a/packages/cli/src/generate/generators/hook/create-hook.spec.ts b/packages/cli/src/generate/generators/hook/create-hook.spec.ts index c436d01daa..de1ce0f928 100644 --- a/packages/cli/src/generate/generators/hook/create-hook.spec.ts +++ b/packages/cli/src/generate/generators/hook/create-hook.spec.ts @@ -1,43 +1,38 @@ // FoalTS -import { - rmDirAndFilesIfExist, - rmfileIfExists, - TestEnvironment, -} from '../../utils'; +import { FileSystem } from '../../file-system'; import { createHook } from './create-hook'; describe('createHook', () => { - afterEach(() => { - rmDirAndFilesIfExist('src/app'); - // We cannot remove src/ since the generator code lives within. This is bad testing - // approach. - rmDirAndFilesIfExist('hooks'); - rmfileIfExists('test-foo-bar.hook.ts'); - rmfileIfExists('index.ts'); - }); + const fs = new FileSystem(); + + beforeEach(() => fs.setUp()); + + afterEach(() => fs.tearDown()); function test(root: string) { describe(`when the directory ${root}/ exists`, () => { - const testEnv = new TestEnvironment('hook', root); - beforeEach(() => { - testEnv.mkRootDirIfDoesNotExist(); - testEnv.copyFileFromMocks('index.ts'); + fs + .ensureDir(root) + .cd(root) + .copyMock('hook/index.ts', 'index.ts'); }); it('should render the templates in the proper directory.', () => { createHook({ name: 'test-fooBar' }); - testEnv - .validateSpec('test-foo-bar.hook.ts') - .validateSpec('index.ts', 'index.ts'); + fs + .assertEqual('test-foo-bar.hook.ts', 'hook/test-foo-bar.hook.ts') + .assertEqual('index.ts', 'hook/index.ts'); }); it('should not throw an error if index.ts does not exist.', () => { - testEnv.rmfileIfExists('index.ts'); + // TODO: replace with "should create index.ts if it does not exist." + fs + .rmfile('index.Ts'); createHook({ name: 'test-fooBar' }); }); diff --git a/packages/cli/src/generate/generators/hook/create-hook.ts b/packages/cli/src/generate/generators/hook/create-hook.ts index d1f3a0855c..9d87c7ec52 100644 --- a/packages/cli/src/generate/generators/hook/create-hook.ts +++ b/packages/cli/src/generate/generators/hook/create-hook.ts @@ -1,24 +1,22 @@ -// std -import { existsSync } from 'fs'; - // FoalTS -import { Generator, getNames } from '../../utils'; +import { FileSystem } from '../../file-system'; +import { getNames } from '../../utils'; export function createHook({ name }: { name: string }) { - const names = getNames(name); + const fs = new FileSystem(); let root = ''; - - if (existsSync('src/app/hooks')) { + if (fs.exists('src/app/hooks')) { root = 'src/app/hooks'; - } else if (existsSync('hooks')) { + } else if (fs.exists('hooks')) { root = 'hooks'; } - new Generator('hook', root) - .renderTemplate('hook.ts', names, `${names.kebabName}.hook.ts`) - .updateFile('index.ts', content => { - content += `export { ${names.upperFirstCamelName} } from './${names.kebabName}.hook';\n`; - return content; - }, { allowFailure: true }); + const names = getNames(name); + + fs + .cd(root) + .render('hook/hook.ts', `${names.kebabName}.hook.ts`, names) + .ensureFile('index.ts') + .addNamedExportIn('index.ts', names.upperFirstCamelName, `./${names.kebabName}.hook`); } From 744913a11c5c6131b070777a106357c9f943d8dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Sat, 16 May 2020 11:26:17 +0200 Subject: [PATCH 29/92] [CLI] Refactor create-service with FileSystem --- .../generators/service/create-service.spec.ts | 39 ++++++++----------- .../generators/service/create-service.ts | 26 ++++++------- 2 files changed, 28 insertions(+), 37 deletions(-) diff --git a/packages/cli/src/generate/generators/service/create-service.spec.ts b/packages/cli/src/generate/generators/service/create-service.spec.ts index 7a25293495..0b519abc36 100644 --- a/packages/cli/src/generate/generators/service/create-service.spec.ts +++ b/packages/cli/src/generate/generators/service/create-service.spec.ts @@ -1,45 +1,38 @@ // FoalTS -import { - rmDirAndFilesIfExist, - rmfileIfExists, - TestEnvironment -} from '../../utils'; +import { FileSystem } from '../../file-system'; import { createService } from './create-service'; describe('createService', () => { - afterEach(() => { - rmDirAndFilesIfExist('src/app'); - // We cannot remove src/ since the generator code lives within. This is bad testing - // approach. - rmDirAndFilesIfExist('services'); - rmfileIfExists('test-foo-bar.service.ts'); - rmfileIfExists('test-foo-bar-collection.service.ts'); - rmfileIfExists('test-foo-bar-resolver.service.ts'); - rmfileIfExists('index.ts'); - }); + const fs = new FileSystem(); + + beforeEach(() => fs.setUp()); + + afterEach(() => fs.tearDown()); function test(root: string) { describe(`when the directory ${root}/ exists`, () => { - const testEnv = new TestEnvironment('service', root); - beforeEach(() => { - testEnv.mkRootDirIfDoesNotExist(); - testEnv.copyFileFromMocks('index.ts'); + fs + .ensureDir(root) + .cd(root) + .copyMock('service/index.ts', 'index.ts'); }); it('should render the empty templates in the proper directory.', () => { createService({ name: 'test-fooBar' }); - testEnv - .validateSpec('test-foo-bar.service.empty.ts', 'test-foo-bar.service.ts') - .validateSpec('index.ts', 'index.ts'); + fs + .assertEqual('test-foo-bar.service.ts', 'service/test-foo-bar.service.empty.ts') + .assertEqual('index.ts', 'service/index.ts'); }); it('should not throw an error if index.ts does not exist.', () => { - testEnv.rmfileIfExists('index.ts'); + // TODO: replace with "should create index.ts if it does not exist." + fs + .rmfile('index.ts'); createService({ name: 'test-fooBar' }); }); diff --git a/packages/cli/src/generate/generators/service/create-service.ts b/packages/cli/src/generate/generators/service/create-service.ts index 44a772c745..74b19927b9 100644 --- a/packages/cli/src/generate/generators/service/create-service.ts +++ b/packages/cli/src/generate/generators/service/create-service.ts @@ -1,25 +1,23 @@ -// std -import { existsSync } from 'fs'; - // FoalTS -import { Generator, getNames } from '../../utils'; +import { FileSystem } from '../../file-system'; +import { getNames } from '../../utils'; export function createService({ name }: { name: string }) { - const names = getNames(name); + const fs = new FileSystem(); let root = ''; - - if (existsSync('src/app/services')) { + if (fs.exists('src/app/services')) { root = 'src/app/services'; - } else if (existsSync('services')) { + } else if (fs.exists('services')) { root = 'services'; } - new Generator('service', root) - .renderTemplate('service.empty.ts', names, `${names.kebabName}.service.ts`) - .updateFile('index.ts', content => { - content += `export { ${names.upperFirstCamelName} } from './${names.kebabName}.service';\n`; - return content; - }, { allowFailure: true }); + const names = getNames(name); + + fs + .cd(root) + .render('service/service.empty.ts', `${names.kebabName}.service.ts`, names) + .ensureFile('index.ts') + .addNamedExportIn('index.ts', names.upperFirstCamelName, `./${names.kebabName}.service`); } From a04c7fc0a65dc7d820eae44507d4b881aa4a851f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Sat, 16 May 2020 11:31:09 +0200 Subject: [PATCH 30/92] [CLI] Refactor create-vscode-confi with FileSystem --- .../vscode-config/create-vscode-config.spec.ts | 17 ++++++++--------- .../vscode-config/create-vscode-config.ts | 12 ++++++------ 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/packages/cli/src/generate/generators/vscode-config/create-vscode-config.spec.ts b/packages/cli/src/generate/generators/vscode-config/create-vscode-config.spec.ts index cdfc6ef911..0aa70cde08 100644 --- a/packages/cli/src/generate/generators/vscode-config/create-vscode-config.spec.ts +++ b/packages/cli/src/generate/generators/vscode-config/create-vscode-config.spec.ts @@ -1,22 +1,21 @@ // FoalTS -import { - rmDirAndFilesIfExist, - TestEnvironment, -} from '../../utils'; +import { FileSystem } from '../../file-system'; import { createVSCodeConfig } from './create-vscode-config'; describe('createVSCodeConfig', () => { + const fs = new FileSystem(); - afterEach(() => rmDirAndFilesIfExist('.vscode')); + beforeEach(() => fs.setUp()); - const testEnv = new TestEnvironment('vscode-config', '.vscode'); + afterEach(() => fs.tearDown()); it('should create the directory .vscode/ with default launch.json and tasks.json files.', () => { createVSCodeConfig(); - testEnv - .validateSpec('launch.json') - .validateSpec('tasks.json'); + fs + .cd('.vscode') + .assertEqual('launch.json', 'vscode-config/launch.json') + .assertEqual('tasks.json', 'vscode-config/tasks.json'); }); }); diff --git a/packages/cli/src/generate/generators/vscode-config/create-vscode-config.ts b/packages/cli/src/generate/generators/vscode-config/create-vscode-config.ts index 72e7c599c9..8343a59ae3 100644 --- a/packages/cli/src/generate/generators/vscode-config/create-vscode-config.ts +++ b/packages/cli/src/generate/generators/vscode-config/create-vscode-config.ts @@ -1,10 +1,10 @@ // FoalTS -import { Generator, mkdirIfDoesNotExist } from '../../utils'; +import { FileSystem } from '../../file-system'; export function createVSCodeConfig() { - mkdirIfDoesNotExist('.vscode'); - - new Generator('vscode-config', '.vscode') - .copyFileFromTemplates('launch.json') - .copyFileFromTemplates('tasks.json'); + new FileSystem() + .ensureDir('.vscode') + .cd('.vscode') + .copy('vscode-config/launch.json', 'launch.json') + .copy('vscode-config/tasks.json', 'tasks.json'); } From bd88b4f991d9319751b2687d128b6b84fd89087b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Sat, 16 May 2020 11:42:14 +0200 Subject: [PATCH 31/92] [CLI] Refactor create-vuewith FileSystem --- .../generators/vue/connect-vue.spec.ts | 24 ++++++++++--------- .../generate/generators/vue/connect-vue.ts | 14 ++++++----- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/packages/cli/src/generate/generators/vue/connect-vue.spec.ts b/packages/cli/src/generate/generators/vue/connect-vue.spec.ts index ea2a3e8167..670c0aa268 100644 --- a/packages/cli/src/generate/generators/vue/connect-vue.spec.ts +++ b/packages/cli/src/generate/generators/vue/connect-vue.spec.ts @@ -1,22 +1,23 @@ -import { mkdirIfDoesNotExist, rmDirAndFilesIfExist, TestEnvironment } from '../../utils'; +import { FileSystem } from '../../file-system'; import { connectVue } from './connect-vue'; describe('connectVue', () => { - afterEach(() => rmDirAndFilesIfExist('connector-test')); + const fs = new FileSystem(); - const testEnv = new TestEnvironment('vue'); + beforeEach(() => fs.setUp()); - it('should update package.json to set up the proxy, install ncp and change the output dir.', () => { - mkdirIfDoesNotExist('connector-test/vue'); + afterEach(() => fs.tearDown()); - testEnv - .copyFileFromMocks('package.json', 'connector-test/vue/package.json'); + it('should update package.json to set up the proxy, install ncp and change the output dir.', () => { + fs + .ensureDir('connector-test/vue') + .copyMock('vue/package.json', 'connector-test/vue/package.json'); - connectVue('./connector-test/vue'); + connectVue('./connector-test/vue'); - testEnv - .validateSpec('package.json', 'connector-test/vue/package.json'); + fs + .assertEqual('connector-test/vue/package.json', 'vue/package.json'); }); it('should not throw if the path does not exist.', () => { @@ -24,7 +25,8 @@ describe('connectVue', () => { }); it('should not throw if package.json does not exist.', () => { - mkdirIfDoesNotExist('connector-test/vue'); + fs + .ensureDir('connector-test/vue'); connectVue('./connector-test/vue'); }); diff --git a/packages/cli/src/generate/generators/vue/connect-vue.ts b/packages/cli/src/generate/generators/vue/connect-vue.ts index 75f9207869..5b6721b2c1 100644 --- a/packages/cli/src/generate/generators/vue/connect-vue.ts +++ b/packages/cli/src/generate/generators/vue/connect-vue.ts @@ -1,26 +1,28 @@ import { join, relative } from 'path'; import { red } from 'colors/safe'; -import { existsSync } from 'fs'; -import { Generator } from '../../utils'; +import { FileSystem } from '../../file-system'; export function connectVue(path: string) { - if (!existsSync(path)) { + const fs = new FileSystem(); + + if (!fs.exists(path)) { if (process.env.NODE_ENV !== 'test') { console.log(red(` The directory ${path} does not exist.`)); } return; } - if (!existsSync(join(path, 'package.json'))) { + if (!fs.exists(join(path, 'package.json'))) { if (process.env.NODE_ENV !== 'test') { console.log(red(` The directory ${path} is not a Vue project (missing package.json).`)); } return; } - new Generator('vue', path) - .updateFile('package.json', content => { + fs + .cd(path) + .modify('package.json', content => { const pkg = JSON.parse(content); pkg.vue = pkg.vue || {}; From dcc507ec2898628c0ab7fac2eeaefe959a3ad326 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Sat, 16 May 2020 12:00:00 +0200 Subject: [PATCH 32/92] [CLI] Refactor connect-angular with FileSystem --- .../angular/connect-angular.spec.ts | 54 ++++++++++--------- .../generators/angular/connect-angular.ts | 20 +++---- 2 files changed, 40 insertions(+), 34 deletions(-) diff --git a/packages/cli/src/generate/generators/angular/connect-angular.spec.ts b/packages/cli/src/generate/generators/angular/connect-angular.spec.ts index f9f07e4b48..9ef73caaf9 100644 --- a/packages/cli/src/generate/generators/angular/connect-angular.spec.ts +++ b/packages/cli/src/generate/generators/angular/connect-angular.spec.ts @@ -1,24 +1,28 @@ -import { mkdirIfDoesNotExist, rmDirAndFilesIfExist, TestEnvironment } from '../../utils'; +import { FileSystem } from '../../file-system'; +import { TestEnvironment } from '../../utils'; import { connectAngular } from './connect-angular'; // TODO: To improve: make the tests (more) independent from each other. describe('connectAngular', () => { - afterEach(() => rmDirAndFilesIfExist('connector-test')); + const fs = new FileSystem(); + + beforeEach(() => fs.setUp()); + + afterEach(() => fs.tearDown()); const testEnv = new TestEnvironment('angular'); it('should create a proxy.conf.json file in ${path}/src.', () => { - mkdirIfDoesNotExist('connector-test/angular/src'); - - testEnv - .copyFileFromMocks('angular.json', 'connector-test/angular/angular.json') - .copyFileFromMocks('package.json', 'connector-test/angular/package.json'); + fs + .ensureDir('connector-test/angular/src') + .copyMock('angular/angular.json', 'connector-test/angular/angular.json') + .copyMock('angular/package.json', 'connector-test/angular/package.json'); connectAngular('./connector-test/angular'); - testEnv - .validateSpec('proxy.conf.json', 'connector-test/angular/src/proxy.conf.json'); + fs + .assertEqual('connector-test/angular/src/proxy.conf.json', 'angular/proxy.conf.json'); }); it('should not throw if the path does not exist.', () => { @@ -26,39 +30,39 @@ describe('connectAngular', () => { }); it('should update angular.json with the proxy file and the output dir.', () => { - mkdirIfDoesNotExist('connector-test/angular/src'); - - testEnv - .copyFileFromMocks('angular.json', 'connector-test/angular/angular.json') - .copyFileFromMocks('package.json', 'connector-test/angular/package.json'); + fs + .ensureDir('connector-test/angular/src') + .copyMock('angular/angular.json', 'connector-test/angular/angular.json') + .copyMock('angular/package.json', 'connector-test/angular/package.json'); connectAngular('./connector-test/angular'); - testEnv - .validateSpec('angular.json', 'connector-test/angular/angular.json'); + fs + .assertEqual('connector-test/angular/angular.json', 'angular/angular.json'); }); it('should not throw if angular.json does not exist.', () => { - mkdirIfDoesNotExist('connector-test/angular/src'); + fs + .ensureDir('connector-test/angular/src'); connectAngular('./connector-test/angular'); }); it('should update package.json with the "--prod" flag.', () => { - mkdirIfDoesNotExist('connector-test/angular/src'); - - testEnv - .copyFileFromMocks('angular.json', 'connector-test/angular/angular.json') - .copyFileFromMocks('package.json', 'connector-test/angular/package.json'); + fs + .ensureDir('connector-test/angular/src') + .copyMock('angular/angular.json', 'connector-test/angular/angular.json') + .copyMock('angular/package.json', 'connector-test/angular/package.json'); connectAngular('./connector-test/angular'); - testEnv - .validateSpec('package.json', 'connector-test/angular/package.json'); + fs + .assertEqual('connector-test/angular/package.json', 'angular/package.json'); }); it('should not throw if package.json does not exist.', () => { - mkdirIfDoesNotExist('connector-test/angular/src'); + fs + .ensureDir('connector-test/angular/src'); connectAngular('./connector-test/angular'); }); diff --git a/packages/cli/src/generate/generators/angular/connect-angular.ts b/packages/cli/src/generate/generators/angular/connect-angular.ts index 2923ce21fc..7e5af437db 100644 --- a/packages/cli/src/generate/generators/angular/connect-angular.ts +++ b/packages/cli/src/generate/generators/angular/connect-angular.ts @@ -1,41 +1,43 @@ import { join, relative } from 'path'; import { red } from 'colors/safe'; -import { existsSync } from 'fs'; -import { Generator } from '../../utils'; +import { FileSystem } from '../../file-system'; export function connectAngular(path: string) { - if (!existsSync(path)) { + const fs = new FileSystem(); + + if (!fs.exists(path)) { if (process.env.NODE_ENV !== 'test') { console.log(red(` The directory ${path} does not exist.`)); } return; } - if (!existsSync(join(path, 'angular.json'))) { + if (!fs.exists(join(path, 'angular.json'))) { if (process.env.NODE_ENV !== 'test') { console.log(red(` The directory ${path} is not an Angular project (missing angular.json).`)); } return; } - if (!existsSync(join(path, 'package.json'))) { + if (!fs.exists(join(path, 'package.json'))) { if (process.env.NODE_ENV !== 'test') { console.log(red(` The directory ${path} is not an Angular project (missing package.json).`)); } return; } - new Generator('angular', path) - .copyFileFromTemplates('proxy.conf.json', 'src/proxy.conf.json') - .updateFile('package.json', content => { + fs + .cd(path) + .copy('angular/proxy.conf.json', 'src/proxy.conf.json') + .modify('package.json', content => { const pkg = JSON.parse(content); pkg.scripts.build = 'ng build --prod'; return JSON.stringify(pkg, null, 2); }) - .updateFile('angular.json', content => { + .modify('angular.json', content => { const config = JSON.parse(content); // Proxy configuration From a313bafe6421b26c0cf51ec86365e490e81cc1c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Sat, 16 May 2020 12:06:41 +0200 Subject: [PATCH 33/92] [CLI] Refactor connect-react with FileSystem --- .../generators/react/connect-react.spec.ts | 22 ++++++++++--------- .../generators/react/connect-react.ts | 14 +++++++----- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/packages/cli/src/generate/generators/react/connect-react.spec.ts b/packages/cli/src/generate/generators/react/connect-react.spec.ts index 97cdf01c96..cf69d032b5 100644 --- a/packages/cli/src/generate/generators/react/connect-react.spec.ts +++ b/packages/cli/src/generate/generators/react/connect-react.spec.ts @@ -1,22 +1,23 @@ -import { mkdirIfDoesNotExist, rmDirAndFilesIfExist, TestEnvironment } from '../../utils'; +import { FileSystem } from '../../file-system'; import { connectReact } from './connect-react'; describe('connectReact', () => { - afterEach(() => rmDirAndFilesIfExist('connector-test')); + const fs = new FileSystem(); - const testEnv = new TestEnvironment('react'); + beforeEach(() => fs.setUp()); - it('should update package.json to set up the proxy, install ncp and change the output dir.', () => { - mkdirIfDoesNotExist('connector-test/react'); + afterEach(() => fs.tearDown()); - testEnv - .copyFileFromMocks('package.json', 'connector-test/react/package.json'); + it('should update package.json to set up the proxy, install ncp and change the output dir.', () => { + fs + .ensureDir('connector-test/react') + .copyMock('react/package.json', 'connector-test/react/package.json'); connectReact('./connector-test/react'); - testEnv - .validateSpec('package.json', 'connector-test/react/package.json'); + fs + .assertEqual('connector-test/react/package.json', 'react/package.json'); }); it('should not throw if the path does not exist.', () => { @@ -24,7 +25,8 @@ describe('connectReact', () => { }); it('should not throw if package.json does not exist.', () => { - mkdirIfDoesNotExist('connector-test/react'); + fs + .ensureDir('connector-test/react'); connectReact('./connector-test/react'); }); diff --git a/packages/cli/src/generate/generators/react/connect-react.ts b/packages/cli/src/generate/generators/react/connect-react.ts index c60c83a320..1306209160 100644 --- a/packages/cli/src/generate/generators/react/connect-react.ts +++ b/packages/cli/src/generate/generators/react/connect-react.ts @@ -1,26 +1,28 @@ import { join } from 'path'; import { red } from 'colors/safe'; -import { existsSync } from 'fs'; -import { Generator } from '../../utils'; +import { FileSystem } from '../../file-system'; export function connectReact(path: string) { - if (!existsSync(path)) { + const fs = new FileSystem(); + + if (!fs.exists(path)) { if (process.env.NODE_ENV !== 'test') { console.log(red(` The directory ${path} does not exist.`)); } return; } - if (!existsSync(join(path, 'package.json'))) { + if (!fs.exists(join(path, 'package.json'))) { if (process.env.NODE_ENV !== 'test') { console.log(red(` The directory ${path} is not a React project (missing package.json).`)); } return; } - new Generator('react', path) - .updateFile('package.json', content => { + fs + .cd(path) + .modify('package.json', content => { const pkg = JSON.parse(content); pkg.proxy = 'http://localhost:3001'; return JSON.stringify(pkg, null, 2); From b34721707b5b3d26308e7b2ae14620022e282ae6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Sat, 16 May 2020 15:40:39 +0200 Subject: [PATCH 34/92] [CLI] Refactor create-app with FileSystem --- packages/cli/src/generate/file-system.spec.ts | 35 +- packages/cli/src/generate/file-system.ts | 22 +- .../generators/app/create-app.spec.ts | 302 +++++++++--------- .../src/generate/generators/app/create-app.ts | 158 +++++---- 4 files changed, 304 insertions(+), 213 deletions(-) diff --git a/packages/cli/src/generate/file-system.spec.ts b/packages/cli/src/generate/file-system.spec.ts index 251bdfcf72..1725abe01c 100644 --- a/packages/cli/src/generate/file-system.spec.ts +++ b/packages/cli/src/generate/file-system.spec.ts @@ -438,7 +438,7 @@ describe('FileSystem', () => { }); - describe('has a "assetExists" that', () => { + describe('has an "assetExists" method that', () => { beforeEach(() => { mkdir('test-generators'); @@ -465,7 +465,7 @@ describe('FileSystem', () => { }); - describe('has a "assetNotExists" that', () => { + describe('has an "assetNotExists" method that', () => { beforeEach(() => { mkdir('test-generators'); @@ -492,6 +492,37 @@ describe('FileSystem', () => { }); + describe('has an "assertEmptyDir" that', () => { + + beforeEach(() => { + mkdir('test-generators'); + mkdir('test-generators/foo'); + mkdir('test-generators/bar'); + writeFileSync('test-generators/foo/foobar', Buffer.alloc(2)); + }); + + afterEach(() => { + rmfile('test-generators/foo/foobar'); + rmdir('test-generators/foo'); + rmdir('test-generators/bar'); + rmdir('test-generators'); + }); + + it('should throw an error if the directory is not empty.', () => { + try { + fs.assertEmptyDir('foo'); + throw new Error('An error should have been thrown.'); + } catch (error) { + strictEqual(error.message, 'The directory "foo" should be empty.'); + } + }); + + it('should not throw an error if the directory is empty.', () => { + fs.assertEmptyDir('bar'); + }); + + }); + describe('has a "assertEqual" that', () => { const specDir = join(__dirname, 'specs/test-file-system'); diff --git a/packages/cli/src/generate/file-system.ts b/packages/cli/src/generate/file-system.ts index 9853b88b74..22582759fa 100644 --- a/packages/cli/src/generate/file-system.ts +++ b/packages/cli/src/generate/file-system.ts @@ -31,6 +31,13 @@ function rmDirAndFiles(path: string) { rmdirSync(path); } +/** + * This class provides more methods that Node std "fs". + * It also allows to create an isolated directory during directory. + * + * @export + * @class FileSystem + */ export class FileSystem { currentDir = ''; @@ -270,10 +277,23 @@ export class FileSystem { * @param {string} path - The path relative to the client directory. * @memberof FileSystem */ - assertNotExists(path: string): void { + assertNotExists(path: string): this { if (existsSync(this.parse(path))) { throw new Error(`The file "${path}" should not exist.`); } + return this; + } + + /** + * Throws an error if the directory is not empty. + * + * @param {string} path - The path relative to the client directory. + * @memberof FileSystem + */ + assertEmptyDir(path: string): void { + if (readdirSync(this.parse(path)).length > 0) { + throw new Error(`The directory "${path}" should be empty.`); + } } /** diff --git a/packages/cli/src/generate/generators/app/create-app.spec.ts b/packages/cli/src/generate/generators/app/create-app.spec.ts index 38844154c6..8bd030f8c4 100644 --- a/packages/cli/src/generate/generators/app/create-app.spec.ts +++ b/packages/cli/src/generate/generators/app/create-app.spec.ts @@ -1,261 +1,277 @@ -// std -import { strictEqual } from 'assert'; -import { mkdirSync, readdirSync } from 'fs'; - // FoalTS -import { TestEnvironment } from '../../utils'; +import { FileSystem } from '../../file-system'; import { createApp } from './create-app'; describe('createApp', () => { - const testEnv = new TestEnvironment('app', 'test-foo-bar'); + const fs = new FileSystem(); + + beforeEach(() => fs.setUp()); - afterEach(() => testEnv.rmDirAndFilesIfExist('../test-foo-bar')); + afterEach(() => fs.tearDown()); it('should abort the project creation if a directory already exists.', async () => { - mkdirSync('test-foo-bar'); + fs.ensureDir('test-foo-bar'); await createApp({ name: 'test-fooBar' }); - const files = readdirSync('test-foo-bar'); - - strictEqual(files.length, 0); + fs.assertEmptyDir('test-foo-bar'); }); it('should render the config templates.', async () => { await createApp({ name: 'test-fooBar' }); - testEnv - .validateSpec('config/default.json') - .shouldNotExist('config/default.yml') - .validateSpec('config/development.json') - .shouldNotExist('config/development.yml') - .validateSpec('config/e2e.json') - .shouldNotExist('config/e2e.yml') - .validateSpec('config/production.json') - .shouldNotExist('config/production.yml') - .validateSpec('config/test.json') - .shouldNotExist('config/test.yml'); + fs + .cd('test-foo-bar/config') + .assertEqual('default.json', 'app/config/default.json') + .assertNotExists('default.yml') + .assertEqual('development.json', 'app/config/development.json') + .assertNotExists('development.yml') + .assertEqual('e2e.json', 'app/config/e2e.json') + .assertNotExists('e2e.yml') + .assertEqual('production.json', 'app/config/production.json') + .assertNotExists('production.yml') + .assertEqual('test.json', 'app/config/test.json') + .assertNotExists('test.yml'); }); it('should render the config templates (YAML option).', async () => { await createApp({ name: 'test-fooBar', yaml: true }); - testEnv - .shouldNotExist('config/default.json') - .validateSpec('config/default.yml') - .shouldNotExist('config/development.json') - .validateSpec('config/development.yml') - .shouldNotExist('config/e2e.json') - .validateSpec('config/e2e.yml') - .shouldNotExist('config/production.json') - .validateSpec('config/production.yml') - .shouldNotExist('config/test.json') - .validateSpec('config/test.yml'); + fs + .cd('test-foo-bar/config') + .assertNotExists('default.json') + .assertEqual('default.yml', 'app/config/default.yml') + .assertNotExists('development.json') + .assertEqual('development.yml', 'app/config/development.yml') + .assertNotExists('e2e.json') + .assertEqual('e2e.yml', 'app/config/e2e.yml') + .assertNotExists('production.json') + .assertEqual('production.yml', 'app/config/production.yml') + .assertNotExists('test.json') + .assertEqual('test.yml', 'app/config/test.yml'); }); it('should render the config templates (MongoDB option).', async () => { await createApp({ name: 'test-fooBar', mongodb: true }); - testEnv - .validateSpec('config/default.mongodb.json', 'config/default.json') - .shouldNotExist('config/default.yml') - .validateSpec('config/development.json') - .shouldNotExist('config/development.yml') - .validateSpec('config/e2e.mongodb.json', 'config/e2e.json') - .shouldNotExist('config/e2e.yml') - .validateSpec('config/production.json') - .shouldNotExist('config/production.yml') - .validateSpec('config/test.mongodb.json', 'config/test.json') - .shouldNotExist('config/test.yml'); + fs + .cd('test-foo-bar/config') + .assertEqual('default.json', 'app/config/default.mongodb.json') + .assertNotExists('default.yml') + .assertEqual('development.json', 'app/config/development.json') + .assertNotExists('development.yml') + .assertEqual('e2e.json', 'app/config/e2e.mongodb.json') + .assertNotExists('e2e.yml') + .assertEqual('production.json', 'app/config/production.json') + .assertNotExists('production.yml') + .assertEqual('test.json', 'app/config/test.mongodb.json') + .assertNotExists('test.yml'); }); it('should render the config templates (MongoDB & YAML options).', async () => { await createApp({ name: 'test-fooBar', mongodb: true, yaml: true }); - testEnv - .shouldNotExist('config/default.json') - .validateSpec('config/default.mongodb.yml', 'config/default.yml') - .shouldNotExist('config/development.json') - .validateSpec('config/development.yml') - .shouldNotExist('config/e2e.json') - .validateSpec('config/e2e.mongodb.yml', 'config/e2e.yml') - .shouldNotExist('config/production.json') - .validateSpec('config/production.yml') - .shouldNotExist('config/test.json') - .validateSpec('config/test.mongodb.yml', 'config/test.yml'); + fs + .cd('test-foo-bar/config') + .assertNotExists('default.json') + .assertEqual('default.yml', 'app/config/default.mongodb.yml') + .assertNotExists('development.json') + .assertEqual('development.yml', 'app/config/development.yml') + .assertNotExists('e2e.json') + .assertEqual('e2e.yml', 'app/config/e2e.mongodb.yml') + .assertNotExists('production.json') + .assertEqual('production.yml', 'app/config/production.yml') + .assertNotExists('test.json') + .assertEqual('test.yml', 'app/config/test.mongodb.yml'); }); it('should copy the public directory.', async () => { await createApp({ name: 'test-fooBar' }); - testEnv - .validateSpec('public/index.html') - .validateFileSpec('public/logo.png'); + fs + .cd('test-foo-bar/public') + .assertEqual('index.html', 'app/public/index.html') + .assertEqual('logo.png', 'app/public/logo.png'); }); it('shoud copy the src/e2e templates.', async () => { await createApp({ name: 'test-fooBar' }); - testEnv - .validateSpec('src/e2e/index.ts'); + fs + .cd('test-foo-bar/src/e2e') + .assertEqual('index.ts', 'app/src/e2e/index.ts'); }); it('shoud copy the src/e2e templates (MongoDB option).', async () => { await createApp({ name: 'test-fooBar', mongodb: true }); - testEnv - .validateSpec('src/e2e/index.mongodb.ts', 'src/e2e/index.ts'); + fs + .cd('test-foo-bar/src/e2e') + .assertEqual('index.ts', 'app/src/e2e/index.mongodb.ts'); }); it('shoud copy the src/scripts templates.', async () => { await createApp({ name: 'test-fooBar' }); - testEnv - .validateSpec('src/scripts/create-user.ts'); + fs + .cd('test-foo-bar/src/scripts') + .assertEqual('create-user.ts', 'app/src/scripts/create-user.ts'); }); it('shoud copy the src/scripts templates (MongoDB option).', async () => { await createApp({ name: 'test-fooBar', mongodb: true }); - testEnv - .validateSpec( - 'src/scripts/create-user.mongodb.ts', - 'src/scripts/create-user.ts' - ); + fs + .cd('test-foo-bar/src/scripts') + .assertEqual('create-user.ts', 'app/src/scripts/create-user.mongodb.ts'); }); it('should render the src/app/controllers templates.', async () => { await createApp({ name: 'test-fooBar' }); - testEnv - .validateSpec('src/app/controllers/index.ts') - .validateSpec('src/app/controllers/api.controller.spec.ts') - .validateSpec('src/app/controllers/api.controller.ts'); + fs + .cd('test-foo-bar/src/app/controllers') + .assertEqual('index.ts', 'app/src/app/controllers/index.ts') + .assertEqual('api.controller.spec.ts', 'app/src/app/controllers/api.controller.spec.ts') + .assertEqual('api.controller.ts', 'app/src/app/controllers/api.controller.ts'); }); it('should render the src/app/hooks templates.', async () => { await createApp({ name: 'test-fooBar' }); - testEnv - .validateSpec('src/app/hooks/index.ts'); + fs + .cd('test-foo-bar/src/app/hooks') + .assertEqual('index.ts', 'app/src/app/hooks/index.ts'); }); it('should render the src/app/entities templates.', async () => { await createApp({ name: 'test-fooBar' }); - testEnv - .validateSpec('src/app/entities/index.ts') - .validateSpec('src/app/entities/user.entity.ts') - .shouldNotExist('src/app/models'); + fs + .cd('test-foo-bar/src/app/entities') + .assertEqual('index.ts', 'app/src/app/entities/index.ts') + .assertEqual('user.entity.ts', 'app/src/app/entities/user.entity.ts') + .cd('..') + .assertNotExists('models'); }); it('should render the src/app/models templates (MongoDB option).', async () => { await createApp({ name: 'test-fooBar', mongodb: true }); - testEnv - .validateSpec('src/app/models/index.ts') - .validateSpec('src/app/models/user.model.ts') - .shouldNotExist('src/app/entities'); + fs + .cd('test-foo-bar/src/app/models') + .assertEqual('index.ts', 'app/src/app/models/index.ts') + .assertEqual('user.model.ts', 'app/src/app/models/user.model.ts') + .cd('..') + .assertNotExists('entities'); }); it('should render the src/app/services templates.', async () => { await createApp({ name: 'test-fooBar' }); - testEnv - .validateSpec('src/app/services/index.ts'); + fs + .cd('test-foo-bar/src/app/services') + .assertEqual('index.ts', 'app/src/app/services/index.ts'); }); it('should render the src/app templates.', async () => { await createApp({ name: 'test-fooBar' }); - testEnv - .validateSpec('src/app/app.controller.ts'); + fs + .cd('test-foo-bar/src/app') + .assertEqual('app.controller.ts', 'app/src/app/app.controller.ts'); }); it('should render the src templates.', async () => { await createApp({ name: 'test-fooBar' }); - testEnv - .validateSpec('src/e2e.ts') - .validateSpec('src/index.ts') - .validateSpec('src/test.ts'); + fs + .cd('test-foo-bar/src') + .assertEqual('e2e.ts', 'app/src/e2e.ts') + .assertEqual('index.ts', 'app/src/index.ts') + .assertEqual('test.ts', 'app/src/test.ts'); }); it('should render the src templates (MongoDB option).', async () => { await createApp({ name: 'test-fooBar', mongodb: true }); - testEnv - .validateSpec('src/e2e.ts') - .validateSpec('src/index.mongodb.ts', 'src/index.ts') - .validateSpec('src/test.ts'); + fs + .cd('test-foo-bar/src') + .assertEqual('e2e.ts', 'app/src/e2e.ts') + .assertEqual('index.ts', 'app/src/index.mongodb.ts') + .assertEqual('test.ts', 'app/src/test.ts'); }); it('should render the root templates.', async () => { await createApp({ name: 'test-fooBar' }); - testEnv - .validateSpec('gitignore', '.gitignore') - .validateSpec('ormconfig.js') - .validateSpec('package.json') - .validateSpec('tsconfig.json') - .validateSpec('tsconfig.app.json') - .validateSpec('tsconfig.e2e.json') - .validateSpec('tsconfig.json') - .validateSpec('tsconfig.migrations.json') - .validateSpec('tsconfig.scripts.json') - .validateSpec('tsconfig.test.json') - .validateSpec('.eslintrc.js'); + fs + .cd('test-foo-bar') + .assertEqual('.gitignore', 'app/gitignore') + .assertEqual('ormconfig.js', 'app/ormconfig.js') + .assertEqual('package.json', 'app/package.json') + .assertEqual('tsconfig.json', 'app/tsconfig.json') + .assertEqual('tsconfig.app.json', 'app/tsconfig.app.json') + .assertEqual('tsconfig.e2e.json', 'app/tsconfig.e2e.json') + .assertEqual('tsconfig.json', 'app/tsconfig.json') + .assertEqual('tsconfig.migrations.json', 'app/tsconfig.migrations.json') + .assertEqual('tsconfig.scripts.json', 'app/tsconfig.scripts.json') + .assertEqual('tsconfig.test.json', 'app/tsconfig.test.json') + .assertEqual('.eslintrc.js', 'app/.eslintrc.js'); }); it('should render the root templates (YAML option).', async () => { await createApp({ name: 'test-fooBar', yaml: true }); - testEnv - .validateSpec('gitignore', '.gitignore') - .validateSpec('ormconfig.js') - .validateSpec('package.yaml.json', 'package.json') - .validateSpec('tsconfig.json') - .validateSpec('tsconfig.app.json') - .validateSpec('tsconfig.e2e.json') - .validateSpec('tsconfig.json') - .validateSpec('tsconfig.migrations.json') - .validateSpec('tsconfig.scripts.json') - .validateSpec('tsconfig.test.json') - .validateSpec('.eslintrc.js'); + fs + .cd('test-foo-bar') + .assertEqual('.gitignore', 'app/gitignore') + .assertEqual('ormconfig.js', 'app/ormconfig.js') + .assertEqual('package.json', 'app/package.yaml.json') + .assertEqual('tsconfig.json', 'app/tsconfig.json') + .assertEqual('tsconfig.app.json', 'app/tsconfig.app.json') + .assertEqual('tsconfig.e2e.json', 'app/tsconfig.e2e.json') + .assertEqual('tsconfig.json', 'app/tsconfig.json') + .assertEqual('tsconfig.migrations.json', 'app/tsconfig.migrations.json') + .assertEqual('tsconfig.scripts.json', 'app/tsconfig.scripts.json') + .assertEqual('tsconfig.test.json', 'app/tsconfig.test.json') + .assertEqual('.eslintrc.js', 'app/.eslintrc.js'); }); it('should render the root templates (MongoDB option).', async () => { await createApp({ name: 'test-fooBar', mongodb: true }); - testEnv - .validateSpec('gitignore', '.gitignore') - .shouldNotExist('ormconfig.js') - .validateSpec('package.mongodb.json', 'package.json') - .validateSpec('tsconfig.json') - .validateSpec('tsconfig.app.json') - .validateSpec('tsconfig.e2e.json') - .validateSpec('tsconfig.json') - .shouldNotExist('tsconfig.migrations.json') - .validateSpec('tsconfig.scripts.json') - .validateSpec('tsconfig.test.json') - .validateSpec('.eslintrc.js'); + fs + .cd('test-foo-bar') + .assertEqual('.gitignore', 'app/gitignore') + .assertNotExists('ormconfig.js') + .assertEqual('package.json', 'app/package.mongodb.json') + .assertEqual('tsconfig.json', 'app/tsconfig.json') + .assertEqual('tsconfig.app.json', 'app/tsconfig.app.json') + .assertEqual('tsconfig.e2e.json', 'app/tsconfig.e2e.json') + .assertEqual('tsconfig.json', 'app/tsconfig.json') + .assertNotExists('tsconfig.migrations.json') + .assertEqual('tsconfig.scripts.json', 'app/tsconfig.scripts.json') + .assertEqual('tsconfig.test.json', 'app/tsconfig.test.json') + .assertEqual('.eslintrc.js', 'app/.eslintrc.js'); }); it('should render the root templates (MongoDB & YAML options).', async () => { await createApp({ name: 'test-fooBar', mongodb: true, yaml: true }); - testEnv - .validateSpec('gitignore', '.gitignore') - .shouldNotExist('ormconfig.js') - .validateSpec('package.mongodb.yaml.json', 'package.json') - .validateSpec('tsconfig.json') - .validateSpec('tsconfig.app.json') - .validateSpec('tsconfig.e2e.json') - .validateSpec('tsconfig.json') - .shouldNotExist('tsconfig.migrations.json') - .validateSpec('tsconfig.scripts.json') - .validateSpec('tsconfig.test.json') - .validateSpec('.eslintrc.js'); + fs + .cd('test-foo-bar') + .assertEqual('.gitignore', 'app/gitignore') + .assertNotExists('ormconfig.js') + .assertEqual('package.json', 'app/package.mongodb.yaml.json') + .assertEqual('tsconfig.json', 'app/tsconfig.json') + .assertEqual('tsconfig.app.json', 'app/tsconfig.app.json') + .assertEqual('tsconfig.e2e.json', 'app/tsconfig.e2e.json') + .assertEqual('tsconfig.json', 'app/tsconfig.json') + .assertNotExists('tsconfig.migrations.json') + .assertEqual('tsconfig.scripts.json', 'app/tsconfig.scripts.json') + .assertEqual('tsconfig.test.json', 'app/tsconfig.test.json') + .assertEqual('.eslintrc.js', 'app/.eslintrc.js'); }); }); diff --git a/packages/cli/src/generate/generators/app/create-app.ts b/packages/cli/src/generate/generators/app/create-app.ts index a66e354211..a39a906253 100644 --- a/packages/cli/src/generate/generators/app/create-app.ts +++ b/packages/cli/src/generate/generators/app/create-app.ts @@ -1,13 +1,12 @@ // std import { execSync, spawn, SpawnOptions } from 'child_process'; -import { existsSync, mkdirSync } from 'fs'; // 3p import { cyan, red } from 'colors/safe'; // FoalTS +import { FileSystem } from '../../file-system'; import { - Generator, getNames, initGitRepo, } from '../../utils'; @@ -51,89 +50,114 @@ export async function createApp({ name, autoInstall, initRepo, mongodb = false, const locals = names; - if (existsSync(names.kebabName)) { + const fs = new FileSystem(); + + if (fs.exists(names.kebabName)) { console.log( red(`\n The target directory "${names.kebabName}" already exists. Please remove it before proceeding.`) ); return; } - mkdirSync(names.kebabName); + + fs + .ensureDir(names.kebabName) + .cd(names.kebabName); log(' 📂 Creating files...'); - const generator = new Generator('app', names.kebabName, { noLogs: true }); - - generator - .copyFileFromTemplates('gitignore', '.gitignore') - .copyFileFromTemplatesOnlyIf(!mongodb, 'ormconfig.js') - .renderTemplateOnlyIf(!mongodb && !yaml, 'package.json', locals) - .renderTemplateOnlyIf(!mongodb && yaml, 'package.yaml.json', locals, 'package.json') - .renderTemplateOnlyIf(mongodb && !yaml, 'package.mongodb.json', locals, 'package.json') - .renderTemplateOnlyIf(mongodb && yaml, 'package.mongodb.yaml.json', locals, 'package.json') - .copyFileFromTemplates('tsconfig.app.json') - .copyFileFromTemplates('tsconfig.e2e.json') - .copyFileFromTemplates('tsconfig.json') - .copyFileFromTemplatesOnlyIf(!mongodb, 'tsconfig.migrations.json') - .copyFileFromTemplates('tsconfig.scripts.json') - .copyFileFromTemplates('tsconfig.test.json') - .copyFileFromTemplates('.eslintrc.js') + + // TODO: noLogs + fs + .copy('app/gitignore', '.gitignore') + .copyOnlyIf(!mongodb, 'app/ormconfig.js', 'ormconfig.js') + .renderOnlyIf(!mongodb && !yaml, 'app/package.json', 'package.json', locals) + .renderOnlyIf(!mongodb && yaml, 'app/package.yaml.json', 'package.json', locals) + .renderOnlyIf(mongodb && !yaml, 'app/package.mongodb.json', 'package.json', locals) + .renderOnlyIf(mongodb && yaml, 'app/package.mongodb.yaml.json', 'package.json', locals) + .copy('app/tsconfig.app.json', 'tsconfig.app.json') + .copy('app/tsconfig.e2e.json', 'tsconfig.e2e.json') + .copy('app/tsconfig.json', 'tsconfig.json') + .copyOnlyIf(!mongodb, 'app/tsconfig.migrations.json', 'tsconfig.migrations.json') + .copy('app/tsconfig.scripts.json', 'tsconfig.scripts.json') + .copy('app/tsconfig.test.json', 'tsconfig.test.json') + .copy('app/.eslintrc.js', '.eslintrc.js') // Config - .mkdirIfDoesNotExist('config') - .renderTemplateOnlyIf(!mongodb && !yaml, 'config/default.json', locals) - .renderTemplateOnlyIf(!mongodb && yaml, 'config/default.yml', locals) - .renderTemplateOnlyIf(mongodb && !yaml, 'config/default.mongodb.json', locals, 'config/default.json') - .renderTemplateOnlyIf(mongodb && yaml, 'config/default.mongodb.yml', locals, 'config/default.yml') - .renderTemplateOnlyIf(!yaml, 'config/development.json', locals) - .renderTemplateOnlyIf(yaml, 'config/development.yml', locals) - .renderTemplateOnlyIf(!mongodb && !yaml, 'config/e2e.json', locals) - .renderTemplateOnlyIf(!mongodb && yaml, 'config/e2e.yml', locals) - .renderTemplateOnlyIf(mongodb && !yaml, 'config/e2e.mongodb.json', locals, 'config/e2e.json') - .renderTemplateOnlyIf(mongodb && yaml, 'config/e2e.mongodb.yml', locals, 'config/e2e.yml') - .renderTemplateOnlyIf(!yaml, 'config/production.json', locals) - .renderTemplateOnlyIf(yaml, 'config/production.yml', locals) - .renderTemplateOnlyIf(!mongodb && !yaml, 'config/test.json', locals) - .renderTemplateOnlyIf(!mongodb && yaml, 'config/test.yml', locals) - .renderTemplateOnlyIf(mongodb && !yaml, 'config/test.mongodb.json', locals, 'config/test.json') - .renderTemplateOnlyIf(mongodb && yaml, 'config/test.mongodb.yml', locals, 'config/test.yml') + .ensureDir('config') + .cd('config') + .renderOnlyIf(!mongodb && !yaml, 'app/config/default.json', 'default.json', locals) + .renderOnlyIf(!mongodb && yaml, 'app/config/default.yml', 'default.yml', locals) + .renderOnlyIf(mongodb && !yaml, 'app/config/default.mongodb.json', 'default.json', locals) + .renderOnlyIf(mongodb && yaml, 'app/config/default.mongodb.yml', 'default.yml', locals) + .renderOnlyIf(!yaml, 'app/config/development.json', 'development.json', locals) + .renderOnlyIf(yaml, 'app/config/development.yml', 'development.yml', locals) + .renderOnlyIf(!mongodb && !yaml, 'app/config/e2e.json', 'e2e.json', locals) + .renderOnlyIf(!mongodb && yaml, 'app/config/e2e.yml', 'e2e.yml', locals) + .renderOnlyIf(mongodb && !yaml, 'app/config/e2e.mongodb.json', 'e2e.json', locals) + .renderOnlyIf(mongodb && yaml, 'app/config/e2e.mongodb.yml', 'e2e.yml', locals) + .renderOnlyIf(!yaml, 'app/config/production.json', 'production.json', locals) + .renderOnlyIf(yaml, 'app/config/production.yml', 'production.yml', locals) + .renderOnlyIf(!mongodb && !yaml, 'app/config/test.json', 'test.json', locals) + .renderOnlyIf(!mongodb && yaml, 'app/config/test.yml', 'test.yml', locals) + .renderOnlyIf(mongodb && !yaml, 'app/config/test.mongodb.json', 'test.json', locals) + .renderOnlyIf(mongodb && yaml, 'app/config/test.mongodb.yml', 'test.yml', locals) + .cd('..') // Public - .mkdirIfDoesNotExist('public') - .copyFileFromTemplates('public/index.html') - .copyFileFromTemplates('public/logo.png') + .ensureDir('public') + .cd('public') + .copy('app/public/index.html', 'index.html') + .copy('app/public/logo.png', 'logo.png') + .cd('..') // Src - .mkdirIfDoesNotExist('src') - .copyFileFromTemplates('src/e2e.ts') - .copyFileFromTemplatesOnlyIf(mongodb, 'src/index.mongodb.ts', 'src/index.ts') - .copyFileFromTemplatesOnlyIf(!mongodb, 'src/index.ts') - .copyFileFromTemplates('src/test.ts') + .ensureDir('src') + .cd('src') + .copy('app/src/e2e.ts', 'e2e.ts') + .copyOnlyIf(mongodb, 'app/src/index.mongodb.ts', 'index.ts') + .copyOnlyIf(!mongodb, 'app/src/index.ts', 'index.ts') + .copy('app/src/test.ts', 'test.ts') // App - .mkdirIfDoesNotExist('src/app') - .copyFileFromTemplates('src/app/app.controller.ts') + .ensureDir('app') + .cd('app') + .copy('app/src/app/app.controller.ts', 'app.controller.ts') // Controllers - .mkdirIfDoesNotExist('src/app/controllers') - .copyFileFromTemplates('src/app/controllers/index.ts') - .copyFileFromTemplates('src/app/controllers/api.controller.ts') - .copyFileFromTemplates('src/app/controllers/api.controller.spec.ts') + .ensureDir('controllers') + .cd('controllers') + .copy('app/src/app/controllers/index.ts', 'index.ts') + .copy('app/src/app/controllers/api.controller.ts', 'api.controller.ts') + .copy('app/src/app/controllers/api.controller.spec.ts', 'api.controller.spec.ts') + .cd('..') // Entities - .mkdirIfDoesNotExistOnlyIf(!mongodb, 'src/app/entities') - .copyFileFromTemplatesOnlyIf(!mongodb, 'src/app/entities/index.ts') - .copyFileFromTemplatesOnlyIf(!mongodb, 'src/app/entities/user.entity.ts') + .ensureDirOnlyIf(!mongodb, 'entities') + .cd('entities') + .copyOnlyIf(!mongodb, 'app/src/app/entities/index.ts', 'index.ts') + .copyOnlyIf(!mongodb, 'app/src/app/entities/user.entity.ts', 'user.entity.ts') + .cd('..') // Hooks - .mkdirIfDoesNotExist('src/app/hooks') - .copyFileFromTemplates('src/app/hooks/index.ts') + .ensureDir('hooks') + .cd('hooks') + .copy('app/src/app/hooks/index.ts', 'index.ts') + .cd('..') // Models - .mkdirIfDoesNotExistOnlyIf(mongodb, 'src/app/models') - .copyFileFromTemplatesOnlyIf(mongodb, 'src/app/models/index.ts') - .copyFileFromTemplatesOnlyIf(mongodb, 'src/app/models/user.model.ts') + .ensureDirOnlyIf(mongodb, 'models') + .cd('models') + .copyOnlyIf(mongodb, 'app/src/app/models/index.ts', 'index.ts') + .copyOnlyIf(mongodb, 'app/src/app/models/user.model.ts', 'user.model.ts') + .cd('..') // Services - .mkdirIfDoesNotExist('src/app/services') - .copyFileFromTemplates('src/app/services/index.ts') + .ensureDir('services') + .cd('services') + .copy('app/src/app/services/index.ts', 'index.ts') + .cd('..') + .cd('..') // E2E - .mkdirIfDoesNotExist('src/e2e') - .copyFileFromTemplatesOnlyIf(!mongodb, 'src/e2e/index.ts') - .copyFileFromTemplatesOnlyIf(mongodb, 'src/e2e/index.mongodb.ts', 'src/e2e/index.ts') + .ensureDir('e2e') + .cd('e2e') + .copyOnlyIf(!mongodb, 'app/src/e2e/index.ts', 'index.ts') + .copyOnlyIf(mongodb, 'app/src/e2e/index.mongodb.ts', 'index.ts') + .cd('..') // Scripts - .mkdirIfDoesNotExist('src/scripts') - .copyFileFromTemplatesOnlyIf(!mongodb, 'src/scripts/create-user.ts') - .copyFileFromTemplatesOnlyIf(mongodb, 'src/scripts/create-user.mongodb.ts', 'src/scripts/create-user.ts'); + .ensureDir('scripts') + .cd('scripts') + .copyOnlyIf(!mongodb, 'app/src/scripts/create-user.ts', 'create-user.ts') + .copyOnlyIf(mongodb, 'app/src/scripts/create-user.mongodb.ts', 'create-user.ts'); if (autoInstall) { log(''); From be0832ae0ebd785fa08b5639f67cb57d6d655928 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Sat, 16 May 2020 15:56:50 +0200 Subject: [PATCH 35/92] [CLI] Refactor create-sub-app with FileSystem --- .../generators/sub-app/create-sub-app.spec.ts | 32 +-------- .../generators/sub-app/create-sub-app.ts | 70 ++++++++++--------- 2 files changed, 38 insertions(+), 64 deletions(-) diff --git a/packages/cli/src/generate/generators/sub-app/create-sub-app.spec.ts b/packages/cli/src/generate/generators/sub-app/create-sub-app.spec.ts index 084801b8b5..20623767f6 100644 --- a/packages/cli/src/generate/generators/sub-app/create-sub-app.spec.ts +++ b/packages/cli/src/generate/generators/sub-app/create-sub-app.spec.ts @@ -1,6 +1,6 @@ // std import { strictEqual } from 'assert'; -import { existsSync, writeFileSync } from 'fs'; +import { writeFileSync } from 'fs'; // FoalTS import { @@ -12,7 +12,7 @@ import { } from '../../utils'; import { createSubApp } from './create-sub-app'; -// TODO: Use TestEnvironment. +// TODO: Use FileSystem or remove this command. describe('createSubApp', () => { @@ -188,32 +188,4 @@ describe('createSubApp', () => { }); - describe('should create the controllers/templates directory.', () => { - - function test(prefix = '') { - writeFileSync(`${prefix}index.ts`, indexInitialContent, 'utf8'); - - createSubApp({ name: 'test-fooBar' }); - - if (!existsSync(`${prefix}test-foo-bar/controllers/templates`)) { - throw new Error('The controllers/templates directory should be created.'); - } - } - - it('in src/app/sub-apps/ if the directory exists.', () => { - mkdirIfDoesNotExist('src/app/sub-apps'); - test('src/app/sub-apps/'); - }); - - it('in sub-apps/ if the directory exists.', () => { - mkdirIfDoesNotExist('sub-apps'); - test('sub-apps/'); - }); - - it('in the current directory otherwise.', () => { - test(); - }); - - }); - }); diff --git a/packages/cli/src/generate/generators/sub-app/create-sub-app.ts b/packages/cli/src/generate/generators/sub-app/create-sub-app.ts index 5560db7298..f092382c09 100644 --- a/packages/cli/src/generate/generators/sub-app/create-sub-app.ts +++ b/packages/cli/src/generate/generators/sub-app/create-sub-app.ts @@ -1,46 +1,48 @@ -// 3p -import { existsSync, writeFileSync } from 'fs'; - // FoalTS -import { Generator, getNames, mkdirIfDoesNotExist } from '../../utils'; +import { FileSystem } from '../../file-system'; +import { getNames } from '../../utils'; export function createSubApp({ name }: { name: string }) { - const names = getNames(name); + const fs = new FileSystem(); + (fs as any).testDir = ''; let root = ''; - - if (existsSync('src/app')) { - mkdirIfDoesNotExist('src/app/sub-apps'); - if (!existsSync('src/app/sub-apps/index.ts')) { - writeFileSync('src/app/sub-apps/index.ts', '', 'utf8'); - } + if (fs.exists('src/app')) { + fs + .ensureDir('src/app/sub-apps') + .ensureFile('src/app/sub-apps/index.ts'); root = 'src/app/sub-apps'; - } else if (existsSync('sub-apps')) { + } else if (fs.exists('sub-apps')) { root = 'sub-apps'; } - new Generator('sub-app', root) - .mkdirIfDoesNotExist(names.kebabName) - .updateFile('index.ts', content => { - content += `export { ${names.upperFirstCamelName}Controller } from './${names.kebabName}';\n`; - return content; - }); + const names = getNames(name); - new Generator('sub-app', root ? root + '/' + names.kebabName : names.kebabName) - .renderTemplate('index.ts', names) - .renderTemplate('controller.ts', names, `${names.kebabName}.controller.ts`) - // Controllers - .mkdirIfDoesNotExist('controllers') - .copyFileFromTemplates('controllers/index.ts') - .mkdirIfDoesNotExist('controllers/templates') - // Hooks - .mkdirIfDoesNotExist('hooks') - .copyFileFromTemplates('hooks/index.ts') - // Entities - .mkdirIfDoesNotExist('entities') - .copyFileFromTemplates('entities/index.ts') - // Services - .mkdirIfDoesNotExist('services') - .copyFileFromTemplates('services/index.ts'); + fs + .cd(root) + .ensureDir(names.kebabName) + .addNamedExportIn('index.ts', `${names.upperFirstCamelName}Controller`, `./${names.kebabName}`) + .cd(names.kebabName) + .render('sub-app/index.ts', 'index.ts', names) + .render('sub-app/controller.ts', `${names.kebabName}.controller.ts`, names) + // Controllers + .ensureDir('controllers') + .cd('controllers') + .copy('sub-app/controllers/index.ts', 'index.ts') + .cd('..') + // Hooks + .ensureDir('hooks') + .cd('hooks') + .copy('sub-app/hooks/index.ts', 'index.ts') + .cd('..') + // Entities + .ensureDir('entities') + .cd('entities') + .copy('sub-app/entities/index.ts', 'index.ts') + .cd('..') + // Services + .ensureDir('services') + .cd('services') + .copy('sub-app/services/index.ts', 'index.ts'); } From 5d2d2b04732df0c199565ee16753550e2ef80304 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Sat, 16 May 2020 22:08:13 +0200 Subject: [PATCH 36/92] [CLI] Refactor create-rest-api with FileSystem --- .../rest-api/create-rest-api.spec.ts | 133 +++++++++--------- .../generators/rest-api/create-rest-api.ts | 55 ++++---- 2 files changed, 93 insertions(+), 95 deletions(-) diff --git a/packages/cli/src/generate/generators/rest-api/create-rest-api.spec.ts b/packages/cli/src/generate/generators/rest-api/create-rest-api.spec.ts index 608e77af41..c0dca118a7 100644 --- a/packages/cli/src/generate/generators/rest-api/create-rest-api.spec.ts +++ b/packages/cli/src/generate/generators/rest-api/create-rest-api.spec.ts @@ -1,119 +1,124 @@ // FoalTS -import { - rmDirAndFilesIfExist, - rmfileIfExists, - TestEnvironment, -} from '../../utils'; +import { FileSystem } from '../../file-system'; import { createRestApi } from './create-rest-api'; describe('createRestApi', () => { - afterEach(() => { - rmDirAndFilesIfExist('src/app'); - // We cannot remove src/ since the generator code lives within. This is bad testing - // approach. - rmDirAndFilesIfExist('entities'); - rmDirAndFilesIfExist('controllers'); - rmfileIfExists('app.controller.ts'); - rmfileIfExists('test-foo-bar.entity.ts'); - rmfileIfExists('test-foo-bar.controller.ts'); - rmfileIfExists('test-foo-bar.controller.spec.ts'); - }); + const fs = new FileSystem(); + + beforeEach(() => fs.setUp()); + + afterEach(() => fs.tearDown()); function test(root: string) { describe(`when the directories ${root}entities/ and ${root}controllers/ exist`, () => { - const testEntityEnv = new TestEnvironment('rest-api', root + 'entities'); - const testControllerEnv = new TestEnvironment('rest-api', root + 'controllers'); - beforeEach(() => { - testEntityEnv.mkRootDirIfDoesNotExist(); - testEntityEnv.copyFileFromMocks('index.entities.ts', 'index.ts'); - testControllerEnv.mkRootDirIfDoesNotExist(); - testControllerEnv.copyFileFromMocks('index.controllers.ts', 'index.ts'); + fs + .ensureDir(root) + .cd(root) + .ensureDir('entities') + .cd('entities') + .copyMock('rest-api/index.entities.ts', 'index.ts') + .cd('..') + .ensureDir('controllers') + .cd('controllers') + .copyMock('rest-api/index.controllers.ts', 'index.ts') + .cd('..'); }); it('should render the templates in the proper directory.', () => { createRestApi({ name: 'test-fooBar', register: false }); - testEntityEnv - .validateSpec('test-foo-bar.entity.ts') - .validateSpec('index.entities.ts', 'index.ts'); - - testControllerEnv - .validateSpec('test-foo-bar.controller.ts') - .validateSpec('test-foo-bar.controller.spec.ts') - .validateSpec('index.controllers.ts', 'index.ts'); + fs + .cd('entities') + .assertEqual('test-foo-bar.entity.ts', 'rest-api/test-foo-bar.entity.ts') + .assertEqual('index.ts', 'rest-api/index.entities.ts') + .cd('..') + .cd('controllers') + .assertEqual('test-foo-bar.controller.ts', 'rest-api/test-foo-bar.controller.ts') + .assertEqual('test-foo-bar.controller.spec.ts', 'rest-api/test-foo-bar.controller.spec.ts') + .assertEqual('index.ts', 'rest-api/index.controllers.ts'); }); }); describe(`when the directories ${root}entities/ and ${root}controllers/ exist and "register" is true.`, () => { - const testEntityEnv = new TestEnvironment('rest-api', root + 'entities'); - const testControllerEnv = new TestEnvironment('rest-api', root + 'controllers'); - beforeEach(() => { - testEntityEnv.mkRootDirIfDoesNotExist(); - testEntityEnv.copyFileFromMocks('index.entities.ts', 'index.ts'); - testControllerEnv.mkRootDirIfDoesNotExist(); - testControllerEnv.copyFileFromMocks('index.controllers.ts', 'index.ts'); + fs + .ensureDir(root) + .cd(root) + .ensureDir('entities') + .cd('entities') + .copyMock('rest-api/index.entities.ts', 'index.ts') + .cd('..') + .ensureDir('controllers') + .cd('controllers') + .copyMock('rest-api/index.controllers.ts', 'index.ts') + .cd('..'); }); it('should update the "subControllers" import in src/app/app.controller.ts if it exists.', () => { - testControllerEnv.copyFileFromMocks('app.controller.controller-import.ts', '../app.controller.ts'); + fs + .copyMock('rest-api/app.controller.controller-import.ts', 'app.controller.ts'); createRestApi({ name: 'test-fooBar', register: true }); - testControllerEnv - .validateSpec('app.controller.controller-import.ts', '../app.controller.ts'); + fs + .assertEqual('app.controller.ts', 'rest-api/app.controller.controller-import.ts'); }); it('should add a "subControllers" import in src/app/app.controller.ts if none already exists.', () => { - testControllerEnv.copyFileFromMocks('app.controller.no-controller-import.ts', '../app.controller.ts'); + fs + .copyMock('rest-api/app.controller.no-controller-import.ts', 'app.controller.ts'); createRestApi({ name: 'test-fooBar', register: true }); - testControllerEnv - .validateSpec('app.controller.no-controller-import.ts', '../app.controller.ts'); + fs + .assertEqual('app.controller.ts', 'rest-api/app.controller.no-controller-import.ts'); }); it('should update the "@foal/core" import in src/app/app.controller.ts if it exists.', () => { - testControllerEnv.copyFileFromMocks('app.controller.core-import.ts', '../app.controller.ts'); + fs + .copyMock('rest-api/app.controller.core-import.ts', 'app.controller.ts'); createRestApi({ name: 'test-fooBar', register: true }); - testControllerEnv - .validateSpec('app.controller.core-import.ts', '../app.controller.ts'); + fs + .assertEqual('app.controller.ts', 'rest-api/app.controller.core-import.ts'); }); it('should update the "subControllers = []" property in src/app/app.controller.ts if it exists.', () => { - testControllerEnv.copyFileFromMocks('app.controller.empty-property.ts', '../app.controller.ts'); + fs + .copyMock('rest-api/app.controller.empty-property.ts', 'app.controller.ts'); createRestApi({ name: 'test-fooBar', register: true }); - testControllerEnv - .validateSpec('app.controller.empty-property.ts', '../app.controller.ts'); + fs + .assertEqual('app.controller.ts', 'rest-api/app.controller.empty-property.ts'); }); it('should update the "subControllers = [ \\n \\n ]" property in src/app/app.controller.ts if it exists.', () => { - testControllerEnv.copyFileFromMocks('app.controller.empty-spaced-property.ts', '../app.controller.ts'); + fs + .copyMock('rest-api/app.controller.empty-spaced-property.ts', 'app.controller.ts'); createRestApi({ name: 'test-fooBar', register: true }); - testControllerEnv - .validateSpec('app.controller.empty-spaced-property.ts', '../app.controller.ts'); + fs + .assertEqual('app.controller.ts', 'rest-api/app.controller.empty-spaced-property.ts'); }); it('should update the "subControllers = [ \\n (.*) \\n ]" property in' + ' src/app/app.controller.ts if it exists.', () => { - testControllerEnv.copyFileFromMocks('app.controller.no-empty-property.ts', '../app.controller.ts'); + fs + .copyMock('rest-api/app.controller.no-empty-property.ts', 'app.controller.ts'); createRestApi({ name: 'test-fooBar', register: true }); - testControllerEnv - .validateSpec('app.controller.no-empty-property.ts', '../app.controller.ts'); + fs + .assertEqual('app.controller.ts', 'rest-api/app.controller.no-empty-property.ts'); }); }); @@ -125,21 +130,19 @@ describe('createRestApi', () => { describe('when the directory entities/ or the directory controllers/ does not exist.', () => { - const testEnv = new TestEnvironment('rest-api', ''); - beforeEach(() => { - testEnv.mkRootDirIfDoesNotExist(); - testEnv.copyFileFromMocks('index.current-dir.ts', 'index.ts'); + fs + .copyMock('rest-api/index.current-dir.ts', 'index.ts'); }); it('should render the templates in the current directory.', () => { createRestApi({ name: 'test-fooBar', register: false }); - testEnv - .validateSpec('test-foo-bar.entity.ts') - .validateSpec('test-foo-bar.controller.current-dir.ts', 'test-foo-bar.controller.ts') - .validateSpec('test-foo-bar.controller.spec.current-dir.ts', 'test-foo-bar.controller.spec.ts') - .validateSpec('index.current-dir.ts', 'index.ts'); + fs + .assertEqual('test-foo-bar.entity.ts', 'rest-api/test-foo-bar.entity.ts') + .assertEqual('test-foo-bar.controller.ts', 'rest-api/test-foo-bar.controller.current-dir.ts') + .assertEqual('test-foo-bar.controller.spec.ts', 'rest-api/test-foo-bar.controller.spec.current-dir.ts') + .assertEqual('index.ts', 'rest-api/index.current-dir.ts'); }); }); diff --git a/packages/cli/src/generate/generators/rest-api/create-rest-api.ts b/packages/cli/src/generate/generators/rest-api/create-rest-api.ts index f9c83dce89..5c9aefc6c1 100644 --- a/packages/cli/src/generate/generators/rest-api/create-rest-api.ts +++ b/packages/cli/src/generate/generators/rest-api/create-rest-api.ts @@ -1,12 +1,13 @@ // std -import { existsSync, readFileSync } from 'fs'; +import { readFileSync } from 'fs'; import { join } from 'path'; // 3p import { red, underline } from 'colors/safe'; // FoalTS -import { findProjectPath, Generator, getNames } from '../../utils'; +import { FileSystem } from '../../file-system'; +import { findProjectPath, getNames } from '../../utils'; import { registerController } from '../controller/register-controller'; export function createRestApi({ name, register }: { name: string, register: boolean }) { @@ -22,51 +23,45 @@ export function createRestApi({ name, register }: { name: string, register: bool } } - const names = getNames(name); + const fs = new FileSystem(); let entityRoot = ''; let controllerRoot = ''; - - if (existsSync('src/app/entities') && existsSync('src/app/controllers')) { + if (fs.exists('src/app/entities') && fs.exists('src/app/controllers')) { entityRoot = 'src/app/entities'; controllerRoot = 'src/app/controllers'; - } else if (existsSync('entities') && existsSync('controllers')) { + } else if (fs.exists('entities') && fs.exists('controllers')) { entityRoot = 'entities'; controllerRoot = 'controllers'; } - new Generator('rest-api', entityRoot) - .renderTemplate('entity.ts', names, `${names.kebabName}.entity.ts`) - .updateFile('index.ts', content => { - content += `export { ${names.upperFirstCamelName} } from './${names.kebabName}.entity';\n`; - return content; - }); + const names = getNames(name); - const controllerGenerator = new Generator('rest-api', controllerRoot); + fs + .cd(entityRoot) + .render('rest-api/entity.ts', `${names.kebabName}.entity.ts`, names) + .addNamedExportIn('index.ts', names.upperFirstCamelName, `./${names.kebabName}.entity`); - controllerGenerator - .renderTemplate( - controllerRoot ? 'controller.ts' : 'controller.current-dir.ts', + fs.currentDir = ''; + + fs + .cd(controllerRoot) + .render( + controllerRoot ? 'rest-api/controller.ts' : 'rest-api/controller.current-dir.ts', + `${names.kebabName}.controller.ts`, names, - `${names.kebabName}.controller.ts` ) - .renderTemplate( - controllerRoot ? 'controller.spec.ts' : 'controller.spec.current-dir.ts', + .render( + controllerRoot ? 'rest-api/controller.spec.ts' : 'rest-api/controller.spec.current-dir.ts', + `${names.kebabName}.controller.spec.ts`, names, - `${names.kebabName}.controller.spec.ts` ) - .updateFile('index.ts', content => { - content += `export { ${names.upperFirstCamelName}Controller } from './${names.kebabName}.controller';\n`; - return content; + .addNamedExportIn('index.ts', `${names.upperFirstCamelName}Controller`, `./${names.kebabName}.controller`) + .cd('..') + .modifyOnlyfIf(register, 'app.controller.ts', content => { + return registerController(content, `${names.upperFirstCamelName}Controller`, `/${names.kebabName}s`); }); - if (register) { - controllerGenerator - .updateFile('../app.controller.ts', content => { - return registerController(content, `${names.upperFirstCamelName}Controller`, `/${names.kebabName}s`); - }, { allowFailure: true }); - } - if (process.env.NODE_ENV !== 'test') { console.log( `\n${underline('Next steps:')} Complete ${names.upperFirstCamelName} (${names.kebabName}.entity)` From 02973b0bb73c77ea188cc62f3e0bacf6ab648644 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Sat, 16 May 2020 22:14:58 +0200 Subject: [PATCH 37/92] [CLI] Refactor create-model with FileSystem --- .../generators/model/create-model.spec.ts | 36 ++++++++----------- .../generate/generators/model/create-model.ts | 25 ++++++------- 2 files changed, 27 insertions(+), 34 deletions(-) diff --git a/packages/cli/src/generate/generators/model/create-model.spec.ts b/packages/cli/src/generate/generators/model/create-model.spec.ts index 95eb6cde35..bded2446f3 100644 --- a/packages/cli/src/generate/generators/model/create-model.spec.ts +++ b/packages/cli/src/generate/generators/model/create-model.spec.ts @@ -1,44 +1,36 @@ // FoalTS -import { - rmDirAndFilesIfExist, - rmfileIfExists, - TestEnvironment, -} from '../../utils'; +import { FileSystem } from '../../file-system'; import { createModel } from './create-model'; describe('createModel', () => { - afterEach(() => { - rmDirAndFilesIfExist('src/app'); - // We cannot remove src/ since the generator code lives within. This is bad testing - // approach. - rmDirAndFilesIfExist('models'); - rmfileIfExists('a-test-foo-bar.model.ts'); - rmfileIfExists('test-foo-bar.model.ts'); - rmfileIfExists('index.ts'); - }); + const fs = new FileSystem(); + + beforeEach(() => fs.setUp()); + + afterEach(() => fs.tearDown()); function test(root: string) { describe(`when the directory ${root}/ exists`, () => { - const testEnv = new TestEnvironment('model', root); - beforeEach(() => { - testEnv.mkRootDirIfDoesNotExist(); - testEnv.copyFileFromMocks('index.ts'); + fs + .ensureDir(root) + .cd(root) + .copyMock('model/index.ts', 'index.ts'); }); it('should render the templates in the proper directory.', () => { createModel({ name: 'test-fooBar' }); - testEnv - .validateSpec('test-foo-bar.model.ts') - .validateSpec('index.ts', 'index.ts'); + fs + .assertEqual('test-foo-bar.model.ts', 'model/test-foo-bar.model.ts') + .assertEqual('index.ts', 'model/index.ts'); }); it('should not throw an error if index.ts does not exist.', () => { - testEnv.rmfileIfExists('index.ts'); + fs.rmfile('index.ts'); createModel({ name: 'test-fooBar' }); }); diff --git a/packages/cli/src/generate/generators/model/create-model.ts b/packages/cli/src/generate/generators/model/create-model.ts index 7da7709b36..f266b59326 100644 --- a/packages/cli/src/generate/generators/model/create-model.ts +++ b/packages/cli/src/generate/generators/model/create-model.ts @@ -1,12 +1,13 @@ // std -import { existsSync, readFileSync } from 'fs'; +import { readFileSync } from 'fs'; import { join } from 'path'; // 3p import { red, yellow } from 'colors/safe'; // FoalTS -import { findProjectPath, Generator, getNames } from '../../utils'; +import { FileSystem } from '../../file-system'; +import { findProjectPath, getNames } from '../../utils'; export function createModel({ name, checkMongoose }: { name: string, checkMongoose?: boolean }) { const projectPath = findProjectPath(); @@ -22,20 +23,20 @@ export function createModel({ name, checkMongoose }: { name: string, checkMongoo } } - const names = getNames(name); + const fs = new FileSystem(); let root = ''; - - if (existsSync('src/app/models')) { + if (fs.exists('src/app/models')) { root = 'src/app/models'; - } else if (existsSync('models')) { + } else if (fs.exists('models')) { root = 'models'; } - new Generator('model', root) - .renderTemplate('model.ts', names, `${names.kebabName}.model.ts`) - .updateFile('index.ts', content => { - content += `export { ${names.upperFirstCamelName} } from './${names.kebabName}.model';\n`; - return content; - }, { allowFailure: true }); + const names = getNames(name); + + fs + .cd(root) + .render('model/model.ts', `${names.kebabName}.model.ts`, names) + .ensureFile('index.ts') + .addNamedExportIn('index.ts', names.upperFirstCamelName, `./${names.kebabName}.model`); } From 38fcd43267a555903a73b505d9cc0206cd7e3f18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Sat, 16 May 2020 22:22:41 +0200 Subject: [PATCH 38/92] [CLI] Refactor create-script with FileSystem --- .../generators/script/create-script.spec.ts | 16 ++++++++++------ .../generate/generators/script/create-script.ts | 14 ++++++++++---- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/packages/cli/src/generate/generators/script/create-script.spec.ts b/packages/cli/src/generate/generators/script/create-script.spec.ts index 5996411885..96b1a244fc 100644 --- a/packages/cli/src/generate/generators/script/create-script.spec.ts +++ b/packages/cli/src/generate/generators/script/create-script.spec.ts @@ -1,7 +1,7 @@ // FoalTS +import { FileSystem } from '../../file-system'; import { rmDirAndFilesIfExist, - TestEnvironment, } from '../../utils'; import { createScript } from './create-script'; @@ -9,6 +9,10 @@ import { createScript } from './create-script'; describe('createScript', () => { + const fs = new FileSystem(); + // TODO: remove this line. + (fs as any).testDir = ''; + afterEach(() => { rmDirAndFilesIfExist('src/scripts'); // We cannot remove src/ since the generator code lives within. This is bad testing @@ -17,17 +21,17 @@ describe('createScript', () => { describe(`when the directory src/scripts/ exists`, () => { - const testEnv = new TestEnvironment('script', 'src/scripts'); - beforeEach(() => { - testEnv.mkRootDirIfDoesNotExist(); + fs + .ensureDir('src/scripts') + .cd('src/scripts'); }); it('should copy the empty script file in the proper directory.', () => { createScript({ name: 'test-fooBar' }); - testEnv - .validateSpec('test-foo-bar.ts'); + fs + .assertEqual('test-foo-bar.ts', 'script/test-foo-bar.ts'); }); }); diff --git a/packages/cli/src/generate/generators/script/create-script.ts b/packages/cli/src/generate/generators/script/create-script.ts index 4d2a6d0773..8c0dfdd1c7 100644 --- a/packages/cli/src/generate/generators/script/create-script.ts +++ b/packages/cli/src/generate/generators/script/create-script.ts @@ -6,7 +6,8 @@ import { join, relative } from 'path'; import { red } from 'colors/safe'; // FoalTS -import { findProjectPath, Generator, getNames } from '../../utils'; +import { FileSystem } from '../../file-system'; +import { findProjectPath, getNames } from '../../utils'; export function createScript({ name }: { name: string }) { const names = getNames(name); @@ -26,7 +27,12 @@ export function createScript({ name }: { name: string }) { return; } - // Use `relative` to have pretty CREATE logs. - new Generator('script', relative(process.cwd(), scriptPath)) - .copyFileFromTemplates('script.ts', `${names.kebabName}.ts`); + const fs = new FileSystem(); + // TODO: remove this line. + (fs as any).testDir = ''; + + fs + // Use `relative` to have pretty CREATE logs. + .cd(relative(process.cwd(), scriptPath)) + .copy('script/script.ts', `${names.kebabName}.ts`); } From ae132e0cf36f917e85983aa267405c91db8c7012 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Sat, 16 May 2020 22:24:43 +0200 Subject: [PATCH 39/92] [CLI] Remove Generator and TestEnvironment --- .../angular/connect-angular.spec.ts | 3 - packages/cli/src/generate/utils/generator.ts | 119 ------------------ packages/cli/src/generate/utils/index.ts | 2 - .../src/generate/utils/test-environment.ts | 93 -------------- 4 files changed, 217 deletions(-) delete mode 100644 packages/cli/src/generate/utils/generator.ts delete mode 100644 packages/cli/src/generate/utils/test-environment.ts diff --git a/packages/cli/src/generate/generators/angular/connect-angular.spec.ts b/packages/cli/src/generate/generators/angular/connect-angular.spec.ts index 9ef73caaf9..c104073937 100644 --- a/packages/cli/src/generate/generators/angular/connect-angular.spec.ts +++ b/packages/cli/src/generate/generators/angular/connect-angular.spec.ts @@ -1,5 +1,4 @@ import { FileSystem } from '../../file-system'; -import { TestEnvironment } from '../../utils'; import { connectAngular } from './connect-angular'; // TODO: To improve: make the tests (more) independent from each other. @@ -11,8 +10,6 @@ describe('connectAngular', () => { afterEach(() => fs.tearDown()); - const testEnv = new TestEnvironment('angular'); - it('should create a proxy.conf.json file in ${path}/src.', () => { fs .ensureDir('connector-test/angular/src') diff --git a/packages/cli/src/generate/utils/generator.ts b/packages/cli/src/generate/utils/generator.ts deleted file mode 100644 index d0b2e71f31..0000000000 --- a/packages/cli/src/generate/utils/generator.ts +++ /dev/null @@ -1,119 +0,0 @@ -// std -import { copyFileSync, existsSync, readFileSync, writeFileSync } from 'fs'; -import { join } from 'path'; - -// 3p -import { cyan, green } from 'colors/safe'; - -// FoalTS -import { mkdirIfDoesNotExist } from './mkdir-if-does-not-exist'; - -export class Generator { - constructor( - private name: string, - private root: string, - private options: { - noLogs?: boolean - } = {} - ) {} - - /* Create architecture */ - - mkdirIfDoesNotExist(path: string): this { - mkdirIfDoesNotExist(join(this.root, path)); - return this; - } - - mkdirIfDoesNotExistOnlyIf(condition: boolean, path: string): this { - if (condition) { - return this.mkdirIfDoesNotExist(path); - } - return this; - } - - /* Create files */ - - copyFileFromTemplates(srcPath: string, destPath?: string): this { - destPath = destPath || srcPath; - - const absoluteSrcPath = join(__dirname, '../templates', this.name, srcPath); - - if (!existsSync(absoluteSrcPath)) { - throw new Error(`Template not found: ${srcPath}`); - } - - this.logCreate(destPath); - copyFileSync(absoluteSrcPath, join(this.root, destPath)); - return this; - } - - copyFileFromTemplatesOnlyIf(condition: boolean, srcPath: string, destPath?: string): this { - if (condition) { - return this.copyFileFromTemplates(srcPath, destPath); - } - return this; - } - - renderTemplate(templatePath: string, locals: object, destPath?: string): this { - destPath = destPath || templatePath; - - const absoluteTemplatePath = join(__dirname, '../templates', this.name, templatePath); - - if (!existsSync(absoluteTemplatePath)) { - throw new Error(`Template not found: ${templatePath}`); - } - - this.logCreate(destPath); - const template = readFileSync(absoluteTemplatePath, 'utf8'); - let content = template; - for (const key in locals) { - if (locals.hasOwnProperty(key)) { - content = content.split(`/* ${key} */`).join((locals as any)[key]); - } - } - writeFileSync(join(this.root, destPath), content, 'utf8'); - return this; - } - - renderTemplateOnlyIf(condition: boolean, templatePath: string, locals: object, destPath?: string): this { - if (condition) { - return this.renderTemplate(templatePath, locals, destPath); - } - return this; - } - - /* Update files */ - - updateFile(path: string, cb: (content: string) => string, options: { allowFailure?: boolean } = {}) { - let content: string; - try { - content = readFileSync(join(this.root, path), 'utf8'); - } catch (err) { - if (options.allowFailure) { - return this; - } - throw err; - } - this.logUpdate(path); - writeFileSync(join(this.root, path), cb(content), 'utf8'); - return this; - } - - private logCreate(path: string) { - if (this.root) { - path = join(this.root, path); - } - if (process.env.NODE_ENV !== 'test' && !this.options.noLogs) { - console.log(`${green('CREATE')} ${path}`); - } - } - - private logUpdate(path: string) { - if (this.root) { - path = join(this.root, path); - } - if (process.env.NODE_ENV !== 'test' && !this.options.noLogs) { - console.log(`${cyan('UPDATE')} ${path}`); - } - } -} diff --git a/packages/cli/src/generate/utils/index.ts b/packages/cli/src/generate/utils/index.ts index fa2ef7d630..ce2afc494d 100644 --- a/packages/cli/src/generate/utils/index.ts +++ b/packages/cli/src/generate/utils/index.ts @@ -4,11 +4,9 @@ import { join } from 'path'; // FoalTS export { findProjectPath } from './find-project-path'; -export { Generator } from './generator'; export { initGitRepo } from './init-git-repo'; export { rmDirAndFilesIfExist } from './rm-dir-and-files-if-exist'; export { mkdirIfDoesNotExist } from './mkdir-if-does-not-exist'; -export { TestEnvironment } from './test-environment'; export { getNames } from './get-names'; export function rmfileIfExists(path: string) { diff --git a/packages/cli/src/generate/utils/test-environment.ts b/packages/cli/src/generate/utils/test-environment.ts deleted file mode 100644 index 68eb45e955..0000000000 --- a/packages/cli/src/generate/utils/test-environment.ts +++ /dev/null @@ -1,93 +0,0 @@ -// std -import { ok, strictEqual } from 'assert'; -import { copyFileSync, existsSync, readdirSync, readFileSync, rmdirSync, statSync, unlinkSync } from 'fs'; -import { join } from 'path'; - -// FoalTS -import { mkdirIfDoesNotExist } from './mkdir-if-does-not-exist'; - -export class TestEnvironment { - constructor(private generatorName: string, private root: string = '') {} - - /* Create environment */ - mkRootDirIfDoesNotExist() { - if (this.root) { - mkdirIfDoesNotExist(this.root); - } - } - copyFileFromMocks(srcPath: string, destPath?: string) { - destPath = destPath || srcPath; - copyFileSync( - join(__dirname, '../mocks', this.generatorName, srcPath), - join(this.root, destPath) - ); - return this; - } - - /* Remove environment */ - rmfileIfExists(path: string) { - path = join(this.root, path); - if (existsSync(path)) { - unlinkSync(path); - } - } - rmDirAndFilesIfExist(path: string) { - const absolutePath = join(this.root, path); - if (!existsSync(absolutePath)) { - return; - } - - const files = readdirSync(absolutePath); - for (const file of files) { - const stat = statSync(join(absolutePath, file)); - if (stat.isDirectory()) { - this.rmDirAndFilesIfExist(join(path, file)); - } else { - unlinkSync(join(absolutePath, file)); - } - } - - rmdirSync(absolutePath); - } - - /* Test */ - validateSpec(specPath: string, filePath?: string) { - filePath = filePath || specPath; - - const absoluteSpecPath = join(__dirname, '../specs', this.generatorName, specPath); - - if (!existsSync(absoluteSpecPath)) { - throw new Error(`Spec file not found: ${specPath}`); - } - - const spec = readFileSync(absoluteSpecPath, 'utf8'); - const actual = readFileSync(join(this.root, filePath), 'utf8'); - - strictEqual(actual.replace(/\r\n/g, '\n'), spec.replace(/\r\n/g, '\n')); - - return this; - } - - validateFileSpec(specPath: string, filePath?: string) { - filePath = filePath || specPath; - - const absoluteSpecPath = join(__dirname, '../specs', this.generatorName, specPath); - - if (!existsSync(absoluteSpecPath)) { - throw new Error(`Spec file not found: ${specPath}`); - } - - const spec = readFileSync(absoluteSpecPath); - const actual = readFileSync(join(this.root, filePath)); - - ok(actual.equals(spec)); - - return this; - } - - shouldNotExist(filePath: string) { - const exists = existsSync(join(this.root, filePath)); - ok(!exists, `${filePath} should not exist.`); - return this; - } -} From ee62b70a91729a4bbbceba3d43f6764344a17c53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Sat, 16 May 2020 22:43:51 +0200 Subject: [PATCH 40/92] [CLI] Remove legacy unused code --- packages/cli/src/generate/file-system.ts | 3 ++ .../controller/create-controller.spec.ts | 36 +++++-------------- .../controller/create-controller.ts | 10 +++--- .../test-foo-bar.controller.rest.ts | 8 ----- .../templates/controller/controller.rest.ts | 8 ----- packages/cli/src/index.ts | 2 +- 6 files changed, 17 insertions(+), 50 deletions(-) delete mode 100644 packages/cli/src/generate/specs/controller/test-foo-bar.controller.rest.ts delete mode 100644 packages/cli/src/generate/templates/controller/controller.rest.ts diff --git a/packages/cli/src/generate/file-system.ts b/packages/cli/src/generate/file-system.ts index 22582759fa..cb1f800abb 100644 --- a/packages/cli/src/generate/file-system.ts +++ b/packages/cli/src/generate/file-system.ts @@ -275,6 +275,7 @@ export class FileSystem { * Throws an error if the file or directory exists. * * @param {string} path - The path relative to the client directory. + * @returns {this} * @memberof FileSystem */ assertNotExists(path: string): this { @@ -302,6 +303,7 @@ export class FileSystem { * @param {string} actual - The path relative to the client directory. * @param {string} expected - The path relative to the `specs/` directory. * @param {{ binary: boolean }} [{ binary }={ binary: true }] - Specify if the file is binary. + * @returns {this} * @memberof FileSystem */ assertEqual(actual: string, expected: string, { binary }: { binary: boolean } = { binary: false }): this { @@ -338,6 +340,7 @@ export class FileSystem { * * @param {string} src - The source path relative to the `mocks/` directory. * @param {string} dest - The destination path relative to the client directory. + * @returns {this} * @memberof FileSystem */ copyMock(src: string, dest: string): this { diff --git a/packages/cli/src/generate/generators/controller/create-controller.spec.ts b/packages/cli/src/generate/generators/controller/create-controller.spec.ts index 71d7c92e61..9584633fb7 100644 --- a/packages/cli/src/generate/generators/controller/create-controller.spec.ts +++ b/packages/cli/src/generate/generators/controller/create-controller.spec.ts @@ -22,7 +22,7 @@ describe('createController', () => { }); it('should render the empty templates in the proper directory.', () => { - createController({ name: 'test-fooBar', type: 'Empty', register: false }); + createController({ name: 'test-fooBar', register: false }); fs .assertEqual('test-foo-bar.controller.ts', 'controller/test-foo-bar.controller.empty.ts') @@ -30,18 +30,10 @@ describe('createController', () => { .assertEqual('index.ts', 'controller/index.ts'); }); - it('should render the REST templates in the proper directory.', () => { - createController({ name: 'test-fooBar', type: 'REST', register: false }); - - fs - .assertEqual('test-foo-bar.controller.ts', 'controller/test-foo-bar.controller.rest.ts') - .assertEqual('index.ts', 'controller/index.ts'); - }); - it('should not throw an error if index.ts does not exist.', () => { // TODO: replace with "should create index.ts if it does not exist." fs.rmfile('index.ts'); - createController({ name: 'test-fooBar', type: 'Empty', register: false }); + createController({ name: 'test-fooBar', register: false }); }); }); @@ -68,7 +60,7 @@ describe('createController', () => { fs .copyMock('controller/app.controller.no-import.ts', 'app.controller.ts'); - createController({ name: 'test-fooBar', type: 'Empty', register: true }); + createController({ name: 'test-fooBar', register: true }); fs .assertEqual('app.controller.ts', 'controller/app.controller.no-import.ts'); @@ -78,7 +70,7 @@ describe('createController', () => { fs .copyMock('controller/app.controller.controller-import.ts', 'app.controller.ts'); - createController({ name: 'test-fooBar', type: 'Empty', register: true }); + createController({ name: 'test-fooBar', register: true }); fs .assertEqual('app.controller.ts', 'controller/app.controller.controller-import.ts'); @@ -88,7 +80,7 @@ describe('createController', () => { fs .copyMock('controller/app.controller.no-controller-import.ts', 'app.controller.ts'); - createController({ name: 'test-fooBar', type: 'Empty', register: true }); + createController({ name: 'test-fooBar', register: true }); fs .assertEqual('app.controller.ts', 'controller/app.controller.no-controller-import.ts'); @@ -98,7 +90,7 @@ describe('createController', () => { fs .copyMock('controller/app.controller.core-import.ts', 'app.controller.ts'); - createController({ name: 'test-fooBar', type: 'Empty', register: true }); + createController({ name: 'test-fooBar', register: true }); fs .assertEqual('app.controller.ts', 'controller/app.controller.core-import.ts'); @@ -108,7 +100,7 @@ describe('createController', () => { fs .copyMock('controller/app.controller.empty-property.ts', 'app.controller.ts'); - createController({ name: 'test-fooBar', type: 'Empty', register: true }); + createController({ name: 'test-fooBar', register: true }); fs .assertEqual('app.controller.ts', 'controller/app.controller.empty-property.ts'); @@ -118,7 +110,7 @@ describe('createController', () => { fs .copyMock('controller/app.controller.empty-spaced-property.ts', 'app.controller.ts'); - createController({ name: 'test-fooBar', type: 'Empty', register: true }); + createController({ name: 'test-fooBar', register: true }); fs .assertEqual('app.controller.ts', 'controller/app.controller.empty-spaced-property.ts'); @@ -129,22 +121,12 @@ describe('createController', () => { fs .copyMock('controller/app.controller.no-empty-property.ts', 'app.controller.ts'); - createController({ name: 'test-fooBar', type: 'Empty', register: true }); + createController({ name: 'test-fooBar', register: true }); fs .assertEqual('app.controller.ts', 'controller/app.controller.no-empty-property.ts'); }); - it('should update the "subControllers" property with a special URL if the controller is a REST controller.', () => { - fs - .copyMock('controller/app.controller.rest.ts', 'app.controller.ts'); - - createController({ name: 'test-fooBar', type: 'REST', register: true }); - - fs - .assertEqual('app.controller.ts', 'controller/app.controller.rest.ts'); - }); - }); }); diff --git a/packages/cli/src/generate/generators/controller/create-controller.ts b/packages/cli/src/generate/generators/controller/create-controller.ts index 73aa2d8030..f901e15862 100644 --- a/packages/cli/src/generate/generators/controller/create-controller.ts +++ b/packages/cli/src/generate/generators/controller/create-controller.ts @@ -3,9 +3,7 @@ import { FileSystem } from '../../file-system'; import { getNames } from '../../utils'; import { registerController } from './register-controller'; -export type ControllerType = 'Empty'|'REST'; - -export function createController({ name, type, register }: { name: string, type: ControllerType, register: boolean }) { +export function createController({ name, register }: { name: string, register: boolean }) { const fs = new FileSystem(); let root = ''; @@ -17,7 +15,7 @@ export function createController({ name, type, register }: { name: string, type: const names = getNames(name); - const templatePath = `controller/controller.${type.toLowerCase()}.ts`; + const templatePath = `controller/controller.empty.ts`; const specTemplatePath = `controller/controller.spec.empty.ts`; const fileName = `${names.kebabName}.controller.ts`; @@ -29,12 +27,12 @@ export function createController({ name, type, register }: { name: string, type: .cd(root) .render(templatePath, fileName, names) // TODO: the condition "Empty" is not tested. - .renderOnlyIf(type === 'Empty', specTemplatePath, specFileName, names) + .render(specTemplatePath, specFileName, names) .ensureFile('index.ts') .addNamedExportIn('index.ts', className, `./${names.kebabName}.controller`) .cd('..') .modifyOnlyfIf(register, 'app.controller.ts', content => { - const path = `/${names.kebabName}${type === 'REST' ? 's' : ''}`; + const path = `/${names.kebabName}`; return registerController(content, className, path); }); } diff --git a/packages/cli/src/generate/specs/controller/test-foo-bar.controller.rest.ts b/packages/cli/src/generate/specs/controller/test-foo-bar.controller.rest.ts deleted file mode 100644 index e29d0737e7..0000000000 --- a/packages/cli/src/generate/specs/controller/test-foo-bar.controller.rest.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { dependency, RestController } from '@foal/core'; - -import { TestFooBarCollection } from '../services'; - -export class TestFooBarController extends RestController { - @dependency - collection: TestFooBarCollection; -} diff --git a/packages/cli/src/generate/templates/controller/controller.rest.ts b/packages/cli/src/generate/templates/controller/controller.rest.ts deleted file mode 100644 index 29e6591b50..0000000000 --- a/packages/cli/src/generate/templates/controller/controller.rest.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { dependency, RestController } from '@foal/core'; - -import { /* upperFirstCamelName */Collection } from '../services'; - -export class /* upperFirstCamelName */Controller extends RestController { - @dependency - collection: /* upperFirstCamelName */Collection; -} diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 076864acff..9a03164f8d 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -115,7 +115,7 @@ program .action(async (type: GenerateType, name: string, options: { register: boolean }) => { switch (type) { case 'controller': - createController({ name, type: 'Empty', register: options.register }); + createController({ name, register: options.register }); break; case 'entity': createEntity({ name }); From f18fb53fb37b6e6feaae0ec63aaa86c9ad8360ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Sat, 16 May 2020 22:50:43 +0200 Subject: [PATCH 41/92] [CLI] Re-organize legacy code --- .../controller/create-controller.ts | 1 - .../generators/sub-app/create-sub-app.spec.ts | 20 ++++++++++++++---- packages/cli/src/generate/utils/index.ts | 21 ------------------- .../cli/src/run-script/run-script.spec.ts | 10 +++++++-- 4 files changed, 24 insertions(+), 28 deletions(-) diff --git a/packages/cli/src/generate/generators/controller/create-controller.ts b/packages/cli/src/generate/generators/controller/create-controller.ts index f901e15862..c03c5d532c 100644 --- a/packages/cli/src/generate/generators/controller/create-controller.ts +++ b/packages/cli/src/generate/generators/controller/create-controller.ts @@ -26,7 +26,6 @@ export function createController({ name, register }: { name: string, register: b fs .cd(root) .render(templatePath, fileName, names) - // TODO: the condition "Empty" is not tested. .render(specTemplatePath, specFileName, names) .ensureFile('index.ts') .addNamedExportIn('index.ts', className, `./${names.kebabName}.controller`) diff --git a/packages/cli/src/generate/generators/sub-app/create-sub-app.spec.ts b/packages/cli/src/generate/generators/sub-app/create-sub-app.spec.ts index 20623767f6..b804cce333 100644 --- a/packages/cli/src/generate/generators/sub-app/create-sub-app.spec.ts +++ b/packages/cli/src/generate/generators/sub-app/create-sub-app.spec.ts @@ -1,19 +1,31 @@ // std import { strictEqual } from 'assert'; -import { writeFileSync } from 'fs'; +import { existsSync, readFileSync, unlinkSync, writeFileSync } from 'fs'; +import { join } from 'path'; // FoalTS import { mkdirIfDoesNotExist, - readFileFromRoot, - readFileFromTemplatesSpec, rmDirAndFilesIfExist, - rmfileIfExists } from '../../utils'; import { createSubApp } from './create-sub-app'; // TODO: Use FileSystem or remove this command. +function rmfileIfExists(path: string) { + if (existsSync(path)) { + unlinkSync(path); + } +} + +function readFileFromTemplatesSpec(src: string): string { + return readFileSync(join(__dirname, '../../specs', src), 'utf8'); +} + +function readFileFromRoot(src: string): string { + return readFileSync(src, 'utf8'); +} + describe('createSubApp', () => { afterEach(() => { diff --git a/packages/cli/src/generate/utils/index.ts b/packages/cli/src/generate/utils/index.ts index ce2afc494d..a0d27ed433 100644 --- a/packages/cli/src/generate/utils/index.ts +++ b/packages/cli/src/generate/utils/index.ts @@ -1,26 +1,5 @@ -// std -import * as fs from 'fs'; -import { join } from 'path'; - -// FoalTS export { findProjectPath } from './find-project-path'; export { initGitRepo } from './init-git-repo'; export { rmDirAndFilesIfExist } from './rm-dir-and-files-if-exist'; export { mkdirIfDoesNotExist } from './mkdir-if-does-not-exist'; export { getNames } from './get-names'; - -export function rmfileIfExists(path: string) { - if (fs.existsSync(path)) { - fs.unlinkSync(path); - } -} - -// TODO: remove this. -export function readFileFromTemplatesSpec(src: string): string { - return fs.readFileSync(join(__dirname, '../specs', src), 'utf8'); -} - -// TODO: remove this. -export function readFileFromRoot(src: string): string { - return fs.readFileSync(src, 'utf8'); -} diff --git a/packages/cli/src/run-script/run-script.spec.ts b/packages/cli/src/run-script/run-script.spec.ts index 787f37fd67..0e5910f35e 100644 --- a/packages/cli/src/run-script/run-script.spec.ts +++ b/packages/cli/src/run-script/run-script.spec.ts @@ -3,10 +3,16 @@ import { deepStrictEqual, strictEqual } from 'assert'; import { join } from 'path'; // FoalTS -import { existsSync, readFileSync, writeFileSync } from 'fs'; -import { mkdirIfDoesNotExist, rmDirAndFilesIfExist, rmfileIfExists } from '../generate/utils'; +import { existsSync, readFileSync, unlinkSync, writeFileSync } from 'fs'; +import { mkdirIfDoesNotExist, rmDirAndFilesIfExist } from '../generate/utils'; import { runScript } from './run-script'; +function rmfileIfExists(path: string) { + if (existsSync(path)) { + unlinkSync(path); + } +} + describe('runScript', () => { afterEach(() => { From 27f0492a56c4c57ae0245f4e0db0ddabf8ea3e90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Sat, 16 May 2020 22:59:19 +0200 Subject: [PATCH 42/92] [CLI] Improve tests --- packages/cli/src/generate/file-system.ts | 4 +++- .../generators/controller/create-controller.spec.ts | 8 ++++---- .../src/generate/generators/entity/create-entity.spec.ts | 9 +++++---- .../cli/src/generate/generators/hook/create-hook.spec.ts | 9 +++++---- .../src/generate/generators/model/create-model.spec.ts | 5 ++++- .../generate/generators/rest-api/create-rest-api.spec.ts | 2 ++ .../generate/generators/service/create-service.spec.ts | 9 +++++---- 7 files changed, 28 insertions(+), 18 deletions(-) diff --git a/packages/cli/src/generate/file-system.ts b/packages/cli/src/generate/file-system.ts index cb1f800abb..73100f48cb 100644 --- a/packages/cli/src/generate/file-system.ts +++ b/packages/cli/src/generate/file-system.ts @@ -263,12 +263,14 @@ export class FileSystem { * Throws an error if the file or directory does not exist. * * @param {string} path - The path relative to the client directory. + * @returns {this} * @memberof FileSystem */ - assertExists(path: string): void { + assertExists(path: string): this { if (!existsSync(this.parse(path))) { throw new Error(`The file "${path}" does not exist.`); } + return this; } /** diff --git a/packages/cli/src/generate/generators/controller/create-controller.spec.ts b/packages/cli/src/generate/generators/controller/create-controller.spec.ts index 9584633fb7..a6e42e0b09 100644 --- a/packages/cli/src/generate/generators/controller/create-controller.spec.ts +++ b/packages/cli/src/generate/generators/controller/create-controller.spec.ts @@ -30,10 +30,12 @@ describe('createController', () => { .assertEqual('index.ts', 'controller/index.ts'); }); - it('should not throw an error if index.ts does not exist.', () => { - // TODO: replace with "should create index.ts if it does not exist." + it('should create index.ts if it does not exist.', () => { fs.rmfile('index.ts'); + createController({ name: 'test-fooBar', register: false }); + + fs.assertExists('index.ts'); }); }); @@ -54,8 +56,6 @@ describe('createController', () => { .cd('..'); }); - // TODO: refactor these tests and their mock and spec files. - it('should add all the imports if none exists.', () => { fs .copyMock('controller/app.controller.no-import.ts', 'app.controller.ts'); diff --git a/packages/cli/src/generate/generators/entity/create-entity.spec.ts b/packages/cli/src/generate/generators/entity/create-entity.spec.ts index cd8d8507b9..f9d155d8eb 100644 --- a/packages/cli/src/generate/generators/entity/create-entity.spec.ts +++ b/packages/cli/src/generate/generators/entity/create-entity.spec.ts @@ -29,11 +29,12 @@ describe('createEntity', () => { .assertEqual('index.ts', 'entity/index.ts'); }); - it('should not throw an error if index.ts does not exist.', () => { - // TODO: replace with "should create index.ts if it does not exist." - fs - .rmfile('index.ts'); + it('create index.ts if it does not exist.', () => { + fs.rmfile('index.ts'); + createEntity({ name: 'test-fooBar' }); + + fs.assertExists('index.ts'); }); }); diff --git a/packages/cli/src/generate/generators/hook/create-hook.spec.ts b/packages/cli/src/generate/generators/hook/create-hook.spec.ts index de1ce0f928..c3f93b6ab6 100644 --- a/packages/cli/src/generate/generators/hook/create-hook.spec.ts +++ b/packages/cli/src/generate/generators/hook/create-hook.spec.ts @@ -29,11 +29,12 @@ describe('createHook', () => { .assertEqual('index.ts', 'hook/index.ts'); }); - it('should not throw an error if index.ts does not exist.', () => { - // TODO: replace with "should create index.ts if it does not exist." - fs - .rmfile('index.Ts'); + it('should create index.ts if it does not exist.', () => { + fs.rmfile('index.ts'); + createHook({ name: 'test-fooBar' }); + + fs.assertExists('index.ts'); }); }); diff --git a/packages/cli/src/generate/generators/model/create-model.spec.ts b/packages/cli/src/generate/generators/model/create-model.spec.ts index bded2446f3..24150e7c18 100644 --- a/packages/cli/src/generate/generators/model/create-model.spec.ts +++ b/packages/cli/src/generate/generators/model/create-model.spec.ts @@ -29,9 +29,12 @@ describe('createModel', () => { .assertEqual('index.ts', 'model/index.ts'); }); - it('should not throw an error if index.ts does not exist.', () => { + it('should create index.ts if it does not exist.', () => { fs.rmfile('index.ts'); + createModel({ name: 'test-fooBar' }); + + fs.assertExists('index.ts'); }); }); diff --git a/packages/cli/src/generate/generators/rest-api/create-rest-api.spec.ts b/packages/cli/src/generate/generators/rest-api/create-rest-api.spec.ts index c0dca118a7..ca374127df 100644 --- a/packages/cli/src/generate/generators/rest-api/create-rest-api.spec.ts +++ b/packages/cli/src/generate/generators/rest-api/create-rest-api.spec.ts @@ -2,6 +2,8 @@ import { FileSystem } from '../../file-system'; import { createRestApi } from './create-rest-api'; +// TODO: add tests like "should create index.ts if it does not exist." + describe('createRestApi', () => { const fs = new FileSystem(); diff --git a/packages/cli/src/generate/generators/service/create-service.spec.ts b/packages/cli/src/generate/generators/service/create-service.spec.ts index 0b519abc36..6587a07c52 100644 --- a/packages/cli/src/generate/generators/service/create-service.spec.ts +++ b/packages/cli/src/generate/generators/service/create-service.spec.ts @@ -29,11 +29,12 @@ describe('createService', () => { .assertEqual('index.ts', 'service/index.ts'); }); - it('should not throw an error if index.ts does not exist.', () => { - // TODO: replace with "should create index.ts if it does not exist." - fs - .rmfile('index.ts'); + it('should should create index.ts if it does not exist.', () => { + fs.rmfile('index.ts'); + createService({ name: 'test-fooBar' }); + + fs.assertExists('index.ts'); }); }); From 964047238af2619ef5a11ea86e59e9dae11714fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Sat, 16 May 2020 23:17:04 +0200 Subject: [PATCH 43/92] [CLI] Fix command logs --- packages/cli/src/generate/file-system.ts | 40 ++++++++++++++++++- .../generators/angular/connect-angular.ts | 6 +-- .../src/generate/generators/app/create-app.ts | 13 +++--- .../generators/react/connect-react.ts | 4 +- .../generators/rest-api/create-rest-api.ts | 2 +- .../generators/script/create-script.ts | 2 +- .../generate/generators/vue/connect-vue.ts | 4 +- 7 files changed, 55 insertions(+), 16 deletions(-) diff --git a/packages/cli/src/generate/file-system.ts b/packages/cli/src/generate/file-system.ts index 73100f48cb..e6a22e6fc7 100644 --- a/packages/cli/src/generate/file-system.ts +++ b/packages/cli/src/generate/file-system.ts @@ -14,7 +14,7 @@ import { import { dirname, join } from 'path'; // 3p -import { green, red } from 'colors/safe'; +import { cyan, green, red } from 'colors/safe'; function rmDirAndFiles(path: string) { const files = readdirSync(path); @@ -43,6 +43,18 @@ export class FileSystem { currentDir = ''; private readonly testDir = 'test-generators'; + private logs = true; + + /** + * Do not show create and update logs. + * + * @returns {this} + * @memberof FileSystem + */ + hideLogs(): this { + this.logs = false; + return this; + } /** * Change the current working directory. @@ -111,6 +123,7 @@ export class FileSystem { */ ensureFile(path: string): this { if (!existsSync(this.parse(path))) { + this.logCreate(path); writeFileSync(this.parse(path), '', 'utf8'); } return this; @@ -129,6 +142,7 @@ export class FileSystem { if (!existsSync(templatePath)) { throw new Error(`The template "${src}" does not exist.`); } + this.logCreate(dest); copyFileSync( templatePath, this.parse(dest) @@ -170,6 +184,7 @@ export class FileSystem { for (const key in locals) { content = content.split(`/* ${key} */`).join(locals[key]); } + this.logCreate(dest); writeFileSync(this.parse(dest), content, 'utf8'); return this; } @@ -201,6 +216,7 @@ export class FileSystem { */ modify(path: string, callback: (content: string) => string): this { const content = readFileSync(this.parse(path), 'utf8'); + this.logUpdate(path); writeFileSync(this.parse(path), callback(content), 'utf8'); return this; } @@ -367,11 +383,31 @@ export class FileSystem { unlinkSync(this.parse(path)); } + private isTestingEnvironment(): boolean { + return process.env.P1Z7kEbSUUPMxF8GqPwD8Gx_FOAL_CLI_TEST === 'true'; + } + private parse(path: string) { - if (process.env.P1Z7kEbSUUPMxF8GqPwD8Gx_FOAL_CLI_TEST === 'true') { + if (this.isTestingEnvironment()) { return join(this.testDir, this.currentDir, path); } return join(this.currentDir, path); } + private logCreate(path: string) { + path = join(this.currentDir, path); + // && !this.options.noLogs + if (!this.isTestingEnvironment() && this.logs) { + console.log(`${green('CREATE')} ${path}`); + } + } + + private logUpdate(path: string) { + // && !this.options.noLogs + path = join(this.currentDir, path); + if (!this.isTestingEnvironment() && this.logs) { + console.log(`${cyan('UPDATE')} ${path}`); + } + } + } diff --git a/packages/cli/src/generate/generators/angular/connect-angular.ts b/packages/cli/src/generate/generators/angular/connect-angular.ts index 7e5af437db..690ceb5334 100644 --- a/packages/cli/src/generate/generators/angular/connect-angular.ts +++ b/packages/cli/src/generate/generators/angular/connect-angular.ts @@ -7,21 +7,21 @@ export function connectAngular(path: string) { const fs = new FileSystem(); if (!fs.exists(path)) { - if (process.env.NODE_ENV !== 'test') { + if (process.env.P1Z7kEbSUUPMxF8GqPwD8Gx_FOAL_CLI_TEST !== 'true') { console.log(red(` The directory ${path} does not exist.`)); } return; } if (!fs.exists(join(path, 'angular.json'))) { - if (process.env.NODE_ENV !== 'test') { + if (process.env.P1Z7kEbSUUPMxF8GqPwD8Gx_FOAL_CLI_TEST !== 'true') { console.log(red(` The directory ${path} is not an Angular project (missing angular.json).`)); } return; } if (!fs.exists(join(path, 'package.json'))) { - if (process.env.NODE_ENV !== 'test') { + if (process.env.P1Z7kEbSUUPMxF8GqPwD8Gx_FOAL_CLI_TEST !== 'true') { console.log(red(` The directory ${path} is not an Angular project (missing package.json).`)); } return; diff --git a/packages/cli/src/generate/generators/app/create-app.ts b/packages/cli/src/generate/generators/app/create-app.ts index a39a906253..aaf0bfa9f5 100644 --- a/packages/cli/src/generate/generators/app/create-app.ts +++ b/packages/cli/src/generate/generators/app/create-app.ts @@ -21,7 +21,7 @@ function isYarnInstalled() { } function log(msg: string) { - if (process.env.NODE_ENV !== 'test') { + if (process.env.P1Z7kEbSUUPMxF8GqPwD8Gx_FOAL_CLI_TEST !== 'true') { console.log(msg); } } @@ -31,7 +31,7 @@ export async function createApp({ name, autoInstall, initRepo, mongodb = false, yaml?: boolean }) { const names = getNames(name); - if (process.env.NODE_ENV !== 'test') { + if (process.env.P1Z7kEbSUUPMxF8GqPwD8Gx_FOAL_CLI_TEST !== 'true') { console.log(cyan( `==================================================================== @@ -53,9 +53,11 @@ export async function createApp({ name, autoInstall, initRepo, mongodb = false, const fs = new FileSystem(); if (fs.exists(names.kebabName)) { - console.log( - red(`\n The target directory "${names.kebabName}" already exists. Please remove it before proceeding.`) - ); + if (process.env.P1Z7kEbSUUPMxF8GqPwD8Gx_FOAL_CLI_TEST !== 'true') { + console.log( + red(`\n The target directory "${names.kebabName}" already exists. Please remove it before proceeding.`) + ); + } return; } @@ -67,6 +69,7 @@ export async function createApp({ name, autoInstall, initRepo, mongodb = false, // TODO: noLogs fs + .hideLogs() .copy('app/gitignore', '.gitignore') .copyOnlyIf(!mongodb, 'app/ormconfig.js', 'ormconfig.js') .renderOnlyIf(!mongodb && !yaml, 'app/package.json', 'package.json', locals) diff --git a/packages/cli/src/generate/generators/react/connect-react.ts b/packages/cli/src/generate/generators/react/connect-react.ts index 1306209160..6b632038be 100644 --- a/packages/cli/src/generate/generators/react/connect-react.ts +++ b/packages/cli/src/generate/generators/react/connect-react.ts @@ -7,14 +7,14 @@ export function connectReact(path: string) { const fs = new FileSystem(); if (!fs.exists(path)) { - if (process.env.NODE_ENV !== 'test') { + if (process.env.P1Z7kEbSUUPMxF8GqPwD8Gx_FOAL_CLI_TEST !== 'true') { console.log(red(` The directory ${path} does not exist.`)); } return; } if (!fs.exists(join(path, 'package.json'))) { - if (process.env.NODE_ENV !== 'test') { + if (process.env.P1Z7kEbSUUPMxF8GqPwD8Gx_FOAL_CLI_TEST !== 'true') { console.log(red(` The directory ${path} is not a React project (missing package.json).`)); } return; diff --git a/packages/cli/src/generate/generators/rest-api/create-rest-api.ts b/packages/cli/src/generate/generators/rest-api/create-rest-api.ts index 5c9aefc6c1..a70767fae3 100644 --- a/packages/cli/src/generate/generators/rest-api/create-rest-api.ts +++ b/packages/cli/src/generate/generators/rest-api/create-rest-api.ts @@ -62,7 +62,7 @@ export function createRestApi({ name, register }: { name: string, register: bool return registerController(content, `${names.upperFirstCamelName}Controller`, `/${names.kebabName}s`); }); - if (process.env.NODE_ENV !== 'test') { + if (process.env.P1Z7kEbSUUPMxF8GqPwD8Gx_FOAL_CLI_TEST !== 'true') { console.log( `\n${underline('Next steps:')} Complete ${names.upperFirstCamelName} (${names.kebabName}.entity)` + ` and ${names.camelName}Schema (${names.kebabName}.controller).` diff --git a/packages/cli/src/generate/generators/script/create-script.ts b/packages/cli/src/generate/generators/script/create-script.ts index 8c0dfdd1c7..be48c97a28 100644 --- a/packages/cli/src/generate/generators/script/create-script.ts +++ b/packages/cli/src/generate/generators/script/create-script.ts @@ -21,7 +21,7 @@ export function createScript({ name }: { name: string }) { const scriptPath = join(root, './src/scripts/'); if (!existsSync(scriptPath)) { - if (process.env.NODE_ENV !== 'test') { + if (process.env.P1Z7kEbSUUPMxF8GqPwD8Gx_FOAL_CLI_TEST !== 'true') { console.log(red(`\n This directory is not a Foal project (${scriptPath} does not exist).\n`)); } return; diff --git a/packages/cli/src/generate/generators/vue/connect-vue.ts b/packages/cli/src/generate/generators/vue/connect-vue.ts index 5b6721b2c1..b018ec4137 100644 --- a/packages/cli/src/generate/generators/vue/connect-vue.ts +++ b/packages/cli/src/generate/generators/vue/connect-vue.ts @@ -7,14 +7,14 @@ export function connectVue(path: string) { const fs = new FileSystem(); if (!fs.exists(path)) { - if (process.env.NODE_ENV !== 'test') { + if (process.env.P1Z7kEbSUUPMxF8GqPwD8Gx_FOAL_CLI_TEST !== 'true') { console.log(red(` The directory ${path} does not exist.`)); } return; } if (!fs.exists(join(path, 'package.json'))) { - if (process.env.NODE_ENV !== 'test') { + if (process.env.P1Z7kEbSUUPMxF8GqPwD8Gx_FOAL_CLI_TEST !== 'true') { console.log(red(` The directory ${path} is not a Vue project (missing package.json).`)); } return; From 39bee759bd14be9773a9e4eade24fc537bfc5c13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Sat, 16 May 2020 23:23:10 +0200 Subject: [PATCH 44/92] [CLI] Check file existence in fs.modify --- packages/cli/src/generate/file-system.spec.ts | 9 +++++++++ packages/cli/src/generate/file-system.ts | 3 +++ 2 files changed, 12 insertions(+) diff --git a/packages/cli/src/generate/file-system.spec.ts b/packages/cli/src/generate/file-system.spec.ts index 1725abe01c..a665f9b37c 100644 --- a/packages/cli/src/generate/file-system.spec.ts +++ b/packages/cli/src/generate/file-system.spec.ts @@ -334,6 +334,15 @@ describe('FileSystem', () => { ); }); + it('should throw an error if the file does not exist.', () => { + try { + fs.modify('test-file-system/foobar.txt', content => content); + throw new Error('An error should have been thrown'); + } catch (error) { + strictEqual(error.message, 'Impossible to modify "test-file-system/foobar.txt": the file does not exist.'); + } + }); + }); describe('has a "modifyOnlyIf" method that should', () => { diff --git a/packages/cli/src/generate/file-system.ts b/packages/cli/src/generate/file-system.ts index e6a22e6fc7..65e71a9ccf 100644 --- a/packages/cli/src/generate/file-system.ts +++ b/packages/cli/src/generate/file-system.ts @@ -215,6 +215,9 @@ export class FileSystem { * @memberof FileSystem */ modify(path: string, callback: (content: string) => string): this { + if (!existsSync(this.parse(path))) { + throw new Error(`Impossible to modify "${path}": the file does not exist.`); + } const content = readFileSync(this.parse(path), 'utf8'); this.logUpdate(path); writeFileSync(this.parse(path), callback(content), 'utf8'); From 73f7d896e55c48da35bb14a39b2b69f87ae87f82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Sun, 17 May 2020 14:54:54 +0200 Subject: [PATCH 45/92] [CLI][Bug] g rest-api: create index.ts if no exist --- .../src/generate/generators/app/create-app.ts | 1 - .../generators/controller/create-controller.ts | 7 ++----- .../generate/generators/controller/index.ts | 2 +- .../rest-api/create-rest-api.spec.ts | 18 ++++++++++++++++++ .../generators/rest-api/create-rest-api.ts | 2 ++ packages/cli/src/test.ts | 4 ++++ 6 files changed, 27 insertions(+), 7 deletions(-) diff --git a/packages/cli/src/generate/generators/app/create-app.ts b/packages/cli/src/generate/generators/app/create-app.ts index aaf0bfa9f5..7f6ad83d4f 100644 --- a/packages/cli/src/generate/generators/app/create-app.ts +++ b/packages/cli/src/generate/generators/app/create-app.ts @@ -67,7 +67,6 @@ export async function createApp({ name, autoInstall, initRepo, mongodb = false, log(' 📂 Creating files...'); - // TODO: noLogs fs .hideLogs() .copy('app/gitignore', '.gitignore') diff --git a/packages/cli/src/generate/generators/controller/create-controller.ts b/packages/cli/src/generate/generators/controller/create-controller.ts index c03c5d532c..0885d787e1 100644 --- a/packages/cli/src/generate/generators/controller/create-controller.ts +++ b/packages/cli/src/generate/generators/controller/create-controller.ts @@ -15,9 +15,6 @@ export function createController({ name, register }: { name: string, register: b const names = getNames(name); - const templatePath = `controller/controller.empty.ts`; - const specTemplatePath = `controller/controller.spec.empty.ts`; - const fileName = `${names.kebabName}.controller.ts`; const specFileName = `${names.kebabName}.controller.spec.ts`; @@ -25,8 +22,8 @@ export function createController({ name, register }: { name: string, register: b fs .cd(root) - .render(templatePath, fileName, names) - .render(specTemplatePath, specFileName, names) + .render('controller/controller.empty.ts', fileName, names) + .render('controller/controller.spec.empty.ts', specFileName, names) .ensureFile('index.ts') .addNamedExportIn('index.ts', className, `./${names.kebabName}.controller`) .cd('..') diff --git a/packages/cli/src/generate/generators/controller/index.ts b/packages/cli/src/generate/generators/controller/index.ts index 783d132ab3..9ee1a7bec9 100644 --- a/packages/cli/src/generate/generators/controller/index.ts +++ b/packages/cli/src/generate/generators/controller/index.ts @@ -1 +1 @@ -export { ControllerType, createController } from './create-controller'; +export { createController } from './create-controller'; diff --git a/packages/cli/src/generate/generators/rest-api/create-rest-api.spec.ts b/packages/cli/src/generate/generators/rest-api/create-rest-api.spec.ts index ca374127df..483edc33fe 100644 --- a/packages/cli/src/generate/generators/rest-api/create-rest-api.spec.ts +++ b/packages/cli/src/generate/generators/rest-api/create-rest-api.spec.ts @@ -44,6 +44,16 @@ describe('createRestApi', () => { .assertEqual('index.ts', 'rest-api/index.controllers.ts'); }); + it('should create the index.ts if they do not exist.', () => { + fs.rmfile('entities/index.ts'); + fs.rmfile('controllers/index.ts'); + + createRestApi({ name: 'test-fooBar', register: false }); + + fs.assertExists('entities/index.ts'); + fs.assertExists('controllers/index.ts'); + }); + }); describe(`when the directories ${root}entities/ and ${root}controllers/ exist and "register" is true.`, () => { @@ -147,6 +157,14 @@ describe('createRestApi', () => { .assertEqual('index.ts', 'rest-api/index.current-dir.ts'); }); + it('should create index.ts if it does not exist.', () => { + fs.rmfile('index.ts'); + + createRestApi({ name: 'test-fooBar', register: false }); + + fs.assertExists('index.ts'); + }); + }); }); diff --git a/packages/cli/src/generate/generators/rest-api/create-rest-api.ts b/packages/cli/src/generate/generators/rest-api/create-rest-api.ts index a70767fae3..f77595f917 100644 --- a/packages/cli/src/generate/generators/rest-api/create-rest-api.ts +++ b/packages/cli/src/generate/generators/rest-api/create-rest-api.ts @@ -40,6 +40,7 @@ export function createRestApi({ name, register }: { name: string, register: bool fs .cd(entityRoot) .render('rest-api/entity.ts', `${names.kebabName}.entity.ts`, names) + .ensureFile('index.ts') .addNamedExportIn('index.ts', names.upperFirstCamelName, `./${names.kebabName}.entity`); fs.currentDir = ''; @@ -56,6 +57,7 @@ export function createRestApi({ name, register }: { name: string, register: bool `${names.kebabName}.controller.spec.ts`, names, ) + .ensureFile('index.ts') .addNamedExportIn('index.ts', `${names.upperFirstCamelName}Controller`, `./${names.kebabName}.controller`) .cd('..') .modifyOnlyfIf(register, 'app.controller.ts', content => { diff --git a/packages/cli/src/test.ts b/packages/cli/src/test.ts index 1a4d2d4a33..51ab3a3ae3 100644 --- a/packages/cli/src/test.ts +++ b/packages/cli/src/test.ts @@ -1 +1,5 @@ +// The environment variable NODE_ENV is not used on purpose because +// one could have this variable defined globally on their host. +// Here we are sure that the name "P1Z7kEbSUUPMxF8GqPwD8Gx_FOAL_CLI_TEST" +// is not used by anyone. process.env.P1Z7kEbSUUPMxF8GqPwD8Gx_FOAL_CLI_TEST = 'true'; From 74f151d12dde0a4e3cd498427af810504245ca28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Sun, 17 May 2020 15:04:34 +0200 Subject: [PATCH 46/92] [CLI] Make tests pass on Windows --- packages/cli/src/generate/file-system.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/generate/file-system.spec.ts b/packages/cli/src/generate/file-system.spec.ts index a665f9b37c..0a35f2566c 100644 --- a/packages/cli/src/generate/file-system.spec.ts +++ b/packages/cli/src/generate/file-system.spec.ts @@ -38,9 +38,9 @@ describe('FileSystem', () => { it('should change the current directory.', () => { strictEqual(fs.currentDir, ''); fs.cd('foobar/foo'); - strictEqual(fs.currentDir, 'foobar/foo'); + strictEqual(fs.currentDir.replace(/\\/g, '/'), 'foobar/foo'); fs.cd('../bar'); - strictEqual(fs.currentDir, 'foobar/bar'); + strictEqual(fs.currentDir.replace(/\\/g, '/'), 'foobar/bar'); }); }); From e1ce61b05687176546c5718fafe59564dac7ac72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Mon, 18 May 2020 18:45:47 +0200 Subject: [PATCH 47/92] [CLI]Support subdir in "foal g controller|service" --- .../controller/create-controller.spec.ts | 18 ++++++++++++++++++ .../generators/controller/create-controller.ts | 9 +++++++-- .../generators/service/create-service.spec.ts | 7 +++++++ .../generators/service/create-service.ts | 6 +++++- 4 files changed, 37 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/generate/generators/controller/create-controller.spec.ts b/packages/cli/src/generate/generators/controller/create-controller.spec.ts index a6e42e0b09..47d5b55132 100644 --- a/packages/cli/src/generate/generators/controller/create-controller.spec.ts +++ b/packages/cli/src/generate/generators/controller/create-controller.spec.ts @@ -30,6 +30,13 @@ describe('createController', () => { .assertEqual('index.ts', 'controller/index.ts'); }); + it('should create the directory if it does not exist.', () => { + createController({ name: 'barfoo/hello/test-fooBar', register: false }); + + fs + .assertExists('barfoo/hello/test-foo-bar.controller.ts'); + }); + it('should create index.ts if it does not exist.', () => { fs.rmfile('index.ts'); @@ -66,6 +73,17 @@ describe('createController', () => { .assertEqual('app.controller.ts', 'controller/app.controller.no-import.ts'); }); + it('should add all the imports if none exists (subdir).', () => { + fs + .ensureDir('controllers/barfoo') + .copyMock('controller/app.controller.no-import.ts', 'controllers/barfoo/hello.controller.ts'); + + createController({ name: 'barfoo/hello/test-fooBar', register: true }); + + fs + .assertEqual('controllers/barfoo/hello.controller.ts', 'controller/app.controller.no-import.ts'); + }); + it('should update the "subControllers" import in src/app/app.controller.ts if it exists.', () => { fs .copyMock('controller/app.controller.controller-import.ts', 'app.controller.ts'); diff --git a/packages/cli/src/generate/generators/controller/create-controller.ts b/packages/cli/src/generate/generators/controller/create-controller.ts index 0885d787e1..23be7c0be2 100644 --- a/packages/cli/src/generate/generators/controller/create-controller.ts +++ b/packages/cli/src/generate/generators/controller/create-controller.ts @@ -1,4 +1,5 @@ // FoalTS +import { basename, dirname, join } from 'path'; import { FileSystem } from '../../file-system'; import { getNames } from '../../utils'; import { registerController } from './register-controller'; @@ -13,7 +14,9 @@ export function createController({ name, register }: { name: string, register: b root = 'controllers'; } - const names = getNames(name); + const names = getNames(basename(name)); + const subdir = dirname(name); + const parentControllerPath = `${subdir === '.' ? 'app' : basename(subdir)}.controller.ts`; const fileName = `${names.kebabName}.controller.ts`; const specFileName = `${names.kebabName}.controller.spec.ts`; @@ -22,12 +25,14 @@ export function createController({ name, register }: { name: string, register: b fs .cd(root) + .ensureDir(subdir) + .cd(subdir) .render('controller/controller.empty.ts', fileName, names) .render('controller/controller.spec.empty.ts', specFileName, names) .ensureFile('index.ts') .addNamedExportIn('index.ts', className, `./${names.kebabName}.controller`) .cd('..') - .modifyOnlyfIf(register, 'app.controller.ts', content => { + .modifyOnlyfIf(register, parentControllerPath, content => { const path = `/${names.kebabName}`; return registerController(content, className, path); }); diff --git a/packages/cli/src/generate/generators/service/create-service.spec.ts b/packages/cli/src/generate/generators/service/create-service.spec.ts index 6587a07c52..b2fa3915d0 100644 --- a/packages/cli/src/generate/generators/service/create-service.spec.ts +++ b/packages/cli/src/generate/generators/service/create-service.spec.ts @@ -29,6 +29,13 @@ describe('createService', () => { .assertEqual('index.ts', 'service/index.ts'); }); + it('should create the directory if it does not exist.', () => { + createService({ name: 'barfoo/hello/test-fooBar' }); + + fs + .assertExists('barfoo/hello/test-foo-bar.service.ts'); + }); + it('should should create index.ts if it does not exist.', () => { fs.rmfile('index.ts'); diff --git a/packages/cli/src/generate/generators/service/create-service.ts b/packages/cli/src/generate/generators/service/create-service.ts index 74b19927b9..b337aaf57c 100644 --- a/packages/cli/src/generate/generators/service/create-service.ts +++ b/packages/cli/src/generate/generators/service/create-service.ts @@ -1,4 +1,5 @@ // FoalTS +import { basename, dirname } from 'path'; import { FileSystem } from '../../file-system'; import { getNames } from '../../utils'; @@ -12,10 +13,13 @@ export function createService({ name }: { name: string }) { root = 'services'; } - const names = getNames(name); + const names = getNames(basename(name)); + const subdir = dirname(name); fs .cd(root) + .ensureDir(subdir) + .cd(subdir) .render('service/service.empty.ts', `${names.kebabName}.service.ts`, names) .ensureFile('index.ts') .addNamedExportIn('index.ts', names.upperFirstCamelName, `./${names.kebabName}.service`); From ea9af94caa9a405981940c041feec10b9f326349 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Mon, 18 May 2020 20:49:09 +0200 Subject: [PATCH 48/92] [CLI] Fix bug on required name on g vscode-config --- packages/cli/src/index.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 9a03164f8d..177ebf52f4 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -103,7 +103,7 @@ const generateTypes: GenerateType[] = [ ]; program - .command('generate ') + .command('generate [name]') .description('Generate and/or modify files.') .option('-r, --register', 'Register the controller into app.controller.ts (only available if type=controller)', false) .alias('g') @@ -113,6 +113,12 @@ program generateTypes.forEach(t => console.log(` ${t}`)); }) .action(async (type: GenerateType, name: string, options: { register: boolean }) => { + if (!name && type !== 'vscode-config') { + console.error(); + console.error(red(`Argument "name" is required when creating a ${type}. Please provide one.`)); + console.error(); + return; + } switch (type) { case 'controller': createController({ name, register: options.register }); From aaf92db11ab909d96b096527c1bdb5df3ac56c2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Tue, 19 May 2020 08:32:58 +0200 Subject: [PATCH 49/92] [CLI] Rename mocks/ to fixtures/ --- .github/CONTRIBUTING.MD | 2 +- packages/cli/src/generate/file-system.spec.ts | 22 +++++++++---------- packages/cli/src/generate/file-system.ts | 14 ++++++------ .../{mocks => fixtures}/angular/angular.json | 0 .../{mocks => fixtures}/angular/package.json | 0 .../app.controller.controller-import.ts | 0 .../controller/app.controller.core-import.ts | 0 .../app.controller.empty-property.ts | 0 .../app.controller.empty-spaced-property.ts | 0 .../app.controller.no-controller-import.ts | 0 .../app.controller.no-empty-property.ts | 0 .../controller/app.controller.no-import.ts | 0 .../controller/app.controller.rest.ts | 0 .../{mocks => fixtures}/controller/index.ts | 0 .../{mocks => fixtures}/entity/index.ts | 0 .../{mocks => fixtures}/hook/index.ts | 0 .../{mocks => fixtures}/model/index.ts | 0 .../{mocks => fixtures}/react/package.json | 0 .../app.controller.controller-import.ts | 0 .../rest-api/app.controller.core-import.ts | 0 .../rest-api/app.controller.empty-property.ts | 0 .../app.controller.empty-spaced-property.ts | 0 .../app.controller.no-controller-import.ts | 0 .../app.controller.no-empty-property.ts | 0 .../rest-api/index.controllers.ts | 0 .../rest-api/index.current-dir.ts | 0 .../rest-api/index.entities.ts | 0 .../{mocks => fixtures}/service/index.ts | 0 .../{mocks => fixtures}/vue/package.json | 0 .../angular/connect-angular.spec.ts | 12 +++++----- .../controller/create-controller.spec.ts | 20 ++++++++--------- .../generators/entity/create-entity.spec.ts | 2 +- .../generators/hook/create-hook.spec.ts | 2 +- .../generators/model/create-model.spec.ts | 2 +- .../generators/react/connect-react.spec.ts | 2 +- .../rest-api/create-rest-api.spec.ts | 22 +++++++++---------- .../generators/service/create-service.spec.ts | 2 +- .../generators/vue/connect-vue.spec.ts | 2 +- tslint.json | 2 +- 39 files changed, 53 insertions(+), 53 deletions(-) rename packages/cli/src/generate/{mocks => fixtures}/angular/angular.json (100%) rename packages/cli/src/generate/{mocks => fixtures}/angular/package.json (100%) rename packages/cli/src/generate/{mocks => fixtures}/controller/app.controller.controller-import.ts (100%) rename packages/cli/src/generate/{mocks => fixtures}/controller/app.controller.core-import.ts (100%) rename packages/cli/src/generate/{mocks => fixtures}/controller/app.controller.empty-property.ts (100%) rename packages/cli/src/generate/{mocks => fixtures}/controller/app.controller.empty-spaced-property.ts (100%) rename packages/cli/src/generate/{mocks => fixtures}/controller/app.controller.no-controller-import.ts (100%) rename packages/cli/src/generate/{mocks => fixtures}/controller/app.controller.no-empty-property.ts (100%) rename packages/cli/src/generate/{mocks => fixtures}/controller/app.controller.no-import.ts (100%) rename packages/cli/src/generate/{mocks => fixtures}/controller/app.controller.rest.ts (100%) rename packages/cli/src/generate/{mocks => fixtures}/controller/index.ts (100%) rename packages/cli/src/generate/{mocks => fixtures}/entity/index.ts (100%) rename packages/cli/src/generate/{mocks => fixtures}/hook/index.ts (100%) rename packages/cli/src/generate/{mocks => fixtures}/model/index.ts (100%) rename packages/cli/src/generate/{mocks => fixtures}/react/package.json (100%) rename packages/cli/src/generate/{mocks => fixtures}/rest-api/app.controller.controller-import.ts (100%) rename packages/cli/src/generate/{mocks => fixtures}/rest-api/app.controller.core-import.ts (100%) rename packages/cli/src/generate/{mocks => fixtures}/rest-api/app.controller.empty-property.ts (100%) rename packages/cli/src/generate/{mocks => fixtures}/rest-api/app.controller.empty-spaced-property.ts (100%) rename packages/cli/src/generate/{mocks => fixtures}/rest-api/app.controller.no-controller-import.ts (100%) rename packages/cli/src/generate/{mocks => fixtures}/rest-api/app.controller.no-empty-property.ts (100%) rename packages/cli/src/generate/{mocks => fixtures}/rest-api/index.controllers.ts (100%) rename packages/cli/src/generate/{mocks => fixtures}/rest-api/index.current-dir.ts (100%) rename packages/cli/src/generate/{mocks => fixtures}/rest-api/index.entities.ts (100%) rename packages/cli/src/generate/{mocks => fixtures}/service/index.ts (100%) rename packages/cli/src/generate/{mocks => fixtures}/vue/package.json (100%) diff --git a/.github/CONTRIBUTING.MD b/.github/CONTRIBUTING.MD index 2546e67df0..c44b99f455 100644 --- a/.github/CONTRIBUTING.MD +++ b/.github/CONTRIBUTING.MD @@ -127,7 +127,7 @@ Here is the list of its sub-directories: | Directory | Description | | --- | --- | | generators | Contains the code which renders the templates or updates the files | -| mocks | Contains some pieces of code used to test the file "updaters" | +| fixtures | Contains some pieces of code used to test the file "updaters" | | specs | Defines how the generated files should look like in different scenarios (specifications) | | templates | Contains the actual templates used to generate the files | | utils | Contains some helpers shared by all the generators | diff --git a/packages/cli/src/generate/file-system.spec.ts b/packages/cli/src/generate/file-system.spec.ts index 0a35f2566c..eee60233e2 100644 --- a/packages/cli/src/generate/file-system.spec.ts +++ b/packages/cli/src/generate/file-system.spec.ts @@ -622,27 +622,27 @@ describe('FileSystem', () => { }); - describe('has a "copyMock" method that', () => { + describe('has a "copyFixture" method that', () => { - const mockDir = join(__dirname, 'mocks/test-file-system'); - const mockPath = join(__dirname, 'mocks/test-file-system/tpl.txt'); + const fixtureDir = join(__dirname, 'fixtures/test-file-system'); + const fixturePath = join(__dirname, 'fixtures/test-file-system/tpl.txt'); beforeEach(() => { mkdir('test-generators'); - mkdir(mockDir); - writeFileSync(mockPath, 'hello', 'utf8'); + mkdir(fixtureDir); + writeFileSync(fixturePath, 'hello', 'utf8'); }); afterEach(() => { - rmfile(mockPath); - rmdir(mockDir); + rmfile(fixturePath); + rmdir(fixtureDir); rmfile('test-generators/hello.txt'); rmdir('test-generators'); }); - it('should copy the file from the `mocks` directory.', () => { - fs.copyMock('test-file-system/tpl.txt', 'hello.txt'); + it('should copy the file from the `fixtures` directory.', () => { + fs.copyFixture('test-file-system/tpl.txt', 'hello.txt'); if (!existsSync('test-generators/hello.txt')) { throw new Error('The file "test-generators/hello.txt" does not exist.'); } @@ -654,10 +654,10 @@ describe('FileSystem', () => { it('should throw an error if the file does not exist.', () => { try { - fs.copyMock('test-file-system/foobar.txt', 'hello.txt'); + fs.copyFixture('test-file-system/foobar.txt', 'hello.txt'); throw new Error('An error should have been thrown'); } catch (error) { - strictEqual(error.message, 'The mock file "test-file-system/foobar.txt" does not exist.'); + strictEqual(error.message, 'The fixture file "test-file-system/foobar.txt" does not exist.'); } }); diff --git a/packages/cli/src/generate/file-system.ts b/packages/cli/src/generate/file-system.ts index 65e71a9ccf..810247f6dc 100644 --- a/packages/cli/src/generate/file-system.ts +++ b/packages/cli/src/generate/file-system.ts @@ -357,20 +357,20 @@ export class FileSystem { } /** - * Copies a file from the `mocks` directory. + * Copies a file from the `fixtures` directory. * - * @param {string} src - The source path relative to the `mocks/` directory. + * @param {string} src - The source path relative to the `fixtures/` directory. * @param {string} dest - The destination path relative to the client directory. * @returns {this} * @memberof FileSystem */ - copyMock(src: string, dest: string): this { - const mockPath = join(__dirname, 'mocks', src); - if (!existsSync(mockPath)) { - throw new Error(`The mock file "${src}" does not exist.`); + copyFixture(src: string, dest: string): this { + const fixturePath = join(__dirname, 'fixtures', src); + if (!existsSync(fixturePath)) { + throw new Error(`The fixture file "${src}" does not exist.`); } copyFileSync( - mockPath, + fixturePath, this.parse(dest) ); return this; diff --git a/packages/cli/src/generate/mocks/angular/angular.json b/packages/cli/src/generate/fixtures/angular/angular.json similarity index 100% rename from packages/cli/src/generate/mocks/angular/angular.json rename to packages/cli/src/generate/fixtures/angular/angular.json diff --git a/packages/cli/src/generate/mocks/angular/package.json b/packages/cli/src/generate/fixtures/angular/package.json similarity index 100% rename from packages/cli/src/generate/mocks/angular/package.json rename to packages/cli/src/generate/fixtures/angular/package.json diff --git a/packages/cli/src/generate/mocks/controller/app.controller.controller-import.ts b/packages/cli/src/generate/fixtures/controller/app.controller.controller-import.ts similarity index 100% rename from packages/cli/src/generate/mocks/controller/app.controller.controller-import.ts rename to packages/cli/src/generate/fixtures/controller/app.controller.controller-import.ts diff --git a/packages/cli/src/generate/mocks/controller/app.controller.core-import.ts b/packages/cli/src/generate/fixtures/controller/app.controller.core-import.ts similarity index 100% rename from packages/cli/src/generate/mocks/controller/app.controller.core-import.ts rename to packages/cli/src/generate/fixtures/controller/app.controller.core-import.ts diff --git a/packages/cli/src/generate/mocks/controller/app.controller.empty-property.ts b/packages/cli/src/generate/fixtures/controller/app.controller.empty-property.ts similarity index 100% rename from packages/cli/src/generate/mocks/controller/app.controller.empty-property.ts rename to packages/cli/src/generate/fixtures/controller/app.controller.empty-property.ts diff --git a/packages/cli/src/generate/mocks/controller/app.controller.empty-spaced-property.ts b/packages/cli/src/generate/fixtures/controller/app.controller.empty-spaced-property.ts similarity index 100% rename from packages/cli/src/generate/mocks/controller/app.controller.empty-spaced-property.ts rename to packages/cli/src/generate/fixtures/controller/app.controller.empty-spaced-property.ts diff --git a/packages/cli/src/generate/mocks/controller/app.controller.no-controller-import.ts b/packages/cli/src/generate/fixtures/controller/app.controller.no-controller-import.ts similarity index 100% rename from packages/cli/src/generate/mocks/controller/app.controller.no-controller-import.ts rename to packages/cli/src/generate/fixtures/controller/app.controller.no-controller-import.ts diff --git a/packages/cli/src/generate/mocks/controller/app.controller.no-empty-property.ts b/packages/cli/src/generate/fixtures/controller/app.controller.no-empty-property.ts similarity index 100% rename from packages/cli/src/generate/mocks/controller/app.controller.no-empty-property.ts rename to packages/cli/src/generate/fixtures/controller/app.controller.no-empty-property.ts diff --git a/packages/cli/src/generate/mocks/controller/app.controller.no-import.ts b/packages/cli/src/generate/fixtures/controller/app.controller.no-import.ts similarity index 100% rename from packages/cli/src/generate/mocks/controller/app.controller.no-import.ts rename to packages/cli/src/generate/fixtures/controller/app.controller.no-import.ts diff --git a/packages/cli/src/generate/mocks/controller/app.controller.rest.ts b/packages/cli/src/generate/fixtures/controller/app.controller.rest.ts similarity index 100% rename from packages/cli/src/generate/mocks/controller/app.controller.rest.ts rename to packages/cli/src/generate/fixtures/controller/app.controller.rest.ts diff --git a/packages/cli/src/generate/mocks/controller/index.ts b/packages/cli/src/generate/fixtures/controller/index.ts similarity index 100% rename from packages/cli/src/generate/mocks/controller/index.ts rename to packages/cli/src/generate/fixtures/controller/index.ts diff --git a/packages/cli/src/generate/mocks/entity/index.ts b/packages/cli/src/generate/fixtures/entity/index.ts similarity index 100% rename from packages/cli/src/generate/mocks/entity/index.ts rename to packages/cli/src/generate/fixtures/entity/index.ts diff --git a/packages/cli/src/generate/mocks/hook/index.ts b/packages/cli/src/generate/fixtures/hook/index.ts similarity index 100% rename from packages/cli/src/generate/mocks/hook/index.ts rename to packages/cli/src/generate/fixtures/hook/index.ts diff --git a/packages/cli/src/generate/mocks/model/index.ts b/packages/cli/src/generate/fixtures/model/index.ts similarity index 100% rename from packages/cli/src/generate/mocks/model/index.ts rename to packages/cli/src/generate/fixtures/model/index.ts diff --git a/packages/cli/src/generate/mocks/react/package.json b/packages/cli/src/generate/fixtures/react/package.json similarity index 100% rename from packages/cli/src/generate/mocks/react/package.json rename to packages/cli/src/generate/fixtures/react/package.json diff --git a/packages/cli/src/generate/mocks/rest-api/app.controller.controller-import.ts b/packages/cli/src/generate/fixtures/rest-api/app.controller.controller-import.ts similarity index 100% rename from packages/cli/src/generate/mocks/rest-api/app.controller.controller-import.ts rename to packages/cli/src/generate/fixtures/rest-api/app.controller.controller-import.ts diff --git a/packages/cli/src/generate/mocks/rest-api/app.controller.core-import.ts b/packages/cli/src/generate/fixtures/rest-api/app.controller.core-import.ts similarity index 100% rename from packages/cli/src/generate/mocks/rest-api/app.controller.core-import.ts rename to packages/cli/src/generate/fixtures/rest-api/app.controller.core-import.ts diff --git a/packages/cli/src/generate/mocks/rest-api/app.controller.empty-property.ts b/packages/cli/src/generate/fixtures/rest-api/app.controller.empty-property.ts similarity index 100% rename from packages/cli/src/generate/mocks/rest-api/app.controller.empty-property.ts rename to packages/cli/src/generate/fixtures/rest-api/app.controller.empty-property.ts diff --git a/packages/cli/src/generate/mocks/rest-api/app.controller.empty-spaced-property.ts b/packages/cli/src/generate/fixtures/rest-api/app.controller.empty-spaced-property.ts similarity index 100% rename from packages/cli/src/generate/mocks/rest-api/app.controller.empty-spaced-property.ts rename to packages/cli/src/generate/fixtures/rest-api/app.controller.empty-spaced-property.ts diff --git a/packages/cli/src/generate/mocks/rest-api/app.controller.no-controller-import.ts b/packages/cli/src/generate/fixtures/rest-api/app.controller.no-controller-import.ts similarity index 100% rename from packages/cli/src/generate/mocks/rest-api/app.controller.no-controller-import.ts rename to packages/cli/src/generate/fixtures/rest-api/app.controller.no-controller-import.ts diff --git a/packages/cli/src/generate/mocks/rest-api/app.controller.no-empty-property.ts b/packages/cli/src/generate/fixtures/rest-api/app.controller.no-empty-property.ts similarity index 100% rename from packages/cli/src/generate/mocks/rest-api/app.controller.no-empty-property.ts rename to packages/cli/src/generate/fixtures/rest-api/app.controller.no-empty-property.ts diff --git a/packages/cli/src/generate/mocks/rest-api/index.controllers.ts b/packages/cli/src/generate/fixtures/rest-api/index.controllers.ts similarity index 100% rename from packages/cli/src/generate/mocks/rest-api/index.controllers.ts rename to packages/cli/src/generate/fixtures/rest-api/index.controllers.ts diff --git a/packages/cli/src/generate/mocks/rest-api/index.current-dir.ts b/packages/cli/src/generate/fixtures/rest-api/index.current-dir.ts similarity index 100% rename from packages/cli/src/generate/mocks/rest-api/index.current-dir.ts rename to packages/cli/src/generate/fixtures/rest-api/index.current-dir.ts diff --git a/packages/cli/src/generate/mocks/rest-api/index.entities.ts b/packages/cli/src/generate/fixtures/rest-api/index.entities.ts similarity index 100% rename from packages/cli/src/generate/mocks/rest-api/index.entities.ts rename to packages/cli/src/generate/fixtures/rest-api/index.entities.ts diff --git a/packages/cli/src/generate/mocks/service/index.ts b/packages/cli/src/generate/fixtures/service/index.ts similarity index 100% rename from packages/cli/src/generate/mocks/service/index.ts rename to packages/cli/src/generate/fixtures/service/index.ts diff --git a/packages/cli/src/generate/mocks/vue/package.json b/packages/cli/src/generate/fixtures/vue/package.json similarity index 100% rename from packages/cli/src/generate/mocks/vue/package.json rename to packages/cli/src/generate/fixtures/vue/package.json diff --git a/packages/cli/src/generate/generators/angular/connect-angular.spec.ts b/packages/cli/src/generate/generators/angular/connect-angular.spec.ts index c104073937..bad5736ed9 100644 --- a/packages/cli/src/generate/generators/angular/connect-angular.spec.ts +++ b/packages/cli/src/generate/generators/angular/connect-angular.spec.ts @@ -13,8 +13,8 @@ describe('connectAngular', () => { it('should create a proxy.conf.json file in ${path}/src.', () => { fs .ensureDir('connector-test/angular/src') - .copyMock('angular/angular.json', 'connector-test/angular/angular.json') - .copyMock('angular/package.json', 'connector-test/angular/package.json'); + .copyFixture('angular/angular.json', 'connector-test/angular/angular.json') + .copyFixture('angular/package.json', 'connector-test/angular/package.json'); connectAngular('./connector-test/angular'); @@ -29,8 +29,8 @@ describe('connectAngular', () => { it('should update angular.json with the proxy file and the output dir.', () => { fs .ensureDir('connector-test/angular/src') - .copyMock('angular/angular.json', 'connector-test/angular/angular.json') - .copyMock('angular/package.json', 'connector-test/angular/package.json'); + .copyFixture('angular/angular.json', 'connector-test/angular/angular.json') + .copyFixture('angular/package.json', 'connector-test/angular/package.json'); connectAngular('./connector-test/angular'); @@ -48,8 +48,8 @@ describe('connectAngular', () => { it('should update package.json with the "--prod" flag.', () => { fs .ensureDir('connector-test/angular/src') - .copyMock('angular/angular.json', 'connector-test/angular/angular.json') - .copyMock('angular/package.json', 'connector-test/angular/package.json'); + .copyFixture('angular/angular.json', 'connector-test/angular/angular.json') + .copyFixture('angular/package.json', 'connector-test/angular/package.json'); connectAngular('./connector-test/angular'); diff --git a/packages/cli/src/generate/generators/controller/create-controller.spec.ts b/packages/cli/src/generate/generators/controller/create-controller.spec.ts index 47d5b55132..c9a6e4b25f 100644 --- a/packages/cli/src/generate/generators/controller/create-controller.spec.ts +++ b/packages/cli/src/generate/generators/controller/create-controller.spec.ts @@ -18,7 +18,7 @@ describe('createController', () => { fs .ensureDir(root) .cd(root) - .copyMock('controller/index.ts', 'index.ts'); + .copyFixture('controller/index.ts', 'index.ts'); }); it('should render the empty templates in the proper directory.', () => { @@ -59,13 +59,13 @@ describe('createController', () => { fs .ensureDir('src/app/controllers') .cd('src/app/controllers') - .copyMock('controller/index.ts', 'index.ts') + .copyFixture('controller/index.ts', 'index.ts') .cd('..'); }); it('should add all the imports if none exists.', () => { fs - .copyMock('controller/app.controller.no-import.ts', 'app.controller.ts'); + .copyFixture('controller/app.controller.no-import.ts', 'app.controller.ts'); createController({ name: 'test-fooBar', register: true }); @@ -76,7 +76,7 @@ describe('createController', () => { it('should add all the imports if none exists (subdir).', () => { fs .ensureDir('controllers/barfoo') - .copyMock('controller/app.controller.no-import.ts', 'controllers/barfoo/hello.controller.ts'); + .copyFixture('controller/app.controller.no-import.ts', 'controllers/barfoo/hello.controller.ts'); createController({ name: 'barfoo/hello/test-fooBar', register: true }); @@ -86,7 +86,7 @@ describe('createController', () => { it('should update the "subControllers" import in src/app/app.controller.ts if it exists.', () => { fs - .copyMock('controller/app.controller.controller-import.ts', 'app.controller.ts'); + .copyFixture('controller/app.controller.controller-import.ts', 'app.controller.ts'); createController({ name: 'test-fooBar', register: true }); @@ -96,7 +96,7 @@ describe('createController', () => { it('should add a "subControllers" import in src/app/app.controller.ts if none already exists.', () => { fs - .copyMock('controller/app.controller.no-controller-import.ts', 'app.controller.ts'); + .copyFixture('controller/app.controller.no-controller-import.ts', 'app.controller.ts'); createController({ name: 'test-fooBar', register: true }); @@ -106,7 +106,7 @@ describe('createController', () => { it('should update the "@foal/core" import in src/app/app.controller.ts if it exists.', () => { fs - .copyMock('controller/app.controller.core-import.ts', 'app.controller.ts'); + .copyFixture('controller/app.controller.core-import.ts', 'app.controller.ts'); createController({ name: 'test-fooBar', register: true }); @@ -116,7 +116,7 @@ describe('createController', () => { it('should update the "subControllers = []" property in src/app/app.controller.ts if it exists.', () => { fs - .copyMock('controller/app.controller.empty-property.ts', 'app.controller.ts'); + .copyFixture('controller/app.controller.empty-property.ts', 'app.controller.ts'); createController({ name: 'test-fooBar', register: true }); @@ -126,7 +126,7 @@ describe('createController', () => { it('should update the "subControllers = [ \\n \\n ]" property in src/app/app.controller.ts if it exists.', () => { fs - .copyMock('controller/app.controller.empty-spaced-property.ts', 'app.controller.ts'); + .copyFixture('controller/app.controller.empty-spaced-property.ts', 'app.controller.ts'); createController({ name: 'test-fooBar', register: true }); @@ -137,7 +137,7 @@ describe('createController', () => { it('should update the "subControllers = [ \\n (.*) \\n ]" property in' + ' src/app/app.controller.ts if it exists.', () => { fs - .copyMock('controller/app.controller.no-empty-property.ts', 'app.controller.ts'); + .copyFixture('controller/app.controller.no-empty-property.ts', 'app.controller.ts'); createController({ name: 'test-fooBar', register: true }); diff --git a/packages/cli/src/generate/generators/entity/create-entity.spec.ts b/packages/cli/src/generate/generators/entity/create-entity.spec.ts index f9d155d8eb..32816345ea 100644 --- a/packages/cli/src/generate/generators/entity/create-entity.spec.ts +++ b/packages/cli/src/generate/generators/entity/create-entity.spec.ts @@ -18,7 +18,7 @@ describe('createEntity', () => { fs .ensureDir(root) .cd(root) - .copyMock('entity/index.ts', 'index.ts'); + .copyFixture('entity/index.ts', 'index.ts'); }); it('should render the templates in the proper directory.', () => { diff --git a/packages/cli/src/generate/generators/hook/create-hook.spec.ts b/packages/cli/src/generate/generators/hook/create-hook.spec.ts index c3f93b6ab6..a80071db5a 100644 --- a/packages/cli/src/generate/generators/hook/create-hook.spec.ts +++ b/packages/cli/src/generate/generators/hook/create-hook.spec.ts @@ -18,7 +18,7 @@ describe('createHook', () => { fs .ensureDir(root) .cd(root) - .copyMock('hook/index.ts', 'index.ts'); + .copyFixture('hook/index.ts', 'index.ts'); }); it('should render the templates in the proper directory.', () => { diff --git a/packages/cli/src/generate/generators/model/create-model.spec.ts b/packages/cli/src/generate/generators/model/create-model.spec.ts index 24150e7c18..8e3419e6e3 100644 --- a/packages/cli/src/generate/generators/model/create-model.spec.ts +++ b/packages/cli/src/generate/generators/model/create-model.spec.ts @@ -18,7 +18,7 @@ describe('createModel', () => { fs .ensureDir(root) .cd(root) - .copyMock('model/index.ts', 'index.ts'); + .copyFixture('model/index.ts', 'index.ts'); }); it('should render the templates in the proper directory.', () => { diff --git a/packages/cli/src/generate/generators/react/connect-react.spec.ts b/packages/cli/src/generate/generators/react/connect-react.spec.ts index cf69d032b5..2d16780763 100644 --- a/packages/cli/src/generate/generators/react/connect-react.spec.ts +++ b/packages/cli/src/generate/generators/react/connect-react.spec.ts @@ -12,7 +12,7 @@ describe('connectReact', () => { it('should update package.json to set up the proxy, install ncp and change the output dir.', () => { fs .ensureDir('connector-test/react') - .copyMock('react/package.json', 'connector-test/react/package.json'); + .copyFixture('react/package.json', 'connector-test/react/package.json'); connectReact('./connector-test/react'); diff --git a/packages/cli/src/generate/generators/rest-api/create-rest-api.spec.ts b/packages/cli/src/generate/generators/rest-api/create-rest-api.spec.ts index 483edc33fe..9eea2c1651 100644 --- a/packages/cli/src/generate/generators/rest-api/create-rest-api.spec.ts +++ b/packages/cli/src/generate/generators/rest-api/create-rest-api.spec.ts @@ -22,11 +22,11 @@ describe('createRestApi', () => { .cd(root) .ensureDir('entities') .cd('entities') - .copyMock('rest-api/index.entities.ts', 'index.ts') + .copyFixture('rest-api/index.entities.ts', 'index.ts') .cd('..') .ensureDir('controllers') .cd('controllers') - .copyMock('rest-api/index.controllers.ts', 'index.ts') + .copyFixture('rest-api/index.controllers.ts', 'index.ts') .cd('..'); }); @@ -64,17 +64,17 @@ describe('createRestApi', () => { .cd(root) .ensureDir('entities') .cd('entities') - .copyMock('rest-api/index.entities.ts', 'index.ts') + .copyFixture('rest-api/index.entities.ts', 'index.ts') .cd('..') .ensureDir('controllers') .cd('controllers') - .copyMock('rest-api/index.controllers.ts', 'index.ts') + .copyFixture('rest-api/index.controllers.ts', 'index.ts') .cd('..'); }); it('should update the "subControllers" import in src/app/app.controller.ts if it exists.', () => { fs - .copyMock('rest-api/app.controller.controller-import.ts', 'app.controller.ts'); + .copyFixture('rest-api/app.controller.controller-import.ts', 'app.controller.ts'); createRestApi({ name: 'test-fooBar', register: true }); @@ -84,7 +84,7 @@ describe('createRestApi', () => { it('should add a "subControllers" import in src/app/app.controller.ts if none already exists.', () => { fs - .copyMock('rest-api/app.controller.no-controller-import.ts', 'app.controller.ts'); + .copyFixture('rest-api/app.controller.no-controller-import.ts', 'app.controller.ts'); createRestApi({ name: 'test-fooBar', register: true }); @@ -94,7 +94,7 @@ describe('createRestApi', () => { it('should update the "@foal/core" import in src/app/app.controller.ts if it exists.', () => { fs - .copyMock('rest-api/app.controller.core-import.ts', 'app.controller.ts'); + .copyFixture('rest-api/app.controller.core-import.ts', 'app.controller.ts'); createRestApi({ name: 'test-fooBar', register: true }); @@ -104,7 +104,7 @@ describe('createRestApi', () => { it('should update the "subControllers = []" property in src/app/app.controller.ts if it exists.', () => { fs - .copyMock('rest-api/app.controller.empty-property.ts', 'app.controller.ts'); + .copyFixture('rest-api/app.controller.empty-property.ts', 'app.controller.ts'); createRestApi({ name: 'test-fooBar', register: true }); @@ -114,7 +114,7 @@ describe('createRestApi', () => { it('should update the "subControllers = [ \\n \\n ]" property in src/app/app.controller.ts if it exists.', () => { fs - .copyMock('rest-api/app.controller.empty-spaced-property.ts', 'app.controller.ts'); + .copyFixture('rest-api/app.controller.empty-spaced-property.ts', 'app.controller.ts'); createRestApi({ name: 'test-fooBar', register: true }); @@ -125,7 +125,7 @@ describe('createRestApi', () => { it('should update the "subControllers = [ \\n (.*) \\n ]" property in' + ' src/app/app.controller.ts if it exists.', () => { fs - .copyMock('rest-api/app.controller.no-empty-property.ts', 'app.controller.ts'); + .copyFixture('rest-api/app.controller.no-empty-property.ts', 'app.controller.ts'); createRestApi({ name: 'test-fooBar', register: true }); @@ -144,7 +144,7 @@ describe('createRestApi', () => { beforeEach(() => { fs - .copyMock('rest-api/index.current-dir.ts', 'index.ts'); + .copyFixture('rest-api/index.current-dir.ts', 'index.ts'); }); it('should render the templates in the current directory.', () => { diff --git a/packages/cli/src/generate/generators/service/create-service.spec.ts b/packages/cli/src/generate/generators/service/create-service.spec.ts index b2fa3915d0..19dd2364a0 100644 --- a/packages/cli/src/generate/generators/service/create-service.spec.ts +++ b/packages/cli/src/generate/generators/service/create-service.spec.ts @@ -18,7 +18,7 @@ describe('createService', () => { fs .ensureDir(root) .cd(root) - .copyMock('service/index.ts', 'index.ts'); + .copyFixture('service/index.ts', 'index.ts'); }); it('should render the empty templates in the proper directory.', () => { diff --git a/packages/cli/src/generate/generators/vue/connect-vue.spec.ts b/packages/cli/src/generate/generators/vue/connect-vue.spec.ts index 670c0aa268..47f273a063 100644 --- a/packages/cli/src/generate/generators/vue/connect-vue.spec.ts +++ b/packages/cli/src/generate/generators/vue/connect-vue.spec.ts @@ -12,7 +12,7 @@ describe('connectVue', () => { it('should update package.json to set up the proxy, install ncp and change the output dir.', () => { fs .ensureDir('connector-test/vue') - .copyMock('vue/package.json', 'connector-test/vue/package.json'); + .copyFixture('vue/package.json', 'connector-test/vue/package.json'); connectVue('./connector-test/vue'); diff --git a/tslint.json b/tslint.json index 2546555455..c7e73a1615 100644 --- a/tslint.json +++ b/tslint.json @@ -25,7 +25,7 @@ "linterOptions": { "exclude": [ "**/src/migrations/*.ts", - "**/src/generate/mocks/**/*.ts", + "**/src/generate/fixtures/**/*.ts", "**/src/generate/templates/model/*.ts", "**/src/generate/templates/rest-api/*.ts", "**/src/generate/specs/**/app.controller.*.ts", From 7a0f40c183e4ec71b3f956aa3a68d4d85a4a4de7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Tue, 19 May 2020 08:33:25 +0200 Subject: [PATCH 50/92] Fix linting --- .../cli/src/generate/generators/controller/create-controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/generate/generators/controller/create-controller.ts b/packages/cli/src/generate/generators/controller/create-controller.ts index 23be7c0be2..a293fb82e5 100644 --- a/packages/cli/src/generate/generators/controller/create-controller.ts +++ b/packages/cli/src/generate/generators/controller/create-controller.ts @@ -1,5 +1,5 @@ // FoalTS -import { basename, dirname, join } from 'path'; +import { basename, dirname } from 'path'; import { FileSystem } from '../../file-system'; import { getNames } from '../../utils'; import { registerController } from './register-controller'; From 949a4e80c3bca7437fef0102134435f5df6da173 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Tue, 19 May 2020 09:48:18 +0200 Subject: [PATCH 51/92] [CLI] Add FileSystem.addOrExtendNamedImportIn --- packages/cli/src/generate/file-system.spec.ts | 64 +++++++++++++++++++ packages/cli/src/generate/file-system.ts | 51 ++++++++++++++- 2 files changed, 114 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/generate/file-system.spec.ts b/packages/cli/src/generate/file-system.spec.ts index eee60233e2..3a91b18698 100644 --- a/packages/cli/src/generate/file-system.spec.ts +++ b/packages/cli/src/generate/file-system.spec.ts @@ -397,6 +397,70 @@ describe('FileSystem', () => { }); + + describe('has a "addOrExtendNamedImportIn" method that should', () => { + + beforeEach(() => { + mkdir('test-generators'); + writeFileSync( + 'test-generators/empty.txt', + 'class FooBar {}', + 'utf8' + ); + writeFileSync( + 'test-generators/hello.txt', + '// 3p\n' + + 'import { Hello } from \'./foo.txt\';\n' + + 'import { World } from \'./bar.txt\';\n' + + '\n' + + 'class FooBar {}', + 'utf8' + ); + }); + + afterEach(() => { + rmfile('test-generators/empty.txt'); + rmfile('test-generators/hello.txt'); + rmdir('test-generators'); + }); + + it('should add a named import at the beginning of the file if none exists.', () => { + fs.addOrExtendNamedImportIn('empty.txt', 'FooController', './controllers/foo.controller.txt'); + strictEqual( + readFileSync('test-generators/empty.txt', 'utf8'), + 'import { FooController } from \'./controllers/foo.controller.txt\';\n' + + '\n' + + 'class FooBar {}', + ); + }); + + it('should add a named import after all the imports if it does not already exist.', () => { + fs.addOrExtendNamedImportIn('hello.txt', 'FooController', './controllers/foo.controller.txt'); + strictEqual( + readFileSync('test-generators/hello.txt', 'utf8'), + '// 3p\n' + + 'import { Hello } from \'./foo.txt\';\n' + + 'import { World } from \'./bar.txt\';\n' + + 'import { FooController } from \'./controllers/foo.controller.txt\';\n' + + '\n' + + 'class FooBar {}', + ); + }); + + it('should extend the named import if it already exist.', () => { + fs.addOrExtendNamedImportIn('hello.txt', 'MyController', './bar.txt'); + strictEqual( + readFileSync('test-generators/hello.txt', 'utf8'), + '// 3p\n' + + 'import { Hello } from \'./foo.txt\';\n' + + 'import { MyController, World } from \'./bar.txt\';\n' + + '\n' + + 'class FooBar {}', + ); + }); + + }); + describe('has a "setUp" method that', () => { afterEach(() => { diff --git a/packages/cli/src/generate/file-system.ts b/packages/cli/src/generate/file-system.ts index 810247f6dc..6c4f1947dc 100644 --- a/packages/cli/src/generate/file-system.ts +++ b/packages/cli/src/generate/file-system.ts @@ -241,7 +241,7 @@ export class FileSystem { } /** - * Add a named import at the bottom of the file + * Add a named import at the bottom of the file. * * @param {string} path - The file path relative to the client directory. * @param {string} specifier - The import specifier. @@ -254,6 +254,55 @@ export class FileSystem { return this; } + /** + * Add or extend a named import at the beginning of the file. + * + * If an import already exists with this path, it is completed. + * If it does not already exist, it is added at the end of all imports. + * + * @param {string} path - The file path relative to the client directory. + * @param {string} specifier - The import specifier. + * @param {string} source - The import source. + * @returns {this} + * @memberof FileSystem + */ + addOrExtendNamedImportIn(path: string, specifier: string, source: string): this { + this.modify(path, content => { + // TODO: add tests to support double quotes. + const regex = /import (.*) from '(.*)';/g; + let endPos = 0; + + const replacedContent = content.replace(regex, (match, p1, p2, offset: number) => { + endPos = offset + match.length; + const namedImportRegex = new RegExp(`import {(.*)} from \'(.*)\';`); + return match.replace(namedImportRegex, (subString, specifiers: string, path: string) => { + if (path !== source) { + return subString; + } + const newSpecifiers = specifiers + .split(',') + .map(imp => imp.trim()) + .concat(specifier) + .sort((a, b) => a.localeCompare(b)) + .join(', '); + return `import { ${newSpecifiers} } from '${source}';`; + }); + }); + + if (replacedContent !== content) { + return replacedContent; + } + + const newImport = `import { ${specifier} } from '${source}';`; + if (endPos === 0) { + return `${newImport}\n\n${content}`; + } + + return content.substr(0, endPos) + '\n' + newImport + content.substr(endPos); + }); + return this; + } + /************************ Testing Methods ************************/ From 6c460d9c50bbe508e6b00e1a3dad36bc039eee89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Tue, 19 May 2020 15:40:04 +0200 Subject: [PATCH 52/92] [CLI] Add FS.addOrExtendClassArrayPropertyIn --- packages/cli/src/generate/file-system.spec.ts | 167 +++++++++++++++++- packages/cli/src/generate/file-system.ts | 52 +++++- 2 files changed, 215 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/generate/file-system.spec.ts b/packages/cli/src/generate/file-system.spec.ts index 3a91b18698..0a126387eb 100644 --- a/packages/cli/src/generate/file-system.spec.ts +++ b/packages/cli/src/generate/file-system.spec.ts @@ -397,8 +397,7 @@ describe('FileSystem', () => { }); - - describe('has a "addOrExtendNamedImportIn" method that should', () => { + describe('has an "addOrExtendNamedImportIn" method that should', () => { beforeEach(() => { mkdir('test-generators'); @@ -461,6 +460,170 @@ describe('FileSystem', () => { }); + describe('has an "addOrExtendClassArrayProperty" method that should', () => { + + beforeEach(() => { + mkdir('test-generators'); + }); + + afterEach(() => { + rmfile('test-generators/foo.txt'); + rmdir('test-generators'); + }); + + it('should add the class property if it does not exist (empty class).', () => { + writeFileSync( + 'test-generators/foo.txt', + 'class FooBar {}', + 'utf8' + ); + fs.addOrExtendClassArrayPropertyIn( + 'foo.txt', + 'FooBar', + 'subControllers', + 'controller(\'/api\', ApiController)' + ); + strictEqual( + readFileSync('test-generators/foo.txt', 'utf8'), + 'class FooBar {\n' + + ' subControllers = [\n' + + ' controller(\'/api\', ApiController)\n' + + ' ];\n' + + '}', + ); + }); + + it('should add the class property if it does not exist (empty class with line returns).', () => { + writeFileSync( + 'test-generators/foo.txt', + 'class FooBar {\n\n}', + 'utf8' + ); + fs.addOrExtendClassArrayPropertyIn( + 'foo.txt', + 'FooBar', + 'subControllers', + 'controller(\'/api\', ApiController)' + ); + strictEqual( + readFileSync('test-generators/foo.txt', 'utf8'), + 'class FooBar {\n' + + ' subControllers = [\n' + + ' controller(\'/api\', ApiController)\n' + + ' ];\n' + + '}', + ); + }); + + it('should add the class property if it does not exist (class with existing properties).', () => { + writeFileSync( + 'test-generators/foo.txt', + 'class FooBar {\n' + + ' foo = 3;\n' + + ' bar() {};\n' + + '}', + 'utf8' + ); + fs.addOrExtendClassArrayPropertyIn( + 'foo.txt', + 'FooBar', + 'subControllers', + 'controller(\'/api\', ApiController)' + ); + strictEqual( + readFileSync('test-generators/foo.txt', 'utf8'), + 'class FooBar {\n' + + ' subControllers = [\n' + + ' controller(\'/api\', ApiController)\n' + + ' ];\n' + + '\n' + + ' foo = 3;\n' + + ' bar() {};\n' + + '}', + ); + }); + + it('should extend the class property if it already exists (empty array).', () => { + writeFileSync( + 'test-generators/foo.txt', + 'class FooBar {\n' + + ' subControllers = [];\n' + + '}', + 'utf8' + ); + fs.addOrExtendClassArrayPropertyIn( + 'foo.txt', + 'FooBar', + 'subControllers', + 'controller(\'/api\', ApiController)' + ); + strictEqual( + readFileSync('test-generators/foo.txt', 'utf8'), + 'class FooBar {\n' + + ' subControllers = [\n' + + ' controller(\'/api\', ApiController)\n' + + ' ];\n' + + '}', + ); + }); + + it('should extend the class property if it already exists (empty array with line returns).', () => { + writeFileSync( + 'test-generators/foo.txt', + 'class FooBar {\n' + + ' subControllers = [\n' + + '\n' + + ' ];\n' + + '}', + 'utf8' + ); + fs.addOrExtendClassArrayPropertyIn( + 'foo.txt', + 'FooBar', + 'subControllers', + 'controller(\'/api\', ApiController)' + ); + strictEqual( + readFileSync('test-generators/foo.txt', 'utf8'), + 'class FooBar {\n' + + ' subControllers = [\n' + + ' controller(\'/api\', ApiController)\n' + + ' ];\n' + + '}', + ); + }); + + it('should extend the class property if it already exists (empty array with existing items).', () => { + writeFileSync( + 'test-generators/foo.txt', + 'class FooBar {\n' + + ' subControllers = [\n' + + ' controller(\'\/foo\', FooController),\n' + + ' BarController,\n' + + ' ];\n' + + '}', + 'utf8' + ); + fs.addOrExtendClassArrayPropertyIn( + 'foo.txt', + 'FooBar', + 'subControllers', + 'controller(\'/api\', ApiController)' + ); + strictEqual( + readFileSync('test-generators/foo.txt', 'utf8'), + 'class FooBar {\n' + + ' subControllers = [\n' + + ' controller(\'\/foo\', FooController),\n' + + ' BarController,\n' + + ' controller(\'/api\', ApiController)\n' + + ' ];\n' + + '}', + ); + }); + + }); + describe('has a "setUp" method that', () => { afterEach(() => { diff --git a/packages/cli/src/generate/file-system.ts b/packages/cli/src/generate/file-system.ts index 6c4f1947dc..c24973a81e 100644 --- a/packages/cli/src/generate/file-system.ts +++ b/packages/cli/src/generate/file-system.ts @@ -255,9 +255,9 @@ export class FileSystem { } /** - * Add or extend a named import at the beginning of the file. + * Adds or extends a named import at the beginning of the file. * - * If an import already exists with this path, it is completed. + * If an import already exists with this source path, it is completed. * If it does not already exist, it is added at the end of all imports. * * @param {string} path - The file path relative to the client directory. @@ -300,6 +300,54 @@ export class FileSystem { return content.substr(0, endPos) + '\n' + newImport + content.substr(endPos); }); + + return this; + } + + /** + * Creates or adds an element to the array property of a class. + * + * If the class does not exist, this method does nothing. + * + * @param {string} path - The file path relative to the client directory. + * @param {string} className - The class name. + * @param {string} propertyName - The property name. + * @param {string} element - The item to add to the array. + * @returns {this} + * @memberof FileSystem + */ + addOrExtendClassArrayPropertyIn(path: string, className: string, propertyName: string, element: string): this { + this.modify(path, content => content.replace(new RegExp(`class ${className} {(.*)}`, 's'), (match, p1: string) => { + if (/^(\s)*$/.test(p1)) { + return `class ${className} {\n ${propertyName} = [\n ${element}\n ];\n}`; + } + + const replacedMatch = match.replace( + new RegExp(`( *)${propertyName} = \\[(.*)\\];`, 's'), + (_, spaces, content: string) => { + const items = content + .replace(/,\n/g, '\n') + .split('\n') + .map(e => e.trim()) + .concat(element) + .map(e => `${spaces}${spaces}${e}`); + + const cleanItems: string[] = []; + for (const item of items) { + if (item.trim() !== '') { + cleanItems.push(item); + } + } + return `${spaces}${propertyName} = [\n${cleanItems.join(',\n')}\n${spaces}];`; + } + ); + + if (replacedMatch !== match) { + return replacedMatch; + } + + return `class ${className} {\n ${propertyName} = [\n ${element}\n ];\n${p1}}`; + })); return this; } From e649bb413e8de84e1dfc91bbce44a1e0d7c5788a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Tue, 19 May 2020 15:51:48 +0200 Subject: [PATCH 53/92] [CLI] Simplify and improve FileSystem.assertEqual --- packages/cli/src/generate/file-system.spec.ts | 28 +------------------ packages/cli/src/generate/file-system.ts | 22 ++++----------- 2 files changed, 7 insertions(+), 43 deletions(-) diff --git a/packages/cli/src/generate/file-system.spec.ts b/packages/cli/src/generate/file-system.spec.ts index 0a126387eb..628978628c 100644 --- a/packages/cli/src/generate/file-system.spec.ts +++ b/packages/cli/src/generate/file-system.spec.ts @@ -3,9 +3,6 @@ import { notStrictEqual, strictEqual } from 'assert'; import { existsSync, mkdirSync, readFileSync, rmdirSync, unlinkSync, writeFileSync } from 'fs'; import { join } from 'path'; -// 3p -import { green, red } from 'colors/safe'; - // FoalTS import { FileSystem } from './file-system'; @@ -807,7 +804,7 @@ describe('FileSystem', () => { fs.assertEqual('bar.txt', 'test-file-system/foo.spec.txt'); throw new Error('An error should have been thrown.'); } catch (error) { - notStrictEqual(error.message, 'An error should have been thrown.'); + strictEqual(error.message, '\'hi\\nmy\\nearth\\n!\' === \'hello\\nmy\\nworld\''); } }); @@ -824,29 +821,6 @@ describe('FileSystem', () => { } }); - it('should throw understandable errors.', () => { - try { - fs.assertEqual('bar.txt', 'test-file-system/foo.spec.txt'); - throw new Error('An error should have been thrown.'); - } catch (error) { - strictEqual( - error.message, - `The two files "bar.txt" and "test-file-system/foo.spec.txt" are not equal.\n\n` - + 'Line 1\n' - + green(' Expected: hello\n') - + red(' Actual: hi') - + '\n\n' - + 'Line 3\n' - + green(' Expected: world\n') - + red(' Actual: earth') - + '\n\n' - + 'Line 4\n' - + green(' Expected: undefined\n') - + red(' Actual: !') - ); - } - }); - }); describe('has a "copyFixture" method that', () => { diff --git a/packages/cli/src/generate/file-system.ts b/packages/cli/src/generate/file-system.ts index c24973a81e..046f2b2320 100644 --- a/packages/cli/src/generate/file-system.ts +++ b/packages/cli/src/generate/file-system.ts @@ -1,5 +1,5 @@ // std -import { deepStrictEqual } from 'assert'; +import { deepStrictEqual, strictEqual } from 'assert'; import { copyFileSync, existsSync, @@ -14,7 +14,7 @@ import { import { dirname, join } from 'path'; // 3p -import { cyan, green, red } from 'colors/safe'; +import { cyan, green } from 'colors/safe'; function rmDirAndFiles(path: string) { const files = readdirSync(path); @@ -435,20 +435,10 @@ export class FileSystem { readFileSync(specPath) ); } else { - const expectedContent = readFileSync(specPath, 'utf8'); - const actualContent = readFileSync(this.parse(actual), 'utf8'); - if (expectedContent !== actualContent) { - const expectedLines = expectedContent.split('\n'); - const actualLines = actualContent.split('\n'); - let message = `The two files "${actual}" and "${expected}" are not equal.`; - for (let i = 0; i < Math.max(expectedLines.length, actualLines.length); i++) { - if (expectedLines[i] !== actualLines[i]) { - message += `\n\nLine ${i + 1}\n` - + `${green(` Expected: ${expectedLines[i]}\n`)}${red(` Actual: ${actualLines[i]}`)}`; - } - } - throw new Error(message); - } + strictEqual( + readFileSync(this.parse(actual), 'utf8'), + readFileSync(specPath, 'utf8') + ); } return this; } From 5a63ec543119cf615295e705ca12515f5afaaf83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Tue, 19 May 2020 16:10:22 +0200 Subject: [PATCH 54/92] [CLI] Simplify addOrExtendClassArrayPropertyIn --- packages/cli/src/generate/file-system.spec.ts | 6 -- packages/cli/src/generate/file-system.ts | 55 ++++++++++--------- 2 files changed, 29 insertions(+), 32 deletions(-) diff --git a/packages/cli/src/generate/file-system.spec.ts b/packages/cli/src/generate/file-system.spec.ts index 628978628c..ff4988832d 100644 --- a/packages/cli/src/generate/file-system.spec.ts +++ b/packages/cli/src/generate/file-system.spec.ts @@ -476,7 +476,6 @@ describe('FileSystem', () => { ); fs.addOrExtendClassArrayPropertyIn( 'foo.txt', - 'FooBar', 'subControllers', 'controller(\'/api\', ApiController)' ); @@ -498,7 +497,6 @@ describe('FileSystem', () => { ); fs.addOrExtendClassArrayPropertyIn( 'foo.txt', - 'FooBar', 'subControllers', 'controller(\'/api\', ApiController)' ); @@ -523,7 +521,6 @@ describe('FileSystem', () => { ); fs.addOrExtendClassArrayPropertyIn( 'foo.txt', - 'FooBar', 'subControllers', 'controller(\'/api\', ApiController)' ); @@ -550,7 +547,6 @@ describe('FileSystem', () => { ); fs.addOrExtendClassArrayPropertyIn( 'foo.txt', - 'FooBar', 'subControllers', 'controller(\'/api\', ApiController)' ); @@ -576,7 +572,6 @@ describe('FileSystem', () => { ); fs.addOrExtendClassArrayPropertyIn( 'foo.txt', - 'FooBar', 'subControllers', 'controller(\'/api\', ApiController)' ); @@ -603,7 +598,6 @@ describe('FileSystem', () => { ); fs.addOrExtendClassArrayPropertyIn( 'foo.txt', - 'FooBar', 'subControllers', 'controller(\'/api\', ApiController)' ); diff --git a/packages/cli/src/generate/file-system.ts b/packages/cli/src/generate/file-system.ts index 046f2b2320..7b53403f65 100644 --- a/packages/cli/src/generate/file-system.ts +++ b/packages/cli/src/generate/file-system.ts @@ -316,38 +316,41 @@ export class FileSystem { * @returns {this} * @memberof FileSystem */ - addOrExtendClassArrayPropertyIn(path: string, className: string, propertyName: string, element: string): this { - this.modify(path, content => content.replace(new RegExp(`class ${className} {(.*)}`, 's'), (match, p1: string) => { - if (/^(\s)*$/.test(p1)) { - return `class ${className} {\n ${propertyName} = [\n ${element}\n ];\n}`; - } + addOrExtendClassArrayPropertyIn(path: string, propertyName: string, element: string): this { + this.modify(path, content => content.replace( + new RegExp(`class (\\w*) {(.*)}`, 's'), + (match, className: string, p2: string) => { + if (/^(\s)*$/.test(p2)) { + return `class ${className} {\n ${propertyName} = [\n ${element}\n ];\n}`; + } - const replacedMatch = match.replace( - new RegExp(`( *)${propertyName} = \\[(.*)\\];`, 's'), - (_, spaces, content: string) => { - const items = content - .replace(/,\n/g, '\n') - .split('\n') - .map(e => e.trim()) - .concat(element) - .map(e => `${spaces}${spaces}${e}`); - - const cleanItems: string[] = []; - for (const item of items) { - if (item.trim() !== '') { - cleanItems.push(item); + const replacedMatch = match.replace( + new RegExp(`( *)${propertyName} = \\[(.*)\\];`, 's'), + (_, spaces, content: string) => { + const items = content + .replace(/,\n/g, '\n') + .split('\n') + .map(e => e.trim()) + .concat(element) + .map(e => `${spaces}${spaces}${e}`); + + const cleanItems: string[] = []; + for (const item of items) { + if (item.trim() !== '') { + cleanItems.push(item); + } } + return `${spaces}${propertyName} = [\n${cleanItems.join(',\n')}\n${spaces}];`; } - return `${spaces}${propertyName} = [\n${cleanItems.join(',\n')}\n${spaces}];`; + ); + + if (replacedMatch !== match) { + return replacedMatch; } - ); - if (replacedMatch !== match) { - return replacedMatch; + return `class ${className} {\n ${propertyName} = [\n ${element}\n ];\n${p2}}`; } - - return `class ${className} {\n ${propertyName} = [\n ${element}\n ];\n${p1}}`; - })); + )); return this; } From a75a64b85fc2bfe4ff699007914dffd0e81d5b0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Tue, 19 May 2020 16:35:33 +0200 Subject: [PATCH 55/92] [CLI] Remove registerController --- .../fixtures/controller/api.controller.ts | 9 +++ .../app.controller.controller-import.ts | 4 - .../controller/app.controller.core-import.ts | 4 - .../app.controller.empty-property.ts | 6 -- .../app.controller.empty-spaced-property.ts | 8 -- .../app.controller.no-controller-import.ts | 4 - .../app.controller.no-empty-property.ts | 9 --- .../controller/app.controller.no-import.ts | 1 - .../controller/app.controller.rest.ts | 6 -- .../fixtures/controller/app.controller.ts | 9 +++ .../app.controller.controller-import.ts | 4 - .../rest-api/app.controller.core-import.ts | 4 - .../rest-api/app.controller.empty-property.ts | 6 -- .../app.controller.empty-spaced-property.ts | 8 -- .../app.controller.no-controller-import.ts | 4 - .../app.controller.no-empty-property.ts | 9 --- .../fixtures/rest-api/app.controller.ts | 9 +++ .../controller/create-controller.spec.ts | 77 ++----------------- .../controller/create-controller.ts | 28 +++++-- .../controller/register-controller.ts | 61 --------------- .../rest-api/create-rest-api.spec.ts | 60 ++------------- .../generators/rest-api/create-rest-api.ts | 29 +++++-- ...no-empty-property.ts => api.controller.ts} | 4 +- .../app.controller.controller-import.ts | 5 -- .../controller/app.controller.core-import.ts | 5 -- .../app.controller.empty-property.ts | 10 --- .../app.controller.empty-spaced-property.ts | 10 --- .../app.controller.no-controller-import.ts | 6 -- .../controller/app.controller.no-import.ts | 4 - .../specs/controller/app.controller.rest.ts | 10 --- .../specs/controller/app.controller.ts | 11 +++ .../app.controller.controller-import.ts | 5 -- .../rest-api/app.controller.core-import.ts | 5 -- .../rest-api/app.controller.empty-property.ts | 10 --- .../app.controller.empty-spaced-property.ts | 10 --- .../app.controller.no-controller-import.ts | 6 -- ...no-empty-property.ts => app.controller.ts} | 4 +- 37 files changed, 99 insertions(+), 365 deletions(-) create mode 100644 packages/cli/src/generate/fixtures/controller/api.controller.ts delete mode 100644 packages/cli/src/generate/fixtures/controller/app.controller.controller-import.ts delete mode 100644 packages/cli/src/generate/fixtures/controller/app.controller.core-import.ts delete mode 100644 packages/cli/src/generate/fixtures/controller/app.controller.empty-property.ts delete mode 100644 packages/cli/src/generate/fixtures/controller/app.controller.empty-spaced-property.ts delete mode 100644 packages/cli/src/generate/fixtures/controller/app.controller.no-controller-import.ts delete mode 100644 packages/cli/src/generate/fixtures/controller/app.controller.no-empty-property.ts delete mode 100644 packages/cli/src/generate/fixtures/controller/app.controller.no-import.ts delete mode 100644 packages/cli/src/generate/fixtures/controller/app.controller.rest.ts create mode 100644 packages/cli/src/generate/fixtures/controller/app.controller.ts delete mode 100644 packages/cli/src/generate/fixtures/rest-api/app.controller.controller-import.ts delete mode 100644 packages/cli/src/generate/fixtures/rest-api/app.controller.core-import.ts delete mode 100644 packages/cli/src/generate/fixtures/rest-api/app.controller.empty-property.ts delete mode 100644 packages/cli/src/generate/fixtures/rest-api/app.controller.empty-spaced-property.ts delete mode 100644 packages/cli/src/generate/fixtures/rest-api/app.controller.no-controller-import.ts delete mode 100644 packages/cli/src/generate/fixtures/rest-api/app.controller.no-empty-property.ts create mode 100644 packages/cli/src/generate/fixtures/rest-api/app.controller.ts delete mode 100644 packages/cli/src/generate/generators/controller/register-controller.ts rename packages/cli/src/generate/specs/controller/{app.controller.no-empty-property.ts => api.controller.ts} (65%) delete mode 100644 packages/cli/src/generate/specs/controller/app.controller.controller-import.ts delete mode 100644 packages/cli/src/generate/specs/controller/app.controller.core-import.ts delete mode 100644 packages/cli/src/generate/specs/controller/app.controller.empty-property.ts delete mode 100644 packages/cli/src/generate/specs/controller/app.controller.empty-spaced-property.ts delete mode 100644 packages/cli/src/generate/specs/controller/app.controller.no-controller-import.ts delete mode 100644 packages/cli/src/generate/specs/controller/app.controller.no-import.ts delete mode 100644 packages/cli/src/generate/specs/controller/app.controller.rest.ts create mode 100644 packages/cli/src/generate/specs/controller/app.controller.ts delete mode 100644 packages/cli/src/generate/specs/rest-api/app.controller.controller-import.ts delete mode 100644 packages/cli/src/generate/specs/rest-api/app.controller.core-import.ts delete mode 100644 packages/cli/src/generate/specs/rest-api/app.controller.empty-property.ts delete mode 100644 packages/cli/src/generate/specs/rest-api/app.controller.empty-spaced-property.ts delete mode 100644 packages/cli/src/generate/specs/rest-api/app.controller.no-controller-import.ts rename packages/cli/src/generate/specs/rest-api/{app.controller.no-empty-property.ts => app.controller.ts} (64%) diff --git a/packages/cli/src/generate/fixtures/controller/api.controller.ts b/packages/cli/src/generate/fixtures/controller/api.controller.ts new file mode 100644 index 0000000000..92a663a828 --- /dev/null +++ b/packages/cli/src/generate/fixtures/controller/api.controller.ts @@ -0,0 +1,9 @@ +// 3p +import { MyController, MyController2 } from './api'; + +export class ApiController { + subControllers = [ + controller('/', MyController), + controller('/', MyController2), + ]; +} diff --git a/packages/cli/src/generate/fixtures/controller/app.controller.controller-import.ts b/packages/cli/src/generate/fixtures/controller/app.controller.controller-import.ts deleted file mode 100644 index 0708239bd0..0000000000 --- a/packages/cli/src/generate/fixtures/controller/app.controller.controller-import.ts +++ /dev/null @@ -1,4 +0,0 @@ -// App -import { ViewController } from './controllers'; - -export class MyController {} diff --git a/packages/cli/src/generate/fixtures/controller/app.controller.core-import.ts b/packages/cli/src/generate/fixtures/controller/app.controller.core-import.ts deleted file mode 100644 index ae19148cfb..0000000000 --- a/packages/cli/src/generate/fixtures/controller/app.controller.core-import.ts +++ /dev/null @@ -1,4 +0,0 @@ -// 3p -import { something } from '@foal/core'; - -export class MyController {} diff --git a/packages/cli/src/generate/fixtures/controller/app.controller.empty-property.ts b/packages/cli/src/generate/fixtures/controller/app.controller.empty-property.ts deleted file mode 100644 index 65d111a3c6..0000000000 --- a/packages/cli/src/generate/fixtures/controller/app.controller.empty-property.ts +++ /dev/null @@ -1,6 +0,0 @@ -// 3p -import {} from 'somewhere'; - -export class MyController { - subControllers = []; -} diff --git a/packages/cli/src/generate/fixtures/controller/app.controller.empty-spaced-property.ts b/packages/cli/src/generate/fixtures/controller/app.controller.empty-spaced-property.ts deleted file mode 100644 index 9c02d968a7..0000000000 --- a/packages/cli/src/generate/fixtures/controller/app.controller.empty-spaced-property.ts +++ /dev/null @@ -1,8 +0,0 @@ -// 3p -import {} from 'somewhere'; - -export class MyController { - subControllers = [ - - ]; -} diff --git a/packages/cli/src/generate/fixtures/controller/app.controller.no-controller-import.ts b/packages/cli/src/generate/fixtures/controller/app.controller.no-controller-import.ts deleted file mode 100644 index 34dee3c713..0000000000 --- a/packages/cli/src/generate/fixtures/controller/app.controller.no-controller-import.ts +++ /dev/null @@ -1,4 +0,0 @@ -// 3p -import { Something } from '@somewhere'; - -export class MyController {} diff --git a/packages/cli/src/generate/fixtures/controller/app.controller.no-empty-property.ts b/packages/cli/src/generate/fixtures/controller/app.controller.no-empty-property.ts deleted file mode 100644 index 8a6921dd94..0000000000 --- a/packages/cli/src/generate/fixtures/controller/app.controller.no-empty-property.ts +++ /dev/null @@ -1,9 +0,0 @@ -// 3p -import { controller } from '@foal/core'; - -export class MyController { - subControllers = [ - controller('/', MyController), - controller('/', MyController2) - ]; -} diff --git a/packages/cli/src/generate/fixtures/controller/app.controller.no-import.ts b/packages/cli/src/generate/fixtures/controller/app.controller.no-import.ts deleted file mode 100644 index 273000c0fb..0000000000 --- a/packages/cli/src/generate/fixtures/controller/app.controller.no-import.ts +++ /dev/null @@ -1 +0,0 @@ -export class MyController {} diff --git a/packages/cli/src/generate/fixtures/controller/app.controller.rest.ts b/packages/cli/src/generate/fixtures/controller/app.controller.rest.ts deleted file mode 100644 index 65d111a3c6..0000000000 --- a/packages/cli/src/generate/fixtures/controller/app.controller.rest.ts +++ /dev/null @@ -1,6 +0,0 @@ -// 3p -import {} from 'somewhere'; - -export class MyController { - subControllers = []; -} diff --git a/packages/cli/src/generate/fixtures/controller/app.controller.ts b/packages/cli/src/generate/fixtures/controller/app.controller.ts new file mode 100644 index 0000000000..b73f970b12 --- /dev/null +++ b/packages/cli/src/generate/fixtures/controller/app.controller.ts @@ -0,0 +1,9 @@ +// 3p +import { MyController, MyController2 } from './controllers'; + +export class AppController { + subControllers = [ + controller('/', MyController), + controller('/', MyController2), + ]; +} diff --git a/packages/cli/src/generate/fixtures/rest-api/app.controller.controller-import.ts b/packages/cli/src/generate/fixtures/rest-api/app.controller.controller-import.ts deleted file mode 100644 index 0708239bd0..0000000000 --- a/packages/cli/src/generate/fixtures/rest-api/app.controller.controller-import.ts +++ /dev/null @@ -1,4 +0,0 @@ -// App -import { ViewController } from './controllers'; - -export class MyController {} diff --git a/packages/cli/src/generate/fixtures/rest-api/app.controller.core-import.ts b/packages/cli/src/generate/fixtures/rest-api/app.controller.core-import.ts deleted file mode 100644 index ae19148cfb..0000000000 --- a/packages/cli/src/generate/fixtures/rest-api/app.controller.core-import.ts +++ /dev/null @@ -1,4 +0,0 @@ -// 3p -import { something } from '@foal/core'; - -export class MyController {} diff --git a/packages/cli/src/generate/fixtures/rest-api/app.controller.empty-property.ts b/packages/cli/src/generate/fixtures/rest-api/app.controller.empty-property.ts deleted file mode 100644 index 65d111a3c6..0000000000 --- a/packages/cli/src/generate/fixtures/rest-api/app.controller.empty-property.ts +++ /dev/null @@ -1,6 +0,0 @@ -// 3p -import {} from 'somewhere'; - -export class MyController { - subControllers = []; -} diff --git a/packages/cli/src/generate/fixtures/rest-api/app.controller.empty-spaced-property.ts b/packages/cli/src/generate/fixtures/rest-api/app.controller.empty-spaced-property.ts deleted file mode 100644 index 9c02d968a7..0000000000 --- a/packages/cli/src/generate/fixtures/rest-api/app.controller.empty-spaced-property.ts +++ /dev/null @@ -1,8 +0,0 @@ -// 3p -import {} from 'somewhere'; - -export class MyController { - subControllers = [ - - ]; -} diff --git a/packages/cli/src/generate/fixtures/rest-api/app.controller.no-controller-import.ts b/packages/cli/src/generate/fixtures/rest-api/app.controller.no-controller-import.ts deleted file mode 100644 index 34dee3c713..0000000000 --- a/packages/cli/src/generate/fixtures/rest-api/app.controller.no-controller-import.ts +++ /dev/null @@ -1,4 +0,0 @@ -// 3p -import { Something } from '@somewhere'; - -export class MyController {} diff --git a/packages/cli/src/generate/fixtures/rest-api/app.controller.no-empty-property.ts b/packages/cli/src/generate/fixtures/rest-api/app.controller.no-empty-property.ts deleted file mode 100644 index 8a6921dd94..0000000000 --- a/packages/cli/src/generate/fixtures/rest-api/app.controller.no-empty-property.ts +++ /dev/null @@ -1,9 +0,0 @@ -// 3p -import { controller } from '@foal/core'; - -export class MyController { - subControllers = [ - controller('/', MyController), - controller('/', MyController2) - ]; -} diff --git a/packages/cli/src/generate/fixtures/rest-api/app.controller.ts b/packages/cli/src/generate/fixtures/rest-api/app.controller.ts new file mode 100644 index 0000000000..b73f970b12 --- /dev/null +++ b/packages/cli/src/generate/fixtures/rest-api/app.controller.ts @@ -0,0 +1,9 @@ +// 3p +import { MyController, MyController2 } from './controllers'; + +export class AppController { + subControllers = [ + controller('/', MyController), + controller('/', MyController2), + ]; +} diff --git a/packages/cli/src/generate/generators/controller/create-controller.spec.ts b/packages/cli/src/generate/generators/controller/create-controller.spec.ts index c9a6e4b25f..ff045e6960 100644 --- a/packages/cli/src/generate/generators/controller/create-controller.spec.ts +++ b/packages/cli/src/generate/generators/controller/create-controller.spec.ts @@ -63,86 +63,25 @@ describe('createController', () => { .cd('..'); }); - it('should add all the imports if none exists.', () => { + it('should register the controller in app.controller.ts.', () => { fs - .copyFixture('controller/app.controller.no-import.ts', 'app.controller.ts'); + .copyFixture('controller/app.controller.ts', 'app.controller.ts'); createController({ name: 'test-fooBar', register: true }); fs - .assertEqual('app.controller.ts', 'controller/app.controller.no-import.ts'); + .assertEqual('app.controller.ts', 'controller/app.controller.ts'); }); - it('should add all the imports if none exists (subdir).', () => { + it('should register the controller in a parent controller (subdir).', () => { fs - .ensureDir('controllers/barfoo') - .copyFixture('controller/app.controller.no-import.ts', 'controllers/barfoo/hello.controller.ts'); + .ensureDir('controllers/hello') + .copyFixture('controller/api.controller.ts', 'controllers/hello/api.controller.ts'); - createController({ name: 'barfoo/hello/test-fooBar', register: true }); + createController({ name: 'hello/api/test-fooBar', register: true }); fs - .assertEqual('controllers/barfoo/hello.controller.ts', 'controller/app.controller.no-import.ts'); - }); - - it('should update the "subControllers" import in src/app/app.controller.ts if it exists.', () => { - fs - .copyFixture('controller/app.controller.controller-import.ts', 'app.controller.ts'); - - createController({ name: 'test-fooBar', register: true }); - - fs - .assertEqual('app.controller.ts', 'controller/app.controller.controller-import.ts'); - }); - - it('should add a "subControllers" import in src/app/app.controller.ts if none already exists.', () => { - fs - .copyFixture('controller/app.controller.no-controller-import.ts', 'app.controller.ts'); - - createController({ name: 'test-fooBar', register: true }); - - fs - .assertEqual('app.controller.ts', 'controller/app.controller.no-controller-import.ts'); - }); - - it('should update the "@foal/core" import in src/app/app.controller.ts if it exists.', () => { - fs - .copyFixture('controller/app.controller.core-import.ts', 'app.controller.ts'); - - createController({ name: 'test-fooBar', register: true }); - - fs - .assertEqual('app.controller.ts', 'controller/app.controller.core-import.ts'); - }); - - it('should update the "subControllers = []" property in src/app/app.controller.ts if it exists.', () => { - fs - .copyFixture('controller/app.controller.empty-property.ts', 'app.controller.ts'); - - createController({ name: 'test-fooBar', register: true }); - - fs - .assertEqual('app.controller.ts', 'controller/app.controller.empty-property.ts'); - }); - - it('should update the "subControllers = [ \\n \\n ]" property in src/app/app.controller.ts if it exists.', () => { - fs - .copyFixture('controller/app.controller.empty-spaced-property.ts', 'app.controller.ts'); - - createController({ name: 'test-fooBar', register: true }); - - fs - .assertEqual('app.controller.ts', 'controller/app.controller.empty-spaced-property.ts'); - }); - - it('should update the "subControllers = [ \\n (.*) \\n ]" property in' - + ' src/app/app.controller.ts if it exists.', () => { - fs - .copyFixture('controller/app.controller.no-empty-property.ts', 'app.controller.ts'); - - createController({ name: 'test-fooBar', register: true }); - - fs - .assertEqual('app.controller.ts', 'controller/app.controller.no-empty-property.ts'); + .assertEqual('controllers/hello/api.controller.ts', 'controller/api.controller.ts'); }); }); diff --git a/packages/cli/src/generate/generators/controller/create-controller.ts b/packages/cli/src/generate/generators/controller/create-controller.ts index a293fb82e5..61090cce5d 100644 --- a/packages/cli/src/generate/generators/controller/create-controller.ts +++ b/packages/cli/src/generate/generators/controller/create-controller.ts @@ -2,7 +2,6 @@ import { basename, dirname } from 'path'; import { FileSystem } from '../../file-system'; import { getNames } from '../../utils'; -import { registerController } from './register-controller'; export function createController({ name, register }: { name: string, register: boolean }) { const fs = new FileSystem(); @@ -30,10 +29,25 @@ export function createController({ name, register }: { name: string, register: b .render('controller/controller.empty.ts', fileName, names) .render('controller/controller.spec.empty.ts', specFileName, names) .ensureFile('index.ts') - .addNamedExportIn('index.ts', className, `./${names.kebabName}.controller`) - .cd('..') - .modifyOnlyfIf(register, parentControllerPath, content => { - const path = `/${names.kebabName}`; - return registerController(content, className, path); - }); + .addNamedExportIn('index.ts', className, `./${names.kebabName}.controller`); + + if (register) { + fs + .cd('..') + .addOrExtendNamedImportIn( + parentControllerPath, + 'controller', + '@foal/core', + ) + .addOrExtendNamedImportIn( + parentControllerPath, + className, + `./${subdir === '.' ? 'controllers' : basename(subdir)}` + ) + .addOrExtendClassArrayPropertyIn( + parentControllerPath, + 'subControllers', + `controller('/${names.kebabName}', ${className})` + ); + } } diff --git a/packages/cli/src/generate/generators/controller/register-controller.ts b/packages/cli/src/generate/generators/controller/register-controller.ts deleted file mode 100644 index 49bb1d9a6d..0000000000 --- a/packages/cli/src/generate/generators/controller/register-controller.ts +++ /dev/null @@ -1,61 +0,0 @@ -class ImportNotFound extends Error {} - -function createNamedImport(specifiers: string[], path: string): string { - return `import { ${specifiers.join(', ')} } from '${path}';`; -} - -function addImport(fileContent: string, importDeclaration: string): string { - const regex = new RegExp('import (.*) from (.*);', 'g'); - let lastOccurence: RegExpExecArray|undefined; - let lastExec: RegExpExecArray|null; - while ((lastExec = regex.exec(fileContent)) !== null) { - lastOccurence = lastExec; - } - if (lastOccurence === undefined) { - return `${importDeclaration}\n\n${fileContent}`; - } - const endPos = lastOccurence.index + lastOccurence[0].length; - return fileContent.substr(0, endPos) + '\n' + importDeclaration + fileContent.substr(endPos); -} - -function extendImport(fileContent: string, path: string, specifier: string): string { - const pathRegex = path.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); - const importRegex = new RegExp(`import {(.*)} from \'${pathRegex}\';`); - - if (!importRegex.test(fileContent)) { - throw new ImportNotFound(); - } - - return fileContent - .replace(importRegex, (str, content: string) => { - const importSpecifiers = content.split(',').map(imp => imp.trim()); - if (importSpecifiers.includes(specifier)) { - return str; - } - importSpecifiers.push(specifier); - importSpecifiers.sort((a, b) => a.localeCompare(b)); - return createNamedImport(importSpecifiers, path); - }); -} - -function addOrExtendImport(fileContent: string, specifier: string, path: string): string { - try { - return extendImport(fileContent, path, specifier); - } catch (err) { - const namedImport = createNamedImport([ specifier ], path); - return addImport(fileContent, namedImport); - } -} - -export function registerController(fileContent: string, controllerName: string, path: string): string { - fileContent = addOrExtendImport(fileContent, controllerName, './controllers'); - fileContent = addOrExtendImport(fileContent, 'controller', '@foal/core'); - return fileContent - .replace(/( *)subControllers = \[((.|\n)*)\];/, (str, spaces, content: string) => { - const regex = /controller\((.*)\)/g; - const controllerCalls = content.match(regex) || []; - controllerCalls.push(`controller('${path}', ${controllerName})`); - const formattedCalls = controllerCalls.join(`,\n${spaces} `); - return `${spaces}subControllers = [\n${spaces} ${formattedCalls}\n${spaces}];`; - }); -} diff --git a/packages/cli/src/generate/generators/rest-api/create-rest-api.spec.ts b/packages/cli/src/generate/generators/rest-api/create-rest-api.spec.ts index 9eea2c1651..936313d0a9 100644 --- a/packages/cli/src/generate/generators/rest-api/create-rest-api.spec.ts +++ b/packages/cli/src/generate/generators/rest-api/create-rest-api.spec.ts @@ -72,66 +72,16 @@ describe('createRestApi', () => { .cd('..'); }); - it('should update the "subControllers" import in src/app/app.controller.ts if it exists.', () => { + it('should register the controller in app.controller.ts.', () => { fs - .copyFixture('rest-api/app.controller.controller-import.ts', 'app.controller.ts'); - - createRestApi({ name: 'test-fooBar', register: true }); - - fs - .assertEqual('app.controller.ts', 'rest-api/app.controller.controller-import.ts'); - }); - - it('should add a "subControllers" import in src/app/app.controller.ts if none already exists.', () => { - fs - .copyFixture('rest-api/app.controller.no-controller-import.ts', 'app.controller.ts'); - - createRestApi({ name: 'test-fooBar', register: true }); - - fs - .assertEqual('app.controller.ts', 'rest-api/app.controller.no-controller-import.ts'); - }); - - it('should update the "@foal/core" import in src/app/app.controller.ts if it exists.', () => { - fs - .copyFixture('rest-api/app.controller.core-import.ts', 'app.controller.ts'); - - createRestApi({ name: 'test-fooBar', register: true }); - - fs - .assertEqual('app.controller.ts', 'rest-api/app.controller.core-import.ts'); - }); - - it('should update the "subControllers = []" property in src/app/app.controller.ts if it exists.', () => { - fs - .copyFixture('rest-api/app.controller.empty-property.ts', 'app.controller.ts'); - - createRestApi({ name: 'test-fooBar', register: true }); - - fs - .assertEqual('app.controller.ts', 'rest-api/app.controller.empty-property.ts'); - }); - - it('should update the "subControllers = [ \\n \\n ]" property in src/app/app.controller.ts if it exists.', () => { - fs - .copyFixture('rest-api/app.controller.empty-spaced-property.ts', 'app.controller.ts'); - + .copyFixture('rest-api/app.controller.ts', 'app.controller.ts'); + createRestApi({ name: 'test-fooBar', register: true }); - + fs - .assertEqual('app.controller.ts', 'rest-api/app.controller.empty-spaced-property.ts'); + .assertEqual('app.controller.ts', 'rest-api/app.controller.ts'); }); - it('should update the "subControllers = [ \\n (.*) \\n ]" property in' - + ' src/app/app.controller.ts if it exists.', () => { - fs - .copyFixture('rest-api/app.controller.no-empty-property.ts', 'app.controller.ts'); - - createRestApi({ name: 'test-fooBar', register: true }); - - fs - .assertEqual('app.controller.ts', 'rest-api/app.controller.no-empty-property.ts'); - }); }); diff --git a/packages/cli/src/generate/generators/rest-api/create-rest-api.ts b/packages/cli/src/generate/generators/rest-api/create-rest-api.ts index f77595f917..960d26c28a 100644 --- a/packages/cli/src/generate/generators/rest-api/create-rest-api.ts +++ b/packages/cli/src/generate/generators/rest-api/create-rest-api.ts @@ -8,7 +8,6 @@ import { red, underline } from 'colors/safe'; // FoalTS import { FileSystem } from '../../file-system'; import { findProjectPath, getNames } from '../../utils'; -import { registerController } from '../controller/register-controller'; export function createRestApi({ name, register }: { name: string, register: boolean }) { const projectPath = findProjectPath(); @@ -37,6 +36,8 @@ export function createRestApi({ name, register }: { name: string, register: bool const names = getNames(name); + const className = `${names.upperFirstCamelName}Controller`; + fs .cd(entityRoot) .render('rest-api/entity.ts', `${names.kebabName}.entity.ts`, names) @@ -58,11 +59,27 @@ export function createRestApi({ name, register }: { name: string, register: bool names, ) .ensureFile('index.ts') - .addNamedExportIn('index.ts', `${names.upperFirstCamelName}Controller`, `./${names.kebabName}.controller`) - .cd('..') - .modifyOnlyfIf(register, 'app.controller.ts', content => { - return registerController(content, `${names.upperFirstCamelName}Controller`, `/${names.kebabName}s`); - }); + .addNamedExportIn('index.ts', className, `./${names.kebabName}.controller`); + + if (register) { + fs + .cd('..') + .addOrExtendNamedImportIn( + 'app.controller.ts', + 'controller', + '@foal/core', + ) + .addOrExtendNamedImportIn( + 'app.controller.ts', + className, + './controllers' + ) + .addOrExtendClassArrayPropertyIn( + 'app.controller.ts', + 'subControllers', + `controller('/${names.kebabName}s', ${className})` + ); + } if (process.env.P1Z7kEbSUUPMxF8GqPwD8Gx_FOAL_CLI_TEST !== 'true') { console.log( diff --git a/packages/cli/src/generate/specs/controller/app.controller.no-empty-property.ts b/packages/cli/src/generate/specs/controller/api.controller.ts similarity index 65% rename from packages/cli/src/generate/specs/controller/app.controller.no-empty-property.ts rename to packages/cli/src/generate/specs/controller/api.controller.ts index 1a58d3d483..41f18dbce1 100644 --- a/packages/cli/src/generate/specs/controller/app.controller.no-empty-property.ts +++ b/packages/cli/src/generate/specs/controller/api.controller.ts @@ -1,8 +1,8 @@ // 3p +import { MyController, MyController2, TestFooBarController } from './api'; import { controller } from '@foal/core'; -import { TestFooBarController } from './controllers'; -export class MyController { +export class ApiController { subControllers = [ controller('/', MyController), controller('/', MyController2), diff --git a/packages/cli/src/generate/specs/controller/app.controller.controller-import.ts b/packages/cli/src/generate/specs/controller/app.controller.controller-import.ts deleted file mode 100644 index 1216d179ca..0000000000 --- a/packages/cli/src/generate/specs/controller/app.controller.controller-import.ts +++ /dev/null @@ -1,5 +0,0 @@ -// App -import { TestFooBarController, ViewController } from './controllers'; -import { controller } from '@foal/core'; - -export class MyController {} diff --git a/packages/cli/src/generate/specs/controller/app.controller.core-import.ts b/packages/cli/src/generate/specs/controller/app.controller.core-import.ts deleted file mode 100644 index 180e176d29..0000000000 --- a/packages/cli/src/generate/specs/controller/app.controller.core-import.ts +++ /dev/null @@ -1,5 +0,0 @@ -// 3p -import { controller, something } from '@foal/core'; -import { TestFooBarController } from './controllers'; - -export class MyController {} diff --git a/packages/cli/src/generate/specs/controller/app.controller.empty-property.ts b/packages/cli/src/generate/specs/controller/app.controller.empty-property.ts deleted file mode 100644 index 5f560d0043..0000000000 --- a/packages/cli/src/generate/specs/controller/app.controller.empty-property.ts +++ /dev/null @@ -1,10 +0,0 @@ -// 3p -import {} from 'somewhere'; -import { TestFooBarController } from './controllers'; -import { controller } from '@foal/core'; - -export class MyController { - subControllers = [ - controller('/test-foo-bar', TestFooBarController) - ]; -} diff --git a/packages/cli/src/generate/specs/controller/app.controller.empty-spaced-property.ts b/packages/cli/src/generate/specs/controller/app.controller.empty-spaced-property.ts deleted file mode 100644 index 5f560d0043..0000000000 --- a/packages/cli/src/generate/specs/controller/app.controller.empty-spaced-property.ts +++ /dev/null @@ -1,10 +0,0 @@ -// 3p -import {} from 'somewhere'; -import { TestFooBarController } from './controllers'; -import { controller } from '@foal/core'; - -export class MyController { - subControllers = [ - controller('/test-foo-bar', TestFooBarController) - ]; -} diff --git a/packages/cli/src/generate/specs/controller/app.controller.no-controller-import.ts b/packages/cli/src/generate/specs/controller/app.controller.no-controller-import.ts deleted file mode 100644 index 1b2bea7108..0000000000 --- a/packages/cli/src/generate/specs/controller/app.controller.no-controller-import.ts +++ /dev/null @@ -1,6 +0,0 @@ -// 3p -import { Something } from '@somewhere'; -import { TestFooBarController } from './controllers'; -import { controller } from '@foal/core'; - -export class MyController {} diff --git a/packages/cli/src/generate/specs/controller/app.controller.no-import.ts b/packages/cli/src/generate/specs/controller/app.controller.no-import.ts deleted file mode 100644 index 88ee2e4427..0000000000 --- a/packages/cli/src/generate/specs/controller/app.controller.no-import.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { TestFooBarController } from './controllers'; -import { controller } from '@foal/core'; - -export class MyController {} diff --git a/packages/cli/src/generate/specs/controller/app.controller.rest.ts b/packages/cli/src/generate/specs/controller/app.controller.rest.ts deleted file mode 100644 index 4c250d8151..0000000000 --- a/packages/cli/src/generate/specs/controller/app.controller.rest.ts +++ /dev/null @@ -1,10 +0,0 @@ -// 3p -import {} from 'somewhere'; -import { TestFooBarController } from './controllers'; -import { controller } from '@foal/core'; - -export class MyController { - subControllers = [ - controller('/test-foo-bars', TestFooBarController) - ]; -} diff --git a/packages/cli/src/generate/specs/controller/app.controller.ts b/packages/cli/src/generate/specs/controller/app.controller.ts new file mode 100644 index 0000000000..787f9cf306 --- /dev/null +++ b/packages/cli/src/generate/specs/controller/app.controller.ts @@ -0,0 +1,11 @@ +// 3p +import { MyController, MyController2, TestFooBarController } from './controllers'; +import { controller } from '@foal/core'; + +export class AppController { + subControllers = [ + controller('/', MyController), + controller('/', MyController2), + controller('/test-foo-bar', TestFooBarController) + ]; +} diff --git a/packages/cli/src/generate/specs/rest-api/app.controller.controller-import.ts b/packages/cli/src/generate/specs/rest-api/app.controller.controller-import.ts deleted file mode 100644 index 1216d179ca..0000000000 --- a/packages/cli/src/generate/specs/rest-api/app.controller.controller-import.ts +++ /dev/null @@ -1,5 +0,0 @@ -// App -import { TestFooBarController, ViewController } from './controllers'; -import { controller } from '@foal/core'; - -export class MyController {} diff --git a/packages/cli/src/generate/specs/rest-api/app.controller.core-import.ts b/packages/cli/src/generate/specs/rest-api/app.controller.core-import.ts deleted file mode 100644 index 180e176d29..0000000000 --- a/packages/cli/src/generate/specs/rest-api/app.controller.core-import.ts +++ /dev/null @@ -1,5 +0,0 @@ -// 3p -import { controller, something } from '@foal/core'; -import { TestFooBarController } from './controllers'; - -export class MyController {} diff --git a/packages/cli/src/generate/specs/rest-api/app.controller.empty-property.ts b/packages/cli/src/generate/specs/rest-api/app.controller.empty-property.ts deleted file mode 100644 index 4c250d8151..0000000000 --- a/packages/cli/src/generate/specs/rest-api/app.controller.empty-property.ts +++ /dev/null @@ -1,10 +0,0 @@ -// 3p -import {} from 'somewhere'; -import { TestFooBarController } from './controllers'; -import { controller } from '@foal/core'; - -export class MyController { - subControllers = [ - controller('/test-foo-bars', TestFooBarController) - ]; -} diff --git a/packages/cli/src/generate/specs/rest-api/app.controller.empty-spaced-property.ts b/packages/cli/src/generate/specs/rest-api/app.controller.empty-spaced-property.ts deleted file mode 100644 index 4c250d8151..0000000000 --- a/packages/cli/src/generate/specs/rest-api/app.controller.empty-spaced-property.ts +++ /dev/null @@ -1,10 +0,0 @@ -// 3p -import {} from 'somewhere'; -import { TestFooBarController } from './controllers'; -import { controller } from '@foal/core'; - -export class MyController { - subControllers = [ - controller('/test-foo-bars', TestFooBarController) - ]; -} diff --git a/packages/cli/src/generate/specs/rest-api/app.controller.no-controller-import.ts b/packages/cli/src/generate/specs/rest-api/app.controller.no-controller-import.ts deleted file mode 100644 index 1b2bea7108..0000000000 --- a/packages/cli/src/generate/specs/rest-api/app.controller.no-controller-import.ts +++ /dev/null @@ -1,6 +0,0 @@ -// 3p -import { Something } from '@somewhere'; -import { TestFooBarController } from './controllers'; -import { controller } from '@foal/core'; - -export class MyController {} diff --git a/packages/cli/src/generate/specs/rest-api/app.controller.no-empty-property.ts b/packages/cli/src/generate/specs/rest-api/app.controller.ts similarity index 64% rename from packages/cli/src/generate/specs/rest-api/app.controller.no-empty-property.ts rename to packages/cli/src/generate/specs/rest-api/app.controller.ts index 76a35ea36a..3efce8aa26 100644 --- a/packages/cli/src/generate/specs/rest-api/app.controller.no-empty-property.ts +++ b/packages/cli/src/generate/specs/rest-api/app.controller.ts @@ -1,8 +1,8 @@ // 3p +import { MyController, MyController2, TestFooBarController } from './controllers'; import { controller } from '@foal/core'; -import { TestFooBarController } from './controllers'; -export class MyController { +export class AppController { subControllers = [ controller('/', MyController), controller('/', MyController2), From 8696b3cf906fce0f1818910f9828cedc654e37cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Tue, 19 May 2020 16:43:35 +0200 Subject: [PATCH 56/92] [CLI] Do not re-add specifiers --- packages/cli/src/generate/file-system.spec.ts | 14 +++++++++++++- packages/cli/src/generate/file-system.ts | 18 +++++++++++++++--- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/generate/file-system.spec.ts b/packages/cli/src/generate/file-system.spec.ts index ff4988832d..1ed6232417 100644 --- a/packages/cli/src/generate/file-system.spec.ts +++ b/packages/cli/src/generate/file-system.spec.ts @@ -443,7 +443,7 @@ describe('FileSystem', () => { ); }); - it('should extend the named import if it already exist.', () => { + it('should extend the named import if it already exists and it does not have the specifier.', () => { fs.addOrExtendNamedImportIn('hello.txt', 'MyController', './bar.txt'); strictEqual( readFileSync('test-generators/hello.txt', 'utf8'), @@ -455,6 +455,18 @@ describe('FileSystem', () => { ); }); + it('should not extend the named import if it already exists but it has already the specifier.', () => { + fs.addOrExtendNamedImportIn('hello.txt', 'World', './bar.txt'); + strictEqual( + readFileSync('test-generators/hello.txt', 'utf8'), + '// 3p\n' + + 'import { Hello } from \'./foo.txt\';\n' + + 'import { World } from \'./bar.txt\';\n' + + '\n' + + 'class FooBar {}', + ); + }); + }); describe('has an "addOrExtendClassArrayProperty" method that should', () => { diff --git a/packages/cli/src/generate/file-system.ts b/packages/cli/src/generate/file-system.ts index 7b53403f65..907f5d8a8c 100644 --- a/packages/cli/src/generate/file-system.ts +++ b/packages/cli/src/generate/file-system.ts @@ -272,16 +272,24 @@ export class FileSystem { const regex = /import (.*) from '(.*)';/g; let endPos = 0; + let specifierAlreadyExists = false; const replacedContent = content.replace(regex, (match, p1, p2, offset: number) => { endPos = offset + match.length; const namedImportRegex = new RegExp(`import {(.*)} from \'(.*)\';`); - return match.replace(namedImportRegex, (subString, specifiers: string, path: string) => { + return match.replace(namedImportRegex, (subString, specifiersStr: string, path: string) => { if (path !== source) { return subString; } - const newSpecifiers = specifiers + const specifiers = specifiersStr .split(',') - .map(imp => imp.trim()) + .map(imp => imp.trim()); + + if (specifiers.includes(specifier)) { + specifierAlreadyExists = true; + return subString; + } + + const newSpecifiers = specifiers .concat(specifier) .sort((a, b) => a.localeCompare(b)) .join(', '); @@ -289,6 +297,10 @@ export class FileSystem { }); }); + if (specifierAlreadyExists) { + return content; + } + if (replacedContent !== content) { return replacedContent; } From 13ca2ff0247094d2942b2314ea7ad62a05a46c46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Tue, 19 May 2020 16:47:04 +0200 Subject: [PATCH 57/92] Fix linting --- packages/cli/src/generate/file-system.ts | 2 +- .../src/generate/generators/rest-api/create-rest-api.spec.ts | 5 ++--- tslint.json | 3 ++- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/cli/src/generate/file-system.ts b/packages/cli/src/generate/file-system.ts index 907f5d8a8c..c701832f0d 100644 --- a/packages/cli/src/generate/file-system.ts +++ b/packages/cli/src/generate/file-system.ts @@ -283,7 +283,7 @@ export class FileSystem { const specifiers = specifiersStr .split(',') .map(imp => imp.trim()); - + if (specifiers.includes(specifier)) { specifierAlreadyExists = true; return subString; diff --git a/packages/cli/src/generate/generators/rest-api/create-rest-api.spec.ts b/packages/cli/src/generate/generators/rest-api/create-rest-api.spec.ts index 936313d0a9..69f6401c90 100644 --- a/packages/cli/src/generate/generators/rest-api/create-rest-api.spec.ts +++ b/packages/cli/src/generate/generators/rest-api/create-rest-api.spec.ts @@ -75,14 +75,13 @@ describe('createRestApi', () => { it('should register the controller in app.controller.ts.', () => { fs .copyFixture('rest-api/app.controller.ts', 'app.controller.ts'); - + createRestApi({ name: 'test-fooBar', register: true }); - + fs .assertEqual('app.controller.ts', 'rest-api/app.controller.ts'); }); - }); } diff --git a/tslint.json b/tslint.json index c7e73a1615..9d6b58706f 100644 --- a/tslint.json +++ b/tslint.json @@ -28,7 +28,8 @@ "**/src/generate/fixtures/**/*.ts", "**/src/generate/templates/model/*.ts", "**/src/generate/templates/rest-api/*.ts", - "**/src/generate/specs/**/app.controller.*.ts", + "**/src/generate/specs/**/app.controller.ts", + "**/src/generate/specs/**/api.controller.ts", "**/src/generate/specs/entity/test-foo-bar.entity.ts", "**/src/generate/templates/entity/entity.ts" ] From 93bb81475e1b6cd979a2b7bc0197e6063847d2af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Tue, 19 May 2020 17:00:04 +0200 Subject: [PATCH 58/92] [CLI] Fix build issue --- packages/cli/tsconfig-build.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/tsconfig-build.json b/packages/cli/tsconfig-build.json index 9228b97e30..6d04566c8e 100644 --- a/packages/cli/tsconfig-build.json +++ b/packages/cli/tsconfig-build.json @@ -5,7 +5,7 @@ }, "exclude": [ "./src/**/*.spec.ts", - "./src/generate/mocks/**/*.ts", + "./src/generate/fixtures/**/*.ts", "./src/generate/specs/**/*.ts", "./src/generate/templates/**/*.ts" ] From a639eb0bcf22c3dd432a4fd2da40c01a624b7d5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Wed, 20 May 2020 08:31:28 +0200 Subject: [PATCH 59/92] [CLI] Make tests pass on Node 10 --- packages/cli/src/generate/file-system.spec.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/generate/file-system.spec.ts b/packages/cli/src/generate/file-system.spec.ts index 1ed6232417..b2c4137f68 100644 --- a/packages/cli/src/generate/file-system.spec.ts +++ b/packages/cli/src/generate/file-system.spec.ts @@ -810,7 +810,9 @@ describe('FileSystem', () => { fs.assertEqual('bar.txt', 'test-file-system/foo.spec.txt'); throw new Error('An error should have been thrown.'); } catch (error) { - strictEqual(error.message, '\'hi\\nmy\\nearth\\n!\' === \'hello\\nmy\\nworld\''); + strictEqual(error.code, 'ERR_ASSERTION'); + strictEqual(error.message.includes('\'hi\\nmy\\nearth\\n!\''), true); + strictEqual(error.message.includes('\'hello\\nmy\\nworld\''), true); } }); From 9e5ba13338f19782ef8a5e02e8ebe9134e94fa36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Wed, 20 May 2020 08:53:09 +0200 Subject: [PATCH 60/92] [Docs] foal g support sub-directories --- .../code-generation.md | 79 ++++++++++++++++++- 1 file changed, 77 insertions(+), 2 deletions(-) diff --git a/docs/development-environment/code-generation.md b/docs/development-environment/code-generation.md index e40b14a9f3..5453d0fa0b 100644 --- a/docs/development-environment/code-generation.md +++ b/docs/development-environment/code-generation.md @@ -20,12 +20,69 @@ foal g controller Create a new controller in `./src/app/controllers`, in `./controllers` or in the current directory depending on which folders are found. -If you are in the root directory and you want to automatically register the controller within the app controller you can add the `--register` flag. +*Example* +```shell +foal g controller auth +foal g controller api/product +``` + +*Output* +``` +src/ + '- app/ + '- controllers/ + |- api/ + | |- product.controller.ts + | '- index.ts + |- auth.controller.ts + '- index.ts +``` + +### The `--register` flag ```shell foal g controller --register ``` +If you wish to automatically create a new route attached to this controller, you can use the `--register` flag to do so. + +*Example* +```shell +foal g controller api --register +foal g controller api/auth --register +``` + +*Output* +``` +src/ + '- app/ + |- controllers/ + | |- api/ + | | |- product.controller.ts + | | '- index.ts + | |- api.controller.ts + | '- index.ts + '- app.controller.ts +``` + +*app.controller.ts* +```typescript +export class AppController { + subControllers = [ + controller('/api', ApiController) + ] +} +``` + +*api.controller.ts* +```typescript +export class ApiController { + subControllers = [ + controller('/product', ProductController) + ] +} +``` + ## Create an entity (simple model) ```shell @@ -89,4 +146,22 @@ Create a new sub-app with all its files in `./src/app/sub-apps`, in `./sub-apps` foal g service ``` -Create a new service in `./src/app/services`, in `./services` or in the current directory depending on which folders are found. \ No newline at end of file +Create a new service in `./src/app/services`, in `./services` or in the current directory depending on which folders are found. + +*Example* +```shell +foal g service auth +foal g service apis/github +``` + +*Output* +``` +src/ + '- app/ + '- services/ + |- apis/ + | '- github.service.ts + | '- index.ts + |- auth.service.ts + '- index.ts +``` \ No newline at end of file From e6ea9883f2e5d455356c4d8efab84cda2360efef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Wed, 20 May 2020 09:15:50 +0200 Subject: [PATCH 61/92] [Docs] Fix typo --- docs/development-environment/code-generation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/development-environment/code-generation.md b/docs/development-environment/code-generation.md index 5453d0fa0b..1b734c3202 100644 --- a/docs/development-environment/code-generation.md +++ b/docs/development-environment/code-generation.md @@ -49,7 +49,7 @@ If you wish to automatically create a new route attached to this controller, you *Example* ```shell foal g controller api --register -foal g controller api/auth --register +foal g controller api/product --register ``` *Output* From e91b946cea2aa8d50a7f65f6283c1076af0dc8b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Wed, 20 May 2020 09:24:34 +0200 Subject: [PATCH 62/92] [CLI] Remove dupplicated "UPDATE xxx" --- packages/cli/src/generate/file-system.ts | 21 +++++++++++++++++-- .../controller/create-controller.ts | 4 +++- .../generators/rest-api/create-rest-api.ts | 4 +++- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/generate/file-system.ts b/packages/cli/src/generate/file-system.ts index c701832f0d..58f55a3f01 100644 --- a/packages/cli/src/generate/file-system.ts +++ b/packages/cli/src/generate/file-system.ts @@ -266,7 +266,12 @@ export class FileSystem { * @returns {this} * @memberof FileSystem */ - addOrExtendNamedImportIn(path: string, specifier: string, source: string): this { + addOrExtendNamedImportIn(path: string, specifier: string, source: string, options?: { logs: boolean }): this { + const initialLogs = this.logs; + if (options) { + this.logs = options.logs; + } + this.modify(path, content => { // TODO: add tests to support double quotes. const regex = /import (.*) from '(.*)';/g; @@ -313,6 +318,8 @@ export class FileSystem { return content.substr(0, endPos) + '\n' + newImport + content.substr(endPos); }); + this.logs = initialLogs; + return this; } @@ -328,7 +335,14 @@ export class FileSystem { * @returns {this} * @memberof FileSystem */ - addOrExtendClassArrayPropertyIn(path: string, propertyName: string, element: string): this { + addOrExtendClassArrayPropertyIn( + path: string, propertyName: string, element: string, options?: { logs: boolean } + ): this { + const initialLogs = this.logs; + if (options) { + this.logs = options.logs; + } + this.modify(path, content => content.replace( new RegExp(`class (\\w*) {(.*)}`, 's'), (match, className: string, p2: string) => { @@ -363,6 +377,9 @@ export class FileSystem { return `class ${className} {\n ${propertyName} = [\n ${element}\n ];\n${p2}}`; } )); + + this.logs = initialLogs; + return this; } diff --git a/packages/cli/src/generate/generators/controller/create-controller.ts b/packages/cli/src/generate/generators/controller/create-controller.ts index 61090cce5d..d1bb7f1762 100644 --- a/packages/cli/src/generate/generators/controller/create-controller.ts +++ b/packages/cli/src/generate/generators/controller/create-controller.ts @@ -38,11 +38,13 @@ export function createController({ name, register }: { name: string, register: b parentControllerPath, 'controller', '@foal/core', + { logs: false } ) .addOrExtendNamedImportIn( parentControllerPath, className, - `./${subdir === '.' ? 'controllers' : basename(subdir)}` + `./${subdir === '.' ? 'controllers' : basename(subdir)}`, + { logs: false } ) .addOrExtendClassArrayPropertyIn( parentControllerPath, diff --git a/packages/cli/src/generate/generators/rest-api/create-rest-api.ts b/packages/cli/src/generate/generators/rest-api/create-rest-api.ts index 960d26c28a..4f54e1b241 100644 --- a/packages/cli/src/generate/generators/rest-api/create-rest-api.ts +++ b/packages/cli/src/generate/generators/rest-api/create-rest-api.ts @@ -68,11 +68,13 @@ export function createRestApi({ name, register }: { name: string, register: bool 'app.controller.ts', 'controller', '@foal/core', + { logs: false } ) .addOrExtendNamedImportIn( 'app.controller.ts', className, - './controllers' + './controllers', + { logs: false } ) .addOrExtendClassArrayPropertyIn( 'app.controller.ts', From 91414f1fd497040595e13d38479f584c1d8360f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Wed, 20 May 2020 09:30:25 +0200 Subject: [PATCH 63/92] [CLI] Fix installation with yarn on node 8 --- packages/cli/src/generate/generators/app/create-app.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/generate/generators/app/create-app.ts b/packages/cli/src/generate/generators/app/create-app.ts index 7f6ad83d4f..1aca1121e8 100644 --- a/packages/cli/src/generate/generators/app/create-app.ts +++ b/packages/cli/src/generate/generators/app/create-app.ts @@ -165,7 +165,8 @@ export async function createApp({ name, autoInstall, initRepo, mongodb = false, log(''); log(' 📦 Installing the dependencies...'); const packageManager = isYarnInstalled() ? 'yarn' : 'npm'; - const args = [ 'install' ]; + // TODO: in version 2, remove the hack "--ignore-engines" + const args = [ 'install', '--ignore-engines' ]; const options: SpawnOptions = { cwd: names.kebabName, shell: true, From c0acc543caa23b1f699d8fa9bc01eeea0a9762ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Wed, 20 May 2020 15:40:56 +0200 Subject: [PATCH 64/92] [CLI] Be more strict on dep versions. --- .../cli/src/generate/specs/app/package.json | 22 ++++++++--------- .../generate/specs/app/package.mongodb.json | 20 ++++++++-------- .../specs/app/package.mongodb.yaml.json | 22 ++++++++--------- .../src/generate/specs/app/package.yaml.json | 24 +++++++++---------- .../src/generate/templates/app/package.json | 22 ++++++++--------- .../templates/app/package.mongodb.json | 20 ++++++++-------- .../templates/app/package.mongodb.yaml.json | 22 ++++++++--------- .../generate/templates/app/package.yaml.json | 24 +++++++++---------- 8 files changed, 88 insertions(+), 88 deletions(-) diff --git a/packages/cli/src/generate/specs/app/package.json b/packages/cli/src/generate/specs/app/package.json index cbde81f642..fa5fe73764 100644 --- a/packages/cli/src/generate/specs/app/package.json +++ b/packages/cli/src/generate/specs/app/package.json @@ -33,19 +33,19 @@ "dependencies": { "@foal/core": "^1.0.0", "@foal/typeorm": "^1.0.0", - "source-map-support": "^0.5.1", - "sqlite3": "^4.0.0", - "typeorm": "^0.2.6" + "source-map-support": "~0.5.1", + "sqlite3": "~4.0.0", + "typeorm": "0.2.24" }, "devDependencies": { - "@types/mocha": "^5.2.1", - "@types/node": "^8.0.47", - "concurrently": "^3.5.1", - "copy": "^0.3.2", - "mocha": "^5.2.0", - "supertest": "^3.3.0", - "supervisor": "^0.12.0", - "eslint": "^6.7.0", + "@types/mocha": "5.2.1", + "@types/node": "8.0.47", + "concurrently": "~3.5.1", + "copy": "~0.3.2", + "mocha": "~5.2.0", + "supertest": "~3.3.0", + "supervisor": "~0.12.0", + "eslint": "~6.7.0", "@typescript-eslint/eslint-plugin": "~2.7.0", "@typescript-eslint/parser": "~2.7.0", "typescript": "~3.5.3" diff --git a/packages/cli/src/generate/specs/app/package.mongodb.json b/packages/cli/src/generate/specs/app/package.mongodb.json index 46cd57ed86..315b811c99 100644 --- a/packages/cli/src/generate/specs/app/package.mongodb.json +++ b/packages/cli/src/generate/specs/app/package.mongodb.json @@ -29,18 +29,18 @@ "dependencies": { "@foal/core": "^1.0.0", "@foal/mongoose": "^1.0.0", - "mongoose": "^5.4.9", - "source-map-support": "^0.5.1" + "mongoose": "~5.4.9", + "source-map-support": "~0.5.1" }, "devDependencies": { - "@types/mocha": "^5.2.1", - "@types/node": "^8.0.47", - "concurrently": "^3.5.1", - "copy": "^0.3.2", - "mocha": "^5.2.0", - "supertest": "^3.3.0", - "supervisor": "^0.12.0", - "eslint": "^6.7.0", + "@types/mocha": "5.2.1", + "@types/node": "8.0.47", + "concurrently": "~3.5.1", + "copy": "~0.3.2", + "mocha": "~5.2.0", + "supertest": "~3.3.0", + "supervisor": "~0.12.0", + "eslint": "~6.7.0", "@typescript-eslint/eslint-plugin": "~2.7.0", "@typescript-eslint/parser": "~2.7.0", "typescript": "~3.5.3" diff --git a/packages/cli/src/generate/specs/app/package.mongodb.yaml.json b/packages/cli/src/generate/specs/app/package.mongodb.yaml.json index f60c59df30..427e57bce0 100644 --- a/packages/cli/src/generate/specs/app/package.mongodb.yaml.json +++ b/packages/cli/src/generate/specs/app/package.mongodb.yaml.json @@ -29,19 +29,19 @@ "dependencies": { "@foal/core": "^1.0.0", "@foal/mongoose": "^1.0.0", - "mongoose": "^5.4.9", - "source-map-support": "^0.5.1", - "yamljs": "^0.3.0" + "mongoose": "~5.4.9", + "source-map-support": "~0.5.1", + "yamljs": "~0.3.0" }, "devDependencies": { - "@types/mocha": "^5.2.1", - "@types/node": "^8.0.47", - "concurrently": "^3.5.1", - "copy": "^0.3.2", - "mocha": "^5.2.0", - "supertest": "^3.3.0", - "supervisor": "^0.12.0", - "eslint": "^6.7.0", + "@types/mocha": "5.2.1", + "@types/node": "8.0.47", + "concurrently": "~3.5.1", + "copy": "~0.3.2", + "mocha": "~5.2.0", + "supertest": "~3.3.0", + "supervisor": "~0.12.0", + "eslint": "~6.7.0", "@typescript-eslint/eslint-plugin": "~2.7.0", "@typescript-eslint/parser": "~2.7.0", "typescript": "~3.5.3" diff --git a/packages/cli/src/generate/specs/app/package.yaml.json b/packages/cli/src/generate/specs/app/package.yaml.json index 25f17818c1..b180076346 100644 --- a/packages/cli/src/generate/specs/app/package.yaml.json +++ b/packages/cli/src/generate/specs/app/package.yaml.json @@ -33,20 +33,20 @@ "dependencies": { "@foal/core": "^1.0.0", "@foal/typeorm": "^1.0.0", - "source-map-support": "^0.5.1", - "sqlite3": "^4.0.0", - "typeorm": "^0.2.6", - "yamljs": "^0.3.0" + "source-map-support": "~0.5.1", + "sqlite3": "~4.0.0", + "typeorm": "0.2.24", + "yamljs": "~0.3.0" }, "devDependencies": { - "@types/mocha": "^5.2.1", - "@types/node": "^8.0.47", - "concurrently": "^3.5.1", - "copy": "^0.3.2", - "mocha": "^5.2.0", - "supertest": "^3.3.0", - "supervisor": "^0.12.0", - "eslint": "^6.7.0", + "@types/mocha": "5.2.1", + "@types/node": "8.0.47", + "concurrently": "~3.5.1", + "copy": "~0.3.2", + "mocha": "~5.2.0", + "supertest": "~3.3.0", + "supervisor": "~0.12.0", + "eslint": "~6.7.0", "@typescript-eslint/eslint-plugin": "~2.7.0", "@typescript-eslint/parser": "~2.7.0", "typescript": "~3.5.3" diff --git a/packages/cli/src/generate/templates/app/package.json b/packages/cli/src/generate/templates/app/package.json index 0c1b0ecd37..2b5e090e7e 100644 --- a/packages/cli/src/generate/templates/app/package.json +++ b/packages/cli/src/generate/templates/app/package.json @@ -33,19 +33,19 @@ "dependencies": { "@foal/core": "^1.0.0", "@foal/typeorm": "^1.0.0", - "source-map-support": "^0.5.1", - "sqlite3": "^4.0.0", - "typeorm": "^0.2.6" + "source-map-support": "~0.5.1", + "sqlite3": "~4.0.0", + "typeorm": "0.2.24" }, "devDependencies": { - "@types/mocha": "^5.2.1", - "@types/node": "^8.0.47", - "concurrently": "^3.5.1", - "copy": "^0.3.2", - "mocha": "^5.2.0", - "supertest": "^3.3.0", - "supervisor": "^0.12.0", - "eslint": "^6.7.0", + "@types/mocha": "5.2.1", + "@types/node": "8.0.47", + "concurrently": "~3.5.1", + "copy": "~0.3.2", + "mocha": "~5.2.0", + "supertest": "~3.3.0", + "supervisor": "~0.12.0", + "eslint": "~6.7.0", "@typescript-eslint/eslint-plugin": "~2.7.0", "@typescript-eslint/parser": "~2.7.0", "typescript": "~3.5.3" diff --git a/packages/cli/src/generate/templates/app/package.mongodb.json b/packages/cli/src/generate/templates/app/package.mongodb.json index 653fe6bd7e..78b2a956f6 100644 --- a/packages/cli/src/generate/templates/app/package.mongodb.json +++ b/packages/cli/src/generate/templates/app/package.mongodb.json @@ -29,18 +29,18 @@ "dependencies": { "@foal/core": "^1.0.0", "@foal/mongoose": "^1.0.0", - "mongoose": "^5.4.9", - "source-map-support": "^0.5.1" + "mongoose": "~5.4.9", + "source-map-support": "~0.5.1" }, "devDependencies": { - "@types/mocha": "^5.2.1", - "@types/node": "^8.0.47", - "concurrently": "^3.5.1", - "copy": "^0.3.2", - "mocha": "^5.2.0", - "supertest": "^3.3.0", - "supervisor": "^0.12.0", - "eslint": "^6.7.0", + "@types/mocha": "5.2.1", + "@types/node": "8.0.47", + "concurrently": "~3.5.1", + "copy": "~0.3.2", + "mocha": "~5.2.0", + "supertest": "~3.3.0", + "supervisor": "~0.12.0", + "eslint": "~6.7.0", "@typescript-eslint/eslint-plugin": "~2.7.0", "@typescript-eslint/parser": "~2.7.0", "typescript": "~3.5.3" diff --git a/packages/cli/src/generate/templates/app/package.mongodb.yaml.json b/packages/cli/src/generate/templates/app/package.mongodb.yaml.json index 7f0a129556..da8f1e156b 100644 --- a/packages/cli/src/generate/templates/app/package.mongodb.yaml.json +++ b/packages/cli/src/generate/templates/app/package.mongodb.yaml.json @@ -29,19 +29,19 @@ "dependencies": { "@foal/core": "^1.0.0", "@foal/mongoose": "^1.0.0", - "mongoose": "^5.4.9", - "source-map-support": "^0.5.1", - "yamljs": "^0.3.0" + "mongoose": "~5.4.9", + "source-map-support": "~0.5.1", + "yamljs": "~0.3.0" }, "devDependencies": { - "@types/mocha": "^5.2.1", - "@types/node": "^8.0.47", - "concurrently": "^3.5.1", - "copy": "^0.3.2", - "mocha": "^5.2.0", - "supertest": "^3.3.0", - "supervisor": "^0.12.0", - "eslint": "^6.7.0", + "@types/mocha": "5.2.1", + "@types/node": "8.0.47", + "concurrently": "~3.5.1", + "copy": "~0.3.2", + "mocha": "~5.2.0", + "supertest": "~3.3.0", + "supervisor": "~0.12.0", + "eslint": "~6.7.0", "@typescript-eslint/eslint-plugin": "~2.7.0", "@typescript-eslint/parser": "~2.7.0", "typescript": "~3.5.3" diff --git a/packages/cli/src/generate/templates/app/package.yaml.json b/packages/cli/src/generate/templates/app/package.yaml.json index d304060d54..448ff21f5a 100644 --- a/packages/cli/src/generate/templates/app/package.yaml.json +++ b/packages/cli/src/generate/templates/app/package.yaml.json @@ -33,20 +33,20 @@ "dependencies": { "@foal/core": "^1.0.0", "@foal/typeorm": "^1.0.0", - "source-map-support": "^0.5.1", - "sqlite3": "^4.0.0", - "typeorm": "^0.2.6", - "yamljs": "^0.3.0" + "source-map-support": "~0.5.1", + "sqlite3": "~4.0.0", + "typeorm": "0.2.24", + "yamljs": "~0.3.0" }, "devDependencies": { - "@types/mocha": "^5.2.1", - "@types/node": "^8.0.47", - "concurrently": "^3.5.1", - "copy": "^0.3.2", - "mocha": "^5.2.0", - "supertest": "^3.3.0", - "supervisor": "^0.12.0", - "eslint": "^6.7.0", + "@types/mocha": "5.2.1", + "@types/node": "8.0.47", + "concurrently": "~3.5.1", + "copy": "~0.3.2", + "mocha": "~5.2.0", + "supertest": "~3.3.0", + "supervisor": "~0.12.0", + "eslint": "~6.7.0", "@typescript-eslint/eslint-plugin": "~2.7.0", "@typescript-eslint/parser": "~2.7.0", "typescript": "~3.5.3" From bfb847e58c3e91c5cf066556b601e7c0639eeaf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Wed, 20 May 2020 16:16:55 +0200 Subject: [PATCH 65/92] Revert "[CLI] Be more strict on dep versions." This reverts commit c0acc543caa23b1f699d8fa9bc01eeea0a9762ad. --- .../cli/src/generate/specs/app/package.json | 22 ++++++++--------- .../generate/specs/app/package.mongodb.json | 20 ++++++++-------- .../specs/app/package.mongodb.yaml.json | 22 ++++++++--------- .../src/generate/specs/app/package.yaml.json | 24 +++++++++---------- .../src/generate/templates/app/package.json | 22 ++++++++--------- .../templates/app/package.mongodb.json | 20 ++++++++-------- .../templates/app/package.mongodb.yaml.json | 22 ++++++++--------- .../generate/templates/app/package.yaml.json | 24 +++++++++---------- 8 files changed, 88 insertions(+), 88 deletions(-) diff --git a/packages/cli/src/generate/specs/app/package.json b/packages/cli/src/generate/specs/app/package.json index fa5fe73764..cbde81f642 100644 --- a/packages/cli/src/generate/specs/app/package.json +++ b/packages/cli/src/generate/specs/app/package.json @@ -33,19 +33,19 @@ "dependencies": { "@foal/core": "^1.0.0", "@foal/typeorm": "^1.0.0", - "source-map-support": "~0.5.1", - "sqlite3": "~4.0.0", - "typeorm": "0.2.24" + "source-map-support": "^0.5.1", + "sqlite3": "^4.0.0", + "typeorm": "^0.2.6" }, "devDependencies": { - "@types/mocha": "5.2.1", - "@types/node": "8.0.47", - "concurrently": "~3.5.1", - "copy": "~0.3.2", - "mocha": "~5.2.0", - "supertest": "~3.3.0", - "supervisor": "~0.12.0", - "eslint": "~6.7.0", + "@types/mocha": "^5.2.1", + "@types/node": "^8.0.47", + "concurrently": "^3.5.1", + "copy": "^0.3.2", + "mocha": "^5.2.0", + "supertest": "^3.3.0", + "supervisor": "^0.12.0", + "eslint": "^6.7.0", "@typescript-eslint/eslint-plugin": "~2.7.0", "@typescript-eslint/parser": "~2.7.0", "typescript": "~3.5.3" diff --git a/packages/cli/src/generate/specs/app/package.mongodb.json b/packages/cli/src/generate/specs/app/package.mongodb.json index 315b811c99..46cd57ed86 100644 --- a/packages/cli/src/generate/specs/app/package.mongodb.json +++ b/packages/cli/src/generate/specs/app/package.mongodb.json @@ -29,18 +29,18 @@ "dependencies": { "@foal/core": "^1.0.0", "@foal/mongoose": "^1.0.0", - "mongoose": "~5.4.9", - "source-map-support": "~0.5.1" + "mongoose": "^5.4.9", + "source-map-support": "^0.5.1" }, "devDependencies": { - "@types/mocha": "5.2.1", - "@types/node": "8.0.47", - "concurrently": "~3.5.1", - "copy": "~0.3.2", - "mocha": "~5.2.0", - "supertest": "~3.3.0", - "supervisor": "~0.12.0", - "eslint": "~6.7.0", + "@types/mocha": "^5.2.1", + "@types/node": "^8.0.47", + "concurrently": "^3.5.1", + "copy": "^0.3.2", + "mocha": "^5.2.0", + "supertest": "^3.3.0", + "supervisor": "^0.12.0", + "eslint": "^6.7.0", "@typescript-eslint/eslint-plugin": "~2.7.0", "@typescript-eslint/parser": "~2.7.0", "typescript": "~3.5.3" diff --git a/packages/cli/src/generate/specs/app/package.mongodb.yaml.json b/packages/cli/src/generate/specs/app/package.mongodb.yaml.json index 427e57bce0..f60c59df30 100644 --- a/packages/cli/src/generate/specs/app/package.mongodb.yaml.json +++ b/packages/cli/src/generate/specs/app/package.mongodb.yaml.json @@ -29,19 +29,19 @@ "dependencies": { "@foal/core": "^1.0.0", "@foal/mongoose": "^1.0.0", - "mongoose": "~5.4.9", - "source-map-support": "~0.5.1", - "yamljs": "~0.3.0" + "mongoose": "^5.4.9", + "source-map-support": "^0.5.1", + "yamljs": "^0.3.0" }, "devDependencies": { - "@types/mocha": "5.2.1", - "@types/node": "8.0.47", - "concurrently": "~3.5.1", - "copy": "~0.3.2", - "mocha": "~5.2.0", - "supertest": "~3.3.0", - "supervisor": "~0.12.0", - "eslint": "~6.7.0", + "@types/mocha": "^5.2.1", + "@types/node": "^8.0.47", + "concurrently": "^3.5.1", + "copy": "^0.3.2", + "mocha": "^5.2.0", + "supertest": "^3.3.0", + "supervisor": "^0.12.0", + "eslint": "^6.7.0", "@typescript-eslint/eslint-plugin": "~2.7.0", "@typescript-eslint/parser": "~2.7.0", "typescript": "~3.5.3" diff --git a/packages/cli/src/generate/specs/app/package.yaml.json b/packages/cli/src/generate/specs/app/package.yaml.json index b180076346..25f17818c1 100644 --- a/packages/cli/src/generate/specs/app/package.yaml.json +++ b/packages/cli/src/generate/specs/app/package.yaml.json @@ -33,20 +33,20 @@ "dependencies": { "@foal/core": "^1.0.0", "@foal/typeorm": "^1.0.0", - "source-map-support": "~0.5.1", - "sqlite3": "~4.0.0", - "typeorm": "0.2.24", - "yamljs": "~0.3.0" + "source-map-support": "^0.5.1", + "sqlite3": "^4.0.0", + "typeorm": "^0.2.6", + "yamljs": "^0.3.0" }, "devDependencies": { - "@types/mocha": "5.2.1", - "@types/node": "8.0.47", - "concurrently": "~3.5.1", - "copy": "~0.3.2", - "mocha": "~5.2.0", - "supertest": "~3.3.0", - "supervisor": "~0.12.0", - "eslint": "~6.7.0", + "@types/mocha": "^5.2.1", + "@types/node": "^8.0.47", + "concurrently": "^3.5.1", + "copy": "^0.3.2", + "mocha": "^5.2.0", + "supertest": "^3.3.0", + "supervisor": "^0.12.0", + "eslint": "^6.7.0", "@typescript-eslint/eslint-plugin": "~2.7.0", "@typescript-eslint/parser": "~2.7.0", "typescript": "~3.5.3" diff --git a/packages/cli/src/generate/templates/app/package.json b/packages/cli/src/generate/templates/app/package.json index 2b5e090e7e..0c1b0ecd37 100644 --- a/packages/cli/src/generate/templates/app/package.json +++ b/packages/cli/src/generate/templates/app/package.json @@ -33,19 +33,19 @@ "dependencies": { "@foal/core": "^1.0.0", "@foal/typeorm": "^1.0.0", - "source-map-support": "~0.5.1", - "sqlite3": "~4.0.0", - "typeorm": "0.2.24" + "source-map-support": "^0.5.1", + "sqlite3": "^4.0.0", + "typeorm": "^0.2.6" }, "devDependencies": { - "@types/mocha": "5.2.1", - "@types/node": "8.0.47", - "concurrently": "~3.5.1", - "copy": "~0.3.2", - "mocha": "~5.2.0", - "supertest": "~3.3.0", - "supervisor": "~0.12.0", - "eslint": "~6.7.0", + "@types/mocha": "^5.2.1", + "@types/node": "^8.0.47", + "concurrently": "^3.5.1", + "copy": "^0.3.2", + "mocha": "^5.2.0", + "supertest": "^3.3.0", + "supervisor": "^0.12.0", + "eslint": "^6.7.0", "@typescript-eslint/eslint-plugin": "~2.7.0", "@typescript-eslint/parser": "~2.7.0", "typescript": "~3.5.3" diff --git a/packages/cli/src/generate/templates/app/package.mongodb.json b/packages/cli/src/generate/templates/app/package.mongodb.json index 78b2a956f6..653fe6bd7e 100644 --- a/packages/cli/src/generate/templates/app/package.mongodb.json +++ b/packages/cli/src/generate/templates/app/package.mongodb.json @@ -29,18 +29,18 @@ "dependencies": { "@foal/core": "^1.0.0", "@foal/mongoose": "^1.0.0", - "mongoose": "~5.4.9", - "source-map-support": "~0.5.1" + "mongoose": "^5.4.9", + "source-map-support": "^0.5.1" }, "devDependencies": { - "@types/mocha": "5.2.1", - "@types/node": "8.0.47", - "concurrently": "~3.5.1", - "copy": "~0.3.2", - "mocha": "~5.2.0", - "supertest": "~3.3.0", - "supervisor": "~0.12.0", - "eslint": "~6.7.0", + "@types/mocha": "^5.2.1", + "@types/node": "^8.0.47", + "concurrently": "^3.5.1", + "copy": "^0.3.2", + "mocha": "^5.2.0", + "supertest": "^3.3.0", + "supervisor": "^0.12.0", + "eslint": "^6.7.0", "@typescript-eslint/eslint-plugin": "~2.7.0", "@typescript-eslint/parser": "~2.7.0", "typescript": "~3.5.3" diff --git a/packages/cli/src/generate/templates/app/package.mongodb.yaml.json b/packages/cli/src/generate/templates/app/package.mongodb.yaml.json index da8f1e156b..7f0a129556 100644 --- a/packages/cli/src/generate/templates/app/package.mongodb.yaml.json +++ b/packages/cli/src/generate/templates/app/package.mongodb.yaml.json @@ -29,19 +29,19 @@ "dependencies": { "@foal/core": "^1.0.0", "@foal/mongoose": "^1.0.0", - "mongoose": "~5.4.9", - "source-map-support": "~0.5.1", - "yamljs": "~0.3.0" + "mongoose": "^5.4.9", + "source-map-support": "^0.5.1", + "yamljs": "^0.3.0" }, "devDependencies": { - "@types/mocha": "5.2.1", - "@types/node": "8.0.47", - "concurrently": "~3.5.1", - "copy": "~0.3.2", - "mocha": "~5.2.0", - "supertest": "~3.3.0", - "supervisor": "~0.12.0", - "eslint": "~6.7.0", + "@types/mocha": "^5.2.1", + "@types/node": "^8.0.47", + "concurrently": "^3.5.1", + "copy": "^0.3.2", + "mocha": "^5.2.0", + "supertest": "^3.3.0", + "supervisor": "^0.12.0", + "eslint": "^6.7.0", "@typescript-eslint/eslint-plugin": "~2.7.0", "@typescript-eslint/parser": "~2.7.0", "typescript": "~3.5.3" diff --git a/packages/cli/src/generate/templates/app/package.yaml.json b/packages/cli/src/generate/templates/app/package.yaml.json index 448ff21f5a..d304060d54 100644 --- a/packages/cli/src/generate/templates/app/package.yaml.json +++ b/packages/cli/src/generate/templates/app/package.yaml.json @@ -33,20 +33,20 @@ "dependencies": { "@foal/core": "^1.0.0", "@foal/typeorm": "^1.0.0", - "source-map-support": "~0.5.1", - "sqlite3": "~4.0.0", - "typeorm": "0.2.24", - "yamljs": "~0.3.0" + "source-map-support": "^0.5.1", + "sqlite3": "^4.0.0", + "typeorm": "^0.2.6", + "yamljs": "^0.3.0" }, "devDependencies": { - "@types/mocha": "5.2.1", - "@types/node": "8.0.47", - "concurrently": "~3.5.1", - "copy": "~0.3.2", - "mocha": "~5.2.0", - "supertest": "~3.3.0", - "supervisor": "~0.12.0", - "eslint": "~6.7.0", + "@types/mocha": "^5.2.1", + "@types/node": "^8.0.47", + "concurrently": "^3.5.1", + "copy": "^0.3.2", + "mocha": "^5.2.0", + "supertest": "^3.3.0", + "supervisor": "^0.12.0", + "eslint": "^6.7.0", "@typescript-eslint/eslint-plugin": "~2.7.0", "@typescript-eslint/parser": "~2.7.0", "typescript": "~3.5.3" From 3a9b5e86c0d1e5ad738e755cacdd6d50a2a5af03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Wed, 20 May 2020 16:21:02 +0200 Subject: [PATCH 66/92] [CLI] Use v0.2.24 of TypeORM --- packages/cli/src/generate/specs/app/package.json | 2 +- packages/cli/src/generate/specs/app/package.yaml.json | 2 +- packages/cli/src/generate/templates/app/package.json | 2 +- packages/cli/src/generate/templates/app/package.yaml.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/generate/specs/app/package.json b/packages/cli/src/generate/specs/app/package.json index cbde81f642..18755f2fc9 100644 --- a/packages/cli/src/generate/specs/app/package.json +++ b/packages/cli/src/generate/specs/app/package.json @@ -35,7 +35,7 @@ "@foal/typeorm": "^1.0.0", "source-map-support": "^0.5.1", "sqlite3": "^4.0.0", - "typeorm": "^0.2.6" + "typeorm": "0.2.24" }, "devDependencies": { "@types/mocha": "^5.2.1", diff --git a/packages/cli/src/generate/specs/app/package.yaml.json b/packages/cli/src/generate/specs/app/package.yaml.json index 25f17818c1..df84fda641 100644 --- a/packages/cli/src/generate/specs/app/package.yaml.json +++ b/packages/cli/src/generate/specs/app/package.yaml.json @@ -35,7 +35,7 @@ "@foal/typeorm": "^1.0.0", "source-map-support": "^0.5.1", "sqlite3": "^4.0.0", - "typeorm": "^0.2.6", + "typeorm": "0.2.24", "yamljs": "^0.3.0" }, "devDependencies": { diff --git a/packages/cli/src/generate/templates/app/package.json b/packages/cli/src/generate/templates/app/package.json index 0c1b0ecd37..c624463531 100644 --- a/packages/cli/src/generate/templates/app/package.json +++ b/packages/cli/src/generate/templates/app/package.json @@ -35,7 +35,7 @@ "@foal/typeorm": "^1.0.0", "source-map-support": "^0.5.1", "sqlite3": "^4.0.0", - "typeorm": "^0.2.6" + "typeorm": "0.2.24" }, "devDependencies": { "@types/mocha": "^5.2.1", diff --git a/packages/cli/src/generate/templates/app/package.yaml.json b/packages/cli/src/generate/templates/app/package.yaml.json index d304060d54..81cad78807 100644 --- a/packages/cli/src/generate/templates/app/package.yaml.json +++ b/packages/cli/src/generate/templates/app/package.yaml.json @@ -35,7 +35,7 @@ "@foal/typeorm": "^1.0.0", "source-map-support": "^0.5.1", "sqlite3": "^4.0.0", - "typeorm": "^0.2.6", + "typeorm": "0.2.24", "yamljs": "^0.3.0" }, "devDependencies": { From dd63b26de69e7d90e0e13bcfb06cccf1ea426bdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Wed, 20 May 2020 16:41:02 +0200 Subject: [PATCH 67/92] Fix bash test failure --- e2e_test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e_test.sh b/e2e_test.sh index 807fbe36e0..d28f83eab9 100755 --- a/e2e_test.sh +++ b/e2e_test.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env sh +#!/usr/bin/env bash set -e mkdir e2e-test-temp From 718f736edf35ed3273492eae61dc61cd51dc34e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Wed, 20 May 2020 16:35:23 +0200 Subject: [PATCH 68/92] [CLI] Add ClientError --- packages/cli/src/generate/file-system.spec.ts | 7 +- packages/cli/src/generate/file-system.ts | 12 ++- packages/cli/src/index.ts | 76 +++++++++++-------- 3 files changed, 60 insertions(+), 35 deletions(-) diff --git a/packages/cli/src/generate/file-system.spec.ts b/packages/cli/src/generate/file-system.spec.ts index b2c4137f68..39090792f3 100644 --- a/packages/cli/src/generate/file-system.spec.ts +++ b/packages/cli/src/generate/file-system.spec.ts @@ -4,7 +4,7 @@ import { existsSync, mkdirSync, readFileSync, rmdirSync, unlinkSync, writeFileSy import { join } from 'path'; // FoalTS -import { FileSystem } from './file-system'; +import { ClientError, FileSystem } from './file-system'; function rmdir(path: string) { if (existsSync(path)) { @@ -331,11 +331,14 @@ describe('FileSystem', () => { ); }); - it('should throw an error if the file does not exist.', () => { + it('should throw a ClientError if the file does not exist.', () => { try { fs.modify('test-file-system/foobar.txt', content => content); throw new Error('An error should have been thrown'); } catch (error) { + if (!(error instanceof ClientError)) { + throw new Error('The error thrown should be an instance of ClientError.'); + } strictEqual(error.message, 'Impossible to modify "test-file-system/foobar.txt": the file does not exist.'); } }); diff --git a/packages/cli/src/generate/file-system.ts b/packages/cli/src/generate/file-system.ts index 58f55a3f01..9dca04f9dd 100644 --- a/packages/cli/src/generate/file-system.ts +++ b/packages/cli/src/generate/file-system.ts @@ -31,6 +31,16 @@ function rmDirAndFiles(path: string) { rmdirSync(path); } +/** + * Error thrown by the FileSystem which aims to be pretty + * printed without the stacktrace. + * + * @export + * @class ClientError + * @extends {Error} + */ +export class ClientError extends Error {} + /** * This class provides more methods that Node std "fs". * It also allows to create an isolated directory during directory. @@ -216,7 +226,7 @@ export class FileSystem { */ modify(path: string, callback: (content: string) => string): this { if (!existsSync(this.parse(path))) { - throw new Error(`Impossible to modify "${path}": the file does not exist.`); + throw new ClientError(`Impossible to modify "${path}": the file does not exist.`); } const content = readFileSync(this.parse(path), 'utf8'); this.logUpdate(path); diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 177ebf52f4..33d7e6e703 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -26,6 +26,7 @@ import { createSubApp, createVSCodeConfig, } from './generate'; +import { ClientError } from './generate/file-system'; import { rmdir } from './rmdir'; import { runScript } from './run-script'; @@ -119,41 +120,52 @@ program console.error(); return; } - switch (type) { - case 'controller': - createController({ name, register: options.register }); - break; - case 'entity': - createEntity({ name }); - break; - case 'rest-api': - createRestApi({ name, register: options.register }); - break; - case 'hook': - createHook({ name }); - break; - case 'model': - createModel({ name, checkMongoose: true }); - break; - case 'sub-app': - createSubApp({ name }); - break; - case 'script': - createScript({ name }); - break; - case 'service': - createService({ name }); - break; - case 'vscode-config': - createVSCodeConfig(); - break; - default: - console.error(); - console.error(red(`Unknown type ${yellow(type)}. Please provide a valid one:`)); + try { + switch (type) { + case 'controller': + createController({ name, register: options.register }); + break; + case 'entity': + createEntity({ name }); + break; + case 'rest-api': + createRestApi({ name, register: options.register }); + break; + case 'hook': + createHook({ name }); + break; + case 'model': + createModel({ name, checkMongoose: true }); + break; + case 'sub-app': + createSubApp({ name }); + break; + case 'script': + createScript({ name }); + break; + case 'service': + createService({ name }); + break; + case 'vscode-config': + createVSCodeConfig(); + break; + default: + console.error(); + console.error(red(`Unknown type ${yellow(type)}. Please provide a valid one:`)); + console.error(); + generateTypes.forEach(t => console.error(red(` ${t}`))); + console.error(); + } + } catch (error) { + if (error instanceof ClientError) { console.error(); - generateTypes.forEach(t => console.error(red(` ${t}`))); + console.error(red(error.message)); console.error(); + return; + } + throw error; } + }); program From 1e33de6d640a0b929c7a7ab55c86808a813b39e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Wed, 20 May 2020 17:35:34 +0200 Subject: [PATCH 69/92] [CLI] Add FileSystem.cdProjectRootDir --- packages/cli/src/generate/file-system.spec.ts | 132 ++++++++++++++++++ packages/cli/src/generate/file-system.ts | 46 +++++- 2 files changed, 176 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/generate/file-system.spec.ts b/packages/cli/src/generate/file-system.spec.ts index 39090792f3..9f3bca8e87 100644 --- a/packages/cli/src/generate/file-system.spec.ts +++ b/packages/cli/src/generate/file-system.spec.ts @@ -42,6 +42,138 @@ describe('FileSystem', () => { }); + describe('has a "cdProjectRootDir" that', () => { + + let cliPkg: Buffer; + let rootPkg: Buffer; + + before(() => { + cliPkg = readFileSync('package.json'); + unlinkSync('package.json'); + + rootPkg = readFileSync('../../package.json'); + unlinkSync('../../package.json'); + }); + + after(() => { + writeFileSync('../../package.json', rootPkg); + writeFileSync('package.json', cliPkg); + }); + + beforeEach(() => { + mkdir('test-generators'); + mkdir('test-generators/foo'); + mkdir('test-generators/foo/bar'); + }); + + afterEach(() => { + rmfile('test-generators/package.json'); + rmdir('test-generators/foo/bar'); + rmdir('test-generators/foo'); + rmdir('test-generators'); + }); + + it('should root the current working directory to the project root directory.', () => { + writeFileSync( + 'test-generators/package.json', + JSON.stringify({ + dependencies: { + '@foal/core': 'versionNumber' + } + }), + 'utf8' + ); + fs.cd('foo/bar'); + fs.cdProjectRootDir(); + strictEqual(fs.currentDir, '.'); + }); + + it('should throw a ClienError if the package.json is not a valid JSON.', () => { + writeFileSync( + 'test-generators/package.json', + 'hello', + 'utf8' + ); + + fs.cd('foo/bar'); + try { + fs.cdProjectRootDir(); + throw new Error('An error should have been thrown'); + } catch (error) { + if (!(error instanceof ClientError)) { + throw new Error('The error thrown should be an instance of ClientError.'); + } + strictEqual( + error.message, + 'The file package.json is not a valid JSON. Unexpected token h in JSON at position 0' + ); + } + }); + + it('should throw a ClienError if the package.json found does not have @foal/core as dependency (no deps).', () => { + writeFileSync( + 'test-generators/package.json', + JSON.stringify({}), + 'utf8' + ); + + fs.cd('foo/bar'); + try { + fs.cdProjectRootDir(); + throw new Error('An error should have been thrown'); + } catch (error) { + if (!(error instanceof ClientError)) { + throw new Error('The error thrown should be an instance of ClientError.'); + } + strictEqual( + error.message, + 'This project is not a FoalTS project. The dependency @foal/core is missing in package.json.' + ); + } + }); + + it('should throw a ClienError if the package.json found does not have @foal/core as dependency (>=1 dep).', () => { + writeFileSync( + 'test-generators/package.json', + JSON.stringify({ + dependencies: {} + }), + 'utf8' + ); + + fs.cd('foo/bar'); + try { + fs.cdProjectRootDir(); + throw new Error('An error should have been thrown'); + } catch (error) { + if (!(error instanceof ClientError)) { + throw new Error('The error thrown should be an instance of ClientError.'); + } + strictEqual( + error.message, + 'This project is not a FoalTS project. The dependency @foal/core is missing in package.json.' + ); + } + }); + + it('should throw a ClientError if no package.json is found.', () => { + fs.cd('foo/bar'); + try { + fs.cdProjectRootDir(); + throw new Error('An error should have been thrown'); + } catch (error) { + if (!(error instanceof ClientError)) { + throw new Error('The error thrown should be an instance of ClientError.'); + } + strictEqual( + error.message, + 'This project is not a FoalTS project. No package.json found.' + ); + } + }); + + }); + describe('has a "exists" method that', () => { beforeEach(() => { diff --git a/packages/cli/src/generate/file-system.ts b/packages/cli/src/generate/file-system.ts index 9dca04f9dd..993cf355f3 100644 --- a/packages/cli/src/generate/file-system.ts +++ b/packages/cli/src/generate/file-system.ts @@ -11,7 +11,7 @@ import { unlinkSync, writeFileSync } from 'fs'; -import { dirname, join } from 'path'; +import { dirname, join, parse } from 'path'; // 3p import { cyan, green } from 'colors/safe'; @@ -67,7 +67,7 @@ export class FileSystem { } /** - * Change the current working directory. + * Changes the current working directory. * * @param {string} path - Relative path of the directory. * @returns {this} @@ -78,6 +78,48 @@ export class FileSystem { return this; } + /** + * Changes the current working directory to the project root + * directory. + * + * It searches for the closer package.json containing @foal/core + * as dependency. + * + * @returns {this} + * @memberof FileSystem + */ + cdProjectRootDir(): this { + // "/" on Unix, C:\ on Windows + const root = parse(process.cwd()).root; + + while (!this.exists('package.json')) { + if (join(process.cwd(), this.parse('.')) === root) { + throw new ClientError( + 'This project is not a FoalTS project. No package.json found.' + ); + } + this.cd('..'); + } + const content = readFileSync(this.parse('package.json'), 'utf8'); + + let pkg: any; + try { + pkg = JSON.parse(content); + } catch (error) { + throw new ClientError( + `The file package.json is not a valid JSON. ${error.message}` + ); + } + + if (!pkg.dependencies || !pkg.dependencies['@foal/core']) { + throw new ClientError( + 'This project is not a FoalTS project. The dependency @foal/core is missing in package.json.' + ); + } + + return this; + } + /** * Checks if a file or directory exists. * From d17722f45a863c79feda2d8c57ec41489263f90e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Wed, 20 May 2020 17:53:06 +0200 Subject: [PATCH 70/92] [CLI] Use cdProjectRootDir in createScript --- packages/cli/.gitignore | 1 + .../src/generate/fixtures/script/package.json | 5 +++ .../generators/script/create-script.spec.ts | 42 +++++-------------- .../generators/script/create-script.ts | 26 ++---------- 4 files changed, 20 insertions(+), 54 deletions(-) create mode 100644 packages/cli/.gitignore create mode 100644 packages/cli/src/generate/fixtures/script/package.json diff --git a/packages/cli/.gitignore b/packages/cli/.gitignore new file mode 100644 index 0000000000..a7a637a6e3 --- /dev/null +++ b/packages/cli/.gitignore @@ -0,0 +1 @@ +test-generators diff --git a/packages/cli/src/generate/fixtures/script/package.json b/packages/cli/src/generate/fixtures/script/package.json new file mode 100644 index 0000000000..dd31387aa4 --- /dev/null +++ b/packages/cli/src/generate/fixtures/script/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "@foal/core": "^1.0.0" + } +} \ No newline at end of file diff --git a/packages/cli/src/generate/generators/script/create-script.spec.ts b/packages/cli/src/generate/generators/script/create-script.spec.ts index 96b1a244fc..3b62c8dc29 100644 --- a/packages/cli/src/generate/generators/script/create-script.spec.ts +++ b/packages/cli/src/generate/generators/script/create-script.spec.ts @@ -1,47 +1,25 @@ // FoalTS import { FileSystem } from '../../file-system'; -import { - rmDirAndFilesIfExist, -} from '../../utils'; import { createScript } from './create-script'; -// TODO: Improve the tests. They currently cover partially `createScript`. - describe('createScript', () => { const fs = new FileSystem(); - // TODO: remove this line. - (fs as any).testDir = ''; - - afterEach(() => { - rmDirAndFilesIfExist('src/scripts'); - // We cannot remove src/ since the generator code lives within. This is bad testing - // approach. - }); - - describe(`when the directory src/scripts/ exists`, () => { - beforeEach(() => { - fs - .ensureDir('src/scripts') - .cd('src/scripts'); - }); + beforeEach(() => fs.setUp()); - it('should copy the empty script file in the proper directory.', () => { - createScript({ name: 'test-fooBar' }); - - fs - .assertEqual('test-foo-bar.ts', 'script/test-foo-bar.ts'); - }); - - }); + afterEach(() => fs.tearDown()); - describe(`when the directory src/scripts/ does not exist`, () => { + it('should copy the empty script file in the proper directory.', () => { + fs + .copyFixture('script/package.json', 'package.json') + .ensureDir('src/scripts'); - it('should not throw an error.', () => { - createScript({ name: 'test-fooBar' }); - }); + createScript({ name: 'test-fooBar' }); + fs + .cd('src/scripts') + .assertEqual('test-foo-bar.ts', 'script/test-foo-bar.ts'); }); }); diff --git a/packages/cli/src/generate/generators/script/create-script.ts b/packages/cli/src/generate/generators/script/create-script.ts index be48c97a28..2baa611e9f 100644 --- a/packages/cli/src/generate/generators/script/create-script.ts +++ b/packages/cli/src/generate/generators/script/create-script.ts @@ -3,36 +3,18 @@ import { existsSync } from 'fs'; import { join, relative } from 'path'; // 3p -import { red } from 'colors/safe'; - // FoalTS import { FileSystem } from '../../file-system'; -import { findProjectPath, getNames } from '../../utils'; +import { getNames } from '../../utils'; export function createScript({ name }: { name: string }) { const names = getNames(name); - const root = findProjectPath(); - - if (!root) { - console.log(red('\n This directory is not a Foal project (missing package.json).\n')); - return; - } - - const scriptPath = join(root, './src/scripts/'); - if (!existsSync(scriptPath)) { - if (process.env.P1Z7kEbSUUPMxF8GqPwD8Gx_FOAL_CLI_TEST !== 'true') { - console.log(red(`\n This directory is not a Foal project (${scriptPath} does not exist).\n`)); - } - return; - } - const fs = new FileSystem(); - // TODO: remove this line. - (fs as any).testDir = ''; fs - // Use `relative` to have pretty CREATE logs. - .cd(relative(process.cwd(), scriptPath)) + // TODO: test this line + .cdProjectRootDir() + .cd('src/scripts') .copy('script/script.ts', `${names.kebabName}.ts`); } From 9ef64e04fe44ec9ec38115724af353ec9e889483 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Wed, 20 May 2020 18:15:53 +0200 Subject: [PATCH 71/92] [CLI] Add projectHasDependency --- packages/cli/src/generate/file-system.spec.ts | 35 +++++++++++++++++++ packages/cli/src/generate/file-system.ts | 18 ++++++++++ 2 files changed, 53 insertions(+) diff --git a/packages/cli/src/generate/file-system.spec.ts b/packages/cli/src/generate/file-system.spec.ts index 9f3bca8e87..5cb6fa03e6 100644 --- a/packages/cli/src/generate/file-system.spec.ts +++ b/packages/cli/src/generate/file-system.spec.ts @@ -783,6 +783,41 @@ describe('FileSystem', () => { }); + describe('has a "projectHasDependency" method that', () => { + + let initialPkg: Buffer; + + before(() => { + mkdir('test-generators'); + initialPkg = readFileSync('package.json'); + writeFileSync('package.json', JSON.stringify({ + dependencies: { + '@foal/core': 'hello', + 'bar': 'world' + } + }), 'utf8'); + }); + + after(() => { + writeFileSync('package.json', initialPkg); + rmdir('test-generators'); + }); + + it('should return true if the project has the dependency in its package.json.', () => { + strictEqual(fs.projectHasDependency('bar'), true); + }); + + it('should return false if the project does not have the dependency in its package.json.', () => { + strictEqual(fs.projectHasDependency('foo'), false); + }); + + it('should not change the current working directory.', () => { + fs.projectHasDependency('commander'); + strictEqual(fs.currentDir, ''); + }); + + }); + describe('has a "tearDown" method that', () => { beforeEach(() => { diff --git a/packages/cli/src/generate/file-system.ts b/packages/cli/src/generate/file-system.ts index 993cf355f3..076791be43 100644 --- a/packages/cli/src/generate/file-system.ts +++ b/packages/cli/src/generate/file-system.ts @@ -435,6 +435,24 @@ export class FileSystem { return this; } + /** + * Returns true if the project package.json has this dependency. + * Returns false otherwise. + * + * @param {string} name - The name of the dependency. + * @returns {boolean} + * @memberof FileSystem + */ + projectHasDependency(name: string): boolean { + const initialCurrentDir = this.currentDir; + + this.cdProjectRootDir(); + const pkg = JSON.parse(readFileSync(this.parse('package.json'), 'utf8')); + + this.currentDir = initialCurrentDir; + return pkg.dependencies.hasOwnProperty(name); + } + /************************ Testing Methods ************************/ From 3caabc2e55eafa929641281451819c461e780a03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Wed, 20 May 2020 18:37:04 +0200 Subject: [PATCH 72/92] [CLI] Use fs.projectHasDependency in createModel --- .../src/generate/fixtures/model/package.json | 5 ++++ .../fixtures/model/package.mongoose.json | 6 ++++ .../generators/model/create-model.spec.ts | 27 ++++++++++++++++- .../generate/generators/model/create-model.ts | 30 +++++++------------ packages/cli/src/index.ts | 2 +- 5 files changed, 48 insertions(+), 22 deletions(-) create mode 100644 packages/cli/src/generate/fixtures/model/package.json create mode 100644 packages/cli/src/generate/fixtures/model/package.mongoose.json diff --git a/packages/cli/src/generate/fixtures/model/package.json b/packages/cli/src/generate/fixtures/model/package.json new file mode 100644 index 0000000000..dd31387aa4 --- /dev/null +++ b/packages/cli/src/generate/fixtures/model/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "@foal/core": "^1.0.0" + } +} \ No newline at end of file diff --git a/packages/cli/src/generate/fixtures/model/package.mongoose.json b/packages/cli/src/generate/fixtures/model/package.mongoose.json new file mode 100644 index 0000000000..563af45af7 --- /dev/null +++ b/packages/cli/src/generate/fixtures/model/package.mongoose.json @@ -0,0 +1,6 @@ +{ + "dependencies": { + "@foal/core": "^1.0.0", + "mongoose": "xxxx" + } +} \ No newline at end of file diff --git a/packages/cli/src/generate/generators/model/create-model.spec.ts b/packages/cli/src/generate/generators/model/create-model.spec.ts index 8e3419e6e3..8a39d01559 100644 --- a/packages/cli/src/generate/generators/model/create-model.spec.ts +++ b/packages/cli/src/generate/generators/model/create-model.spec.ts @@ -1,5 +1,9 @@ +// std +import { strictEqual } from 'assert'; + // FoalTS -import { FileSystem } from '../../file-system'; +import { yellow } from 'colors/safe'; +import { ClientError, FileSystem } from '../../file-system'; import { createModel } from './create-model'; describe('createModel', () => { @@ -16,6 +20,7 @@ describe('createModel', () => { beforeEach(() => { fs + .copyFixture('model/package.mongoose.json', 'package.json') .ensureDir(root) .cd(root) .copyFixture('model/index.ts', 'index.ts'); @@ -45,4 +50,24 @@ describe('createModel', () => { test('models'); test(''); + it('should throw a ClientError if the project does not have the dependency "mongoose".', () => { + fs + .copyFixture('model/index.ts', 'index.ts') + .copyFixture('model/package.json', 'package.json'); + + try { + createModel({ name: 'test-fooBar' }); + throw new Error('An error should have been thrown'); + } catch (error) { + if (!(error instanceof ClientError)) { + throw new Error('The error thrown should be an instance of ClientError.'); + } + strictEqual( + error.message, + `"foal generate|g ${yellow('model')} " can only be used in a Mongoose project.\n` + + ` Please use "foal generate|g ${yellow('entity')} " instead.` + ); + } + }); + }); diff --git a/packages/cli/src/generate/generators/model/create-model.ts b/packages/cli/src/generate/generators/model/create-model.ts index f266b59326..0f6baa16ee 100644 --- a/packages/cli/src/generate/generators/model/create-model.ts +++ b/packages/cli/src/generate/generators/model/create-model.ts @@ -1,30 +1,20 @@ -// std -import { readFileSync } from 'fs'; -import { join } from 'path'; - // 3p -import { red, yellow } from 'colors/safe'; +import { yellow } from 'colors/safe'; // FoalTS -import { FileSystem } from '../../file-system'; -import { findProjectPath, getNames } from '../../utils'; +import { ClientError, FileSystem } from '../../file-system'; +import { getNames } from '../../utils'; -export function createModel({ name, checkMongoose }: { name: string, checkMongoose?: boolean }) { - const projectPath = findProjectPath(); +export function createModel({ name }: { name: string }) { + const fs = new FileSystem(); - if (checkMongoose && projectPath !== null) { - const pkg = JSON.parse(readFileSync(join(projectPath, 'package.json'), 'utf8')); - if (!pkg.dependencies || !pkg.dependencies.mongoose) { - console.log(red( - `\n "foal generate|g ${yellow('model')} " can only be used in a Mongoose project. ` - + `\n Please use "foal generate|g ${yellow('entity')} " instead.\n` - )); - return; - } + if (!fs.projectHasDependency('mongoose')) { + throw new ClientError( + `"foal generate|g ${yellow('model')} " can only be used in a Mongoose project.\n` + + ` Please use "foal generate|g ${yellow('entity')} " instead.` + ); } - const fs = new FileSystem(); - let root = ''; if (fs.exists('src/app/models')) { root = 'src/app/models'; diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 33d7e6e703..82bb56605b 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -135,7 +135,7 @@ program createHook({ name }); break; case 'model': - createModel({ name, checkMongoose: true }); + createModel({ name }); break; case 'sub-app': createSubApp({ name }); From cf31a851f2c2a8ba2eacf994abcaa751d4d2db90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Wed, 20 May 2020 18:45:39 +0200 Subject: [PATCH 73/92] [CLI] Use fs.projectHasDependency in createRestApi --- .../generate/fixtures/rest-api/package.json | 5 ++++ .../fixtures/rest-api/package.mongoose.json | 6 +++++ .../rest-api/create-rest-api.spec.ts | 27 ++++++++++++++++++- .../generators/rest-api/create-rest-api.ts | 24 +++++------------ .../src/generate/utils/find-project-path.ts | 20 -------------- packages/cli/src/generate/utils/index.ts | 1 - 6 files changed, 43 insertions(+), 40 deletions(-) create mode 100644 packages/cli/src/generate/fixtures/rest-api/package.json create mode 100644 packages/cli/src/generate/fixtures/rest-api/package.mongoose.json delete mode 100644 packages/cli/src/generate/utils/find-project-path.ts diff --git a/packages/cli/src/generate/fixtures/rest-api/package.json b/packages/cli/src/generate/fixtures/rest-api/package.json new file mode 100644 index 0000000000..dd31387aa4 --- /dev/null +++ b/packages/cli/src/generate/fixtures/rest-api/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "@foal/core": "^1.0.0" + } +} \ No newline at end of file diff --git a/packages/cli/src/generate/fixtures/rest-api/package.mongoose.json b/packages/cli/src/generate/fixtures/rest-api/package.mongoose.json new file mode 100644 index 0000000000..563af45af7 --- /dev/null +++ b/packages/cli/src/generate/fixtures/rest-api/package.mongoose.json @@ -0,0 +1,6 @@ +{ + "dependencies": { + "@foal/core": "^1.0.0", + "mongoose": "xxxx" + } +} \ No newline at end of file diff --git a/packages/cli/src/generate/generators/rest-api/create-rest-api.spec.ts b/packages/cli/src/generate/generators/rest-api/create-rest-api.spec.ts index 69f6401c90..bb2beb1904 100644 --- a/packages/cli/src/generate/generators/rest-api/create-rest-api.spec.ts +++ b/packages/cli/src/generate/generators/rest-api/create-rest-api.spec.ts @@ -1,5 +1,8 @@ +// std +import { strictEqual } from 'assert'; + // FoalTS -import { FileSystem } from '../../file-system'; +import { ClientError, FileSystem } from '../../file-system'; import { createRestApi } from './create-rest-api'; // TODO: add tests like "should create index.ts if it does not exist." @@ -18,6 +21,7 @@ describe('createRestApi', () => { beforeEach(() => { fs + .copyFixture('rest-api/package.json', 'package.json') .ensureDir(root) .cd(root) .ensureDir('entities') @@ -60,6 +64,7 @@ describe('createRestApi', () => { beforeEach(() => { fs + .copyFixture('rest-api/package.json', 'package.json') .ensureDir(root) .cd(root) .ensureDir('entities') @@ -93,6 +98,7 @@ describe('createRestApi', () => { beforeEach(() => { fs + .copyFixture('rest-api/package.json', 'package.json') .copyFixture('rest-api/index.current-dir.ts', 'index.ts'); }); @@ -116,4 +122,23 @@ describe('createRestApi', () => { }); + it('should throw a ClientError if the project has the dependency "mongoose".', () => { + fs + .copyFixture('model/index.ts', 'index.ts') + .copyFixture('model/package.mongoose.json', 'package.json'); + + try { + createRestApi({ name: 'test-fooBar', register: false }); + throw new Error('An error should have been thrown'); + } catch (error) { + if (!(error instanceof ClientError)) { + throw new Error('The error thrown should be an instance of ClientError.'); + } + strictEqual( + error.message, + `"foal generate|g rest-api " cannot be used in a Mongoose project.` + ); + } + }); + }); diff --git a/packages/cli/src/generate/generators/rest-api/create-rest-api.ts b/packages/cli/src/generate/generators/rest-api/create-rest-api.ts index 4f54e1b241..9dd4ce32a4 100644 --- a/packages/cli/src/generate/generators/rest-api/create-rest-api.ts +++ b/packages/cli/src/generate/generators/rest-api/create-rest-api.ts @@ -1,29 +1,17 @@ -// std -import { readFileSync } from 'fs'; -import { join } from 'path'; - // 3p -import { red, underline } from 'colors/safe'; +import { underline } from 'colors/safe'; // FoalTS -import { FileSystem } from '../../file-system'; -import { findProjectPath, getNames } from '../../utils'; +import { ClientError, FileSystem } from '../../file-system'; +import { getNames } from '../../utils'; export function createRestApi({ name, register }: { name: string, register: boolean }) { - const projectPath = findProjectPath(); + const fs = new FileSystem(); - if (projectPath !== null) { - const pkg = JSON.parse(readFileSync(join(projectPath, 'package.json'), 'utf8')); - if (pkg.dependencies && pkg.dependencies.mongoose) { - console.log(red( - '\n "foal generate|g rest-api " cannot be used in a Mongoose project.\n' - )); - return; - } + if (fs.projectHasDependency('mongoose')) { + throw new ClientError('"foal generate|g rest-api " cannot be used in a Mongoose project.'); } - const fs = new FileSystem(); - let entityRoot = ''; let controllerRoot = ''; if (fs.exists('src/app/entities') && fs.exists('src/app/controllers')) { diff --git a/packages/cli/src/generate/utils/find-project-path.ts b/packages/cli/src/generate/utils/find-project-path.ts deleted file mode 100644 index ae72885bf0..0000000000 --- a/packages/cli/src/generate/utils/find-project-path.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { existsSync } from 'fs'; -import { join, parse } from 'path'; - -export function findProjectPath(): string|null { - let path = process.cwd(); - const root = parse(path).root; - - while (path !== root) { - if (existsSync(join(path, 'package.json'))) { - break; - } - path = parse(path).dir; - } - - if (path === root) { - return null; - } - - return path; -} diff --git a/packages/cli/src/generate/utils/index.ts b/packages/cli/src/generate/utils/index.ts index a0d27ed433..dc64bfe433 100644 --- a/packages/cli/src/generate/utils/index.ts +++ b/packages/cli/src/generate/utils/index.ts @@ -1,4 +1,3 @@ -export { findProjectPath } from './find-project-path'; export { initGitRepo } from './init-git-repo'; export { rmDirAndFilesIfExist } from './rm-dir-and-files-if-exist'; export { mkdirIfDoesNotExist } from './mkdir-if-does-not-exist'; From 7ee5346ad3344db83c22c11bdf806279d97afc3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Thu, 21 May 2020 08:25:35 +0200 Subject: [PATCH 74/92] [CLI] Add trycatch to script templates --- .../specs/script/test-foo-bar.mongoose.ts | 17 +++++++++++++++++ .../src/generate/specs/script/test-foo-bar.ts | 11 +++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 packages/cli/src/generate/specs/script/test-foo-bar.mongoose.ts diff --git a/packages/cli/src/generate/specs/script/test-foo-bar.mongoose.ts b/packages/cli/src/generate/specs/script/test-foo-bar.mongoose.ts new file mode 100644 index 0000000000..4da5e4ce06 --- /dev/null +++ b/packages/cli/src/generate/specs/script/test-foo-bar.mongoose.ts @@ -0,0 +1,17 @@ +// 3p +import { createConnection } from 'typeorm'; + +export const schema = { + additionalProperties: false, + properties: { + /* To complete */ + }, + required: [ /* To complete */ ], + type: 'object', +}; + +export async function main(args: any) { + await createConnection(); + // MONGOOSE + // Do something. +} diff --git a/packages/cli/src/generate/specs/script/test-foo-bar.ts b/packages/cli/src/generate/specs/script/test-foo-bar.ts index 803eb73366..d645d0cc6d 100644 --- a/packages/cli/src/generate/specs/script/test-foo-bar.ts +++ b/packages/cli/src/generate/specs/script/test-foo-bar.ts @@ -11,7 +11,14 @@ export const schema = { }; export async function main(args: any) { - await createConnection(); + const connection = await createConnection(); - // Do something. + try { + // Do something. + + } catch (error) { + console.error(error); + } finally { + await connection.close(); + } } From ad1eafd3fa262d50d1047d4abe18df70b3f449fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Thu, 21 May 2020 08:52:13 +0200 Subject: [PATCH 75/92] [CLI] Generate mongoose script --- .../fixtures/script/package.mongoose.json | 6 +++++ .../generators/script/create-script.spec.ts | 12 +++++++++ .../generators/script/create-script.ts | 4 ++- .../specs/script/test-foo-bar.mongoose.ts | 17 +++++++++--- .../templates/script/script.mongoose.ts | 26 +++++++++++++++++++ .../src/generate/templates/script/script.ts | 11 ++++++-- 6 files changed, 69 insertions(+), 7 deletions(-) create mode 100644 packages/cli/src/generate/fixtures/script/package.mongoose.json create mode 100644 packages/cli/src/generate/templates/script/script.mongoose.ts diff --git a/packages/cli/src/generate/fixtures/script/package.mongoose.json b/packages/cli/src/generate/fixtures/script/package.mongoose.json new file mode 100644 index 0000000000..28c7c4d24a --- /dev/null +++ b/packages/cli/src/generate/fixtures/script/package.mongoose.json @@ -0,0 +1,6 @@ +{ + "dependencies": { + "@foal/core": "^1.0.0", + "mongoose": "xxx" + } +} \ No newline at end of file diff --git a/packages/cli/src/generate/generators/script/create-script.spec.ts b/packages/cli/src/generate/generators/script/create-script.spec.ts index 3b62c8dc29..75f7a8663a 100644 --- a/packages/cli/src/generate/generators/script/create-script.spec.ts +++ b/packages/cli/src/generate/generators/script/create-script.spec.ts @@ -22,4 +22,16 @@ describe('createScript', () => { .assertEqual('test-foo-bar.ts', 'script/test-foo-bar.ts'); }); + it('should copy the empty script file in the proper directory (mongoose).', () => { + fs + .copyFixture('script/package.mongoose.json', 'package.json') + .ensureDir('src/scripts'); + + createScript({ name: 'test-fooBar' }); + + fs + .cd('src/scripts') + .assertEqual('test-foo-bar.ts', 'script/test-foo-bar.mongoose.ts'); + }); + }); diff --git a/packages/cli/src/generate/generators/script/create-script.ts b/packages/cli/src/generate/generators/script/create-script.ts index 2baa611e9f..07aefdb777 100644 --- a/packages/cli/src/generate/generators/script/create-script.ts +++ b/packages/cli/src/generate/generators/script/create-script.ts @@ -11,10 +11,12 @@ export function createScript({ name }: { name: string }) { const names = getNames(name); const fs = new FileSystem(); + const isMongoose = fs.projectHasDependency('mongoose'); fs // TODO: test this line .cdProjectRootDir() .cd('src/scripts') - .copy('script/script.ts', `${names.kebabName}.ts`); + .copyOnlyIf(!isMongoose, 'script/script.ts', `${names.kebabName}.ts`) + .copyOnlyIf(isMongoose, 'script/script.mongoose.ts', `${names.kebabName}.ts`) } diff --git a/packages/cli/src/generate/specs/script/test-foo-bar.mongoose.ts b/packages/cli/src/generate/specs/script/test-foo-bar.mongoose.ts index 4da5e4ce06..f4f7be9e3a 100644 --- a/packages/cli/src/generate/specs/script/test-foo-bar.mongoose.ts +++ b/packages/cli/src/generate/specs/script/test-foo-bar.mongoose.ts @@ -1,5 +1,6 @@ // 3p -import { createConnection } from 'typeorm'; +import { Config } from '@foal/core'; +import { connect, disconnect } from 'mongoose'; export const schema = { additionalProperties: false, @@ -11,7 +12,15 @@ export const schema = { }; export async function main(args: any) { - await createConnection(); - // MONGOOSE - // Do something. + const uri = Config.getOrThrow('mongodb.uri', 'string'); + await connect(uri, { useNewUrlParser: true, useCreateIndex: true, useUnifiedTopology: true }); + + try { + // Do something. + + } catch (error) { + console.error(error); + } finally { + await disconnect(); + } } diff --git a/packages/cli/src/generate/templates/script/script.mongoose.ts b/packages/cli/src/generate/templates/script/script.mongoose.ts new file mode 100644 index 0000000000..f4f7be9e3a --- /dev/null +++ b/packages/cli/src/generate/templates/script/script.mongoose.ts @@ -0,0 +1,26 @@ +// 3p +import { Config } from '@foal/core'; +import { connect, disconnect } from 'mongoose'; + +export const schema = { + additionalProperties: false, + properties: { + /* To complete */ + }, + required: [ /* To complete */ ], + type: 'object', +}; + +export async function main(args: any) { + const uri = Config.getOrThrow('mongodb.uri', 'string'); + await connect(uri, { useNewUrlParser: true, useCreateIndex: true, useUnifiedTopology: true }); + + try { + // Do something. + + } catch (error) { + console.error(error); + } finally { + await disconnect(); + } +} diff --git a/packages/cli/src/generate/templates/script/script.ts b/packages/cli/src/generate/templates/script/script.ts index 803eb73366..d645d0cc6d 100644 --- a/packages/cli/src/generate/templates/script/script.ts +++ b/packages/cli/src/generate/templates/script/script.ts @@ -11,7 +11,14 @@ export const schema = { }; export async function main(args: any) { - await createConnection(); + const connection = await createConnection(); - // Do something. + try { + // Do something. + + } catch (error) { + console.error(error); + } finally { + await connection.close(); + } } From 20bcffe61b33e2cd292f57aa381f1283f552d11e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Thu, 21 May 2020 08:53:35 +0200 Subject: [PATCH 76/92] [CLI] Improve mongoose create-user script --- .../src/generate/specs/app/src/scripts/create-user.mongodb.ts | 4 ++-- .../generate/templates/app/src/scripts/create-user.mongodb.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/generate/specs/app/src/scripts/create-user.mongodb.ts b/packages/cli/src/generate/specs/app/src/scripts/create-user.mongodb.ts index 997c6701a5..ccf41907cc 100644 --- a/packages/cli/src/generate/specs/app/src/scripts/create-user.mongodb.ts +++ b/packages/cli/src/generate/specs/app/src/scripts/create-user.mongodb.ts @@ -26,7 +26,7 @@ export async function main(/*args*/) { // await user.setPassword(args.password); const uri = Config.getOrThrow('mongodb.uri', 'string'); - connect(uri, { useNewUrlParser: true, useCreateIndex: true, useUnifiedTopology: true }); + await connect(uri, { useNewUrlParser: true, useCreateIndex: true, useUnifiedTopology: true }); try { console.log( @@ -35,6 +35,6 @@ export async function main(/*args*/) { } catch (error) { console.log(error.message); } finally { - disconnect(); + await disconnect(); } } diff --git a/packages/cli/src/generate/templates/app/src/scripts/create-user.mongodb.ts b/packages/cli/src/generate/templates/app/src/scripts/create-user.mongodb.ts index 997c6701a5..ccf41907cc 100644 --- a/packages/cli/src/generate/templates/app/src/scripts/create-user.mongodb.ts +++ b/packages/cli/src/generate/templates/app/src/scripts/create-user.mongodb.ts @@ -26,7 +26,7 @@ export async function main(/*args*/) { // await user.setPassword(args.password); const uri = Config.getOrThrow('mongodb.uri', 'string'); - connect(uri, { useNewUrlParser: true, useCreateIndex: true, useUnifiedTopology: true }); + await connect(uri, { useNewUrlParser: true, useCreateIndex: true, useUnifiedTopology: true }); try { console.log( @@ -35,6 +35,6 @@ export async function main(/*args*/) { } catch (error) { console.log(error.message); } finally { - disconnect(); + await disconnect(); } } From 4a09551ee3b5f3468ecc041fd806a55015e4c996 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Thu, 21 May 2020 08:57:24 +0200 Subject: [PATCH 77/92] [CLI] Close connection in create-user --- .../cli/src/generate/specs/app/src/scripts/create-user.ts | 4 +++- .../cli/src/generate/templates/app/src/scripts/create-user.ts | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/generate/specs/app/src/scripts/create-user.ts b/packages/cli/src/generate/specs/app/src/scripts/create-user.ts index 80124466dd..615bcc7629 100644 --- a/packages/cli/src/generate/specs/app/src/scripts/create-user.ts +++ b/packages/cli/src/generate/specs/app/src/scripts/create-user.ts @@ -1,7 +1,7 @@ // 3p // import { Group, Permission } from '@foal/typeorm'; // import { isCommon } from '@foal/password'; -import { createConnection, getManager, /*getRepository*/ } from 'typeorm'; +import { createConnection, getConnection, getManager, /*getRepository*/ } from 'typeorm'; // App import { User } from '../app/entities'; @@ -55,5 +55,7 @@ export async function main(/*args*/) { ); } catch (error) { console.log(error.message); + } finally { + await getConnection().close(); } } diff --git a/packages/cli/src/generate/templates/app/src/scripts/create-user.ts b/packages/cli/src/generate/templates/app/src/scripts/create-user.ts index 80124466dd..615bcc7629 100644 --- a/packages/cli/src/generate/templates/app/src/scripts/create-user.ts +++ b/packages/cli/src/generate/templates/app/src/scripts/create-user.ts @@ -1,7 +1,7 @@ // 3p // import { Group, Permission } from '@foal/typeorm'; // import { isCommon } from '@foal/password'; -import { createConnection, getManager, /*getRepository*/ } from 'typeorm'; +import { createConnection, getConnection, getManager, /*getRepository*/ } from 'typeorm'; // App import { User } from '../app/entities'; @@ -55,5 +55,7 @@ export async function main(/*args*/) { ); } catch (error) { console.log(error.message); + } finally { + await getConnection().close(); } } From 9d3df4954e5a595793362f78c694dd343077bcdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Thu, 21 May 2020 09:05:41 +0200 Subject: [PATCH 78/92] [CLI] create-vscodeconfig goes to root directory --- .../cli/src/generate/fixtures/vscode-config/package.json | 5 +++++ .../generators/vscode-config/create-vscode-config.spec.ts | 3 +++ .../generators/vscode-config/create-vscode-config.ts | 2 ++ 3 files changed, 10 insertions(+) create mode 100644 packages/cli/src/generate/fixtures/vscode-config/package.json diff --git a/packages/cli/src/generate/fixtures/vscode-config/package.json b/packages/cli/src/generate/fixtures/vscode-config/package.json new file mode 100644 index 0000000000..dd31387aa4 --- /dev/null +++ b/packages/cli/src/generate/fixtures/vscode-config/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "@foal/core": "^1.0.0" + } +} \ No newline at end of file diff --git a/packages/cli/src/generate/generators/vscode-config/create-vscode-config.spec.ts b/packages/cli/src/generate/generators/vscode-config/create-vscode-config.spec.ts index 0aa70cde08..a3a457fc17 100644 --- a/packages/cli/src/generate/generators/vscode-config/create-vscode-config.spec.ts +++ b/packages/cli/src/generate/generators/vscode-config/create-vscode-config.spec.ts @@ -10,6 +10,9 @@ describe('createVSCodeConfig', () => { afterEach(() => fs.tearDown()); it('should create the directory .vscode/ with default launch.json and tasks.json files.', () => { + fs + .copyFixture('vscode-config/package.json', 'package.json'); + createVSCodeConfig(); fs diff --git a/packages/cli/src/generate/generators/vscode-config/create-vscode-config.ts b/packages/cli/src/generate/generators/vscode-config/create-vscode-config.ts index 8343a59ae3..c1a0383a9b 100644 --- a/packages/cli/src/generate/generators/vscode-config/create-vscode-config.ts +++ b/packages/cli/src/generate/generators/vscode-config/create-vscode-config.ts @@ -3,6 +3,8 @@ import { FileSystem } from '../../file-system'; export function createVSCodeConfig() { new FileSystem() + // TODO: test this line + .cdProjectRootDir() .ensureDir('.vscode') .cd('.vscode') .copy('vscode-config/launch.json', 'launch.json') From f0a08e0b75c758971179035c10815454df472042 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Thu, 21 May 2020 09:23:11 +0200 Subject: [PATCH 79/92] [Docs] Add supported SQL databases --- docs/databases/typeorm.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/databases/typeorm.md b/docs/databases/typeorm.md index c74ead4f4d..3def354ff3 100644 --- a/docs/databases/typeorm.md +++ b/docs/databases/typeorm.md @@ -1,5 +1,7 @@ # TypeORM +> *FoalTS components using TypeORM officially support the following databases: PostgreSQL, MySQL, MariaDB and SQLite*. + *A simple model:* ```typescript import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; From 83acd5dc760aff4e0cfc31a4bd7a12e89dfc1a27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Thu, 21 May 2020 09:24:10 +0200 Subject: [PATCH 80/92] Fix linting --- .../cli/src/generate/generators/script/create-script.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/cli/src/generate/generators/script/create-script.ts b/packages/cli/src/generate/generators/script/create-script.ts index 07aefdb777..0d8b96acd9 100644 --- a/packages/cli/src/generate/generators/script/create-script.ts +++ b/packages/cli/src/generate/generators/script/create-script.ts @@ -1,8 +1,3 @@ -// std -import { existsSync } from 'fs'; -import { join, relative } from 'path'; - -// 3p // FoalTS import { FileSystem } from '../../file-system'; import { getNames } from '../../utils'; @@ -18,5 +13,5 @@ export function createScript({ name }: { name: string }) { .cdProjectRootDir() .cd('src/scripts') .copyOnlyIf(!isMongoose, 'script/script.ts', `${names.kebabName}.ts`) - .copyOnlyIf(isMongoose, 'script/script.mongoose.ts', `${names.kebabName}.ts`) + .copyOnlyIf(isMongoose, 'script/script.mongoose.ts', `${names.kebabName}.ts`); } From 66886eafb5b873d0c4c8c84e1aaeb59ff999db35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Thu, 21 May 2020 14:42:24 +0200 Subject: [PATCH 81/92] [CLI] Support --auth on "foal g rest-api" --- .../rest-api/create-rest-api.spec.ts | 24 + .../generators/rest-api/create-rest-api.ts | 30 +- .../rest-api/test-foo-bar.controller.auth.ts | 154 ++++++ ...est-foo-bar.controller.current-dir.auth.ts | 155 ++++++ .../test-foo-bar.controller.spec.auth.ts | 469 +++++++++++++++++ ...oo-bar.controller.spec.current-dir.auth.ts | 470 ++++++++++++++++++ ...est-foo-bar.controller.spec.current-dir.ts | 2 +- .../rest-api/test-foo-bar.controller.spec.ts | 2 +- .../rest-api/test-foo-bar.entity.auth.ts | 17 + .../templates/rest-api/controller.auth.ts | 154 ++++++ .../rest-api/controller.current-dir.auth.ts | 155 ++++++ .../rest-api/controller.spec.auth.ts | 469 +++++++++++++++++ .../controller.spec.current-dir.auth.ts | 470 ++++++++++++++++++ .../rest-api/controller.spec.current-dir.ts | 2 +- .../templates/rest-api/controller.spec.ts | 2 +- .../templates/rest-api/entity.auth.ts | 17 + packages/cli/src/index.ts | 7 +- 17 files changed, 2580 insertions(+), 19 deletions(-) create mode 100644 packages/cli/src/generate/specs/rest-api/test-foo-bar.controller.auth.ts create mode 100644 packages/cli/src/generate/specs/rest-api/test-foo-bar.controller.current-dir.auth.ts create mode 100644 packages/cli/src/generate/specs/rest-api/test-foo-bar.controller.spec.auth.ts create mode 100644 packages/cli/src/generate/specs/rest-api/test-foo-bar.controller.spec.current-dir.auth.ts create mode 100644 packages/cli/src/generate/specs/rest-api/test-foo-bar.entity.auth.ts create mode 100644 packages/cli/src/generate/templates/rest-api/controller.auth.ts create mode 100644 packages/cli/src/generate/templates/rest-api/controller.current-dir.auth.ts create mode 100644 packages/cli/src/generate/templates/rest-api/controller.spec.auth.ts create mode 100644 packages/cli/src/generate/templates/rest-api/controller.spec.current-dir.auth.ts create mode 100644 packages/cli/src/generate/templates/rest-api/entity.auth.ts diff --git a/packages/cli/src/generate/generators/rest-api/create-rest-api.spec.ts b/packages/cli/src/generate/generators/rest-api/create-rest-api.spec.ts index bb2beb1904..b35a9fa950 100644 --- a/packages/cli/src/generate/generators/rest-api/create-rest-api.spec.ts +++ b/packages/cli/src/generate/generators/rest-api/create-rest-api.spec.ts @@ -48,6 +48,20 @@ describe('createRestApi', () => { .assertEqual('index.ts', 'rest-api/index.controllers.ts'); }); + it('should render the templates in the proper directory (--auth flag).', () => { + createRestApi({ name: 'test-fooBar', register: false, auth: true }); + + fs + .cd('entities') + .assertEqual('test-foo-bar.entity.ts', 'rest-api/test-foo-bar.entity.auth.ts') + .assertEqual('index.ts', 'rest-api/index.entities.ts') + .cd('..') + .cd('controllers') + .assertEqual('test-foo-bar.controller.ts', 'rest-api/test-foo-bar.controller.auth.ts') + .assertEqual('test-foo-bar.controller.spec.ts', 'rest-api/test-foo-bar.controller.spec.auth.ts') + .assertEqual('index.ts', 'rest-api/index.controllers.ts'); + }); + it('should create the index.ts if they do not exist.', () => { fs.rmfile('entities/index.ts'); fs.rmfile('controllers/index.ts'); @@ -112,6 +126,16 @@ describe('createRestApi', () => { .assertEqual('index.ts', 'rest-api/index.current-dir.ts'); }); + it('should render the templates in the current directory (--auth flag).', () => { + createRestApi({ name: 'test-fooBar', register: false, auth: true }); + + fs + .assertEqual('test-foo-bar.entity.ts', 'rest-api/test-foo-bar.entity.auth.ts') + .assertEqual('test-foo-bar.controller.ts', 'rest-api/test-foo-bar.controller.current-dir.auth.ts') + .assertEqual('test-foo-bar.controller.spec.ts', 'rest-api/test-foo-bar.controller.spec.current-dir.auth.ts') + .assertEqual('index.ts', 'rest-api/index.current-dir.ts'); + }); + it('should create index.ts if it does not exist.', () => { fs.rmfile('index.ts'); diff --git a/packages/cli/src/generate/generators/rest-api/create-rest-api.ts b/packages/cli/src/generate/generators/rest-api/create-rest-api.ts index 9dd4ce32a4..845afac582 100644 --- a/packages/cli/src/generate/generators/rest-api/create-rest-api.ts +++ b/packages/cli/src/generate/generators/rest-api/create-rest-api.ts @@ -5,7 +5,9 @@ import { underline } from 'colors/safe'; import { ClientError, FileSystem } from '../../file-system'; import { getNames } from '../../utils'; -export function createRestApi({ name, register }: { name: string, register: boolean }) { +export function createRestApi({ name, register, auth }: { name: string, register: boolean, auth?: boolean }) { + auth = auth || false; + const fs = new FileSystem(); if (fs.projectHasDependency('mongoose')) { @@ -28,24 +30,28 @@ export function createRestApi({ name, register }: { name: string, register: bool fs .cd(entityRoot) - .render('rest-api/entity.ts', `${names.kebabName}.entity.ts`, names) + .renderOnlyIf(!auth, 'rest-api/entity.ts', `${names.kebabName}.entity.ts`, names) + .renderOnlyIf(auth, 'rest-api/entity.auth.ts', `${names.kebabName}.entity.ts`, names) .ensureFile('index.ts') .addNamedExportIn('index.ts', names.upperFirstCamelName, `./${names.kebabName}.entity`); fs.currentDir = ''; + const isCurrentDir = !controllerRoot; + fs .cd(controllerRoot) - .render( - controllerRoot ? 'rest-api/controller.ts' : 'rest-api/controller.current-dir.ts', - `${names.kebabName}.controller.ts`, - names, - ) - .render( - controllerRoot ? 'rest-api/controller.spec.ts' : 'rest-api/controller.spec.current-dir.ts', - `${names.kebabName}.controller.spec.ts`, - names, - ) + + .renderOnlyIf(!isCurrentDir && !auth, 'rest-api/controller.ts', `${names.kebabName}.controller.ts`, names) + .renderOnlyIf(!isCurrentDir && auth, 'rest-api/controller.auth.ts', `${names.kebabName}.controller.ts`, names) + .renderOnlyIf(isCurrentDir && !auth, 'rest-api/controller.current-dir.ts', `${names.kebabName}.controller.ts`, names) + .renderOnlyIf(isCurrentDir && auth, 'rest-api/controller.current-dir.auth.ts', `${names.kebabName}.controller.ts`, names) + + .renderOnlyIf(!isCurrentDir && !auth, 'rest-api/controller.spec.ts', `${names.kebabName}.controller.spec.ts`, names) + .renderOnlyIf(!isCurrentDir && auth, 'rest-api/controller.spec.auth.ts', `${names.kebabName}.controller.spec.ts`, names) + .renderOnlyIf(isCurrentDir && !auth, 'rest-api/controller.spec.current-dir.ts', `${names.kebabName}.controller.spec.ts`, names) + .renderOnlyIf(isCurrentDir && auth, 'rest-api/controller.spec.current-dir.auth.ts', `${names.kebabName}.controller.spec.ts`, names) + .ensureFile('index.ts') .addNamedExportIn('index.ts', className, `./${names.kebabName}.controller`); diff --git a/packages/cli/src/generate/specs/rest-api/test-foo-bar.controller.auth.ts b/packages/cli/src/generate/specs/rest-api/test-foo-bar.controller.auth.ts new file mode 100644 index 0000000000..8ea606f658 --- /dev/null +++ b/packages/cli/src/generate/specs/rest-api/test-foo-bar.controller.auth.ts @@ -0,0 +1,154 @@ +import { + ApiOperationDescription, ApiOperationId, ApiOperationSummary, ApiResponse, + ApiUseTag, Context, Delete, Get, HttpResponseCreated, + HttpResponseNoContent, HttpResponseNotFound, HttpResponseOK, Patch, Post, + Put, ValidateBody, ValidateParams, ValidateQuery +} from '@foal/core'; +import { getRepository } from 'typeorm'; + +import { TestFooBar, User } from '../entities'; + +const testFooBarSchema = { + additionalProperties: false, + properties: { + text: { type: 'string', maxLength: 255 }, + }, + required: [ 'text' ], + type: 'object', +}; + +@ApiUseTag('testFooBar') +export class TestFooBarController { + + @Get() + @ApiOperationId('findTestFooBars') + @ApiOperationSummary('Find testFooBars.') + @ApiOperationDescription( + 'The query parameters "skip" and "take" can be used for pagination. The first ' + + 'is the offset and the second is the number of elements to be returned.' + ) + @ApiResponse(400, { description: 'Invalid query parameters.' }) + @ApiResponse(200, { description: 'Returns a list of testFooBars.' }) + @ValidateQuery({ + properties: { + skip: { type: 'number' }, + take: { type: 'number' }, + }, + type: 'object', + }) + async findTestFooBars(ctx: Context) { + const testFooBars = await getRepository(TestFooBar).find({ + skip: ctx.request.query.skip, + take: ctx.request.query.take, + where: { + owner: ctx.user + } + }); + return new HttpResponseOK(testFooBars); + } + + @Get('/:testFooBarId') + @ApiOperationId('findTestFooBarById') + @ApiOperationSummary('Find a testFooBar by ID.') + @ApiResponse(404, { description: 'TestFooBar not found.' }) + @ApiResponse(200, { description: 'Returns the testFooBar.' }) + @ValidateParams({ properties: { testFooBarId: { type: 'number' } }, type: 'object' }) + async findTestFooBarById(ctx: Context) { + const testFooBar = await getRepository(TestFooBar).findOne({ + id: ctx.request.params.testFooBarId, + owner: ctx.user + }); + + if (!testFooBar) { + return new HttpResponseNotFound(); + } + + return new HttpResponseOK(testFooBar); + } + + @Post() + @ApiOperationId('createTestFooBar') + @ApiOperationSummary('Create a new testFooBar.') + @ApiResponse(400, { description: 'Invalid testFooBar.' }) + @ApiResponse(201, { description: 'TestFooBar successfully created. Returns the testFooBar.' }) + @ValidateBody(testFooBarSchema) + async createTestFooBar(ctx: Context) { + const testFooBar = await getRepository(TestFooBar).save({ + ...ctx.request.body, + owner: ctx.user + }); + return new HttpResponseCreated(testFooBar); + } + + @Patch('/:testFooBarId') + @ApiOperationId('modifyTestFooBar') + @ApiOperationSummary('Update/modify an existing testFooBar.') + @ApiResponse(400, { description: 'Invalid testFooBar.' }) + @ApiResponse(404, { description: 'TestFooBar not found.' }) + @ApiResponse(200, { description: 'TestFooBar successfully updated. Returns the testFooBar.' }) + @ValidateParams({ properties: { testFooBarId: { type: 'number' } }, type: 'object' }) + @ValidateBody({ ...testFooBarSchema, required: [] }) + async modifyTestFooBar(ctx: Context) { + const testFooBar = await getRepository(TestFooBar).findOne({ + id: ctx.request.params.testFooBarId, + owner: ctx.user + }); + + if (!testFooBar) { + return new HttpResponseNotFound(); + } + + Object.assign(testFooBar, ctx.request.body); + + await getRepository(TestFooBar).save(testFooBar); + + return new HttpResponseOK(testFooBar); + } + + @Put('/:testFooBarId') + @ApiOperationId('replaceTestFooBar') + @ApiOperationSummary('Update/replace an existing testFooBar.') + @ApiResponse(400, { description: 'Invalid testFooBar.' }) + @ApiResponse(404, { description: 'TestFooBar not found.' }) + @ApiResponse(200, { description: 'TestFooBar successfully updated. Returns the testFooBar.' }) + @ValidateParams({ properties: { testFooBarId: { type: 'number' } }, type: 'object' }) + @ValidateBody(testFooBarSchema) + async replaceTestFooBar(ctx: Context) { + const testFooBar = await getRepository(TestFooBar).findOne({ + id: ctx.request.params.testFooBarId, + owner: ctx.user + }); + + if (!testFooBar) { + return new HttpResponseNotFound(); + } + + Object.assign(testFooBar, ctx.request.body); + + await getRepository(TestFooBar).save(testFooBar); + + return new HttpResponseOK(testFooBar); + } + + @Delete('/:testFooBarId') + @ApiOperationId('deleteTestFooBar') + @ApiOperationSummary('Delete a testFooBar.') + @ApiResponse(404, { description: 'TestFooBar not found.' }) + @ApiResponse(204, { description: 'TestFooBar successfully deleted.' }) + @ValidateParams({ properties: { testFooBarId: { type: 'number' } }, type: 'object' }) + async deleteTestFooBar(ctx: Context) { + const testFooBar = await getRepository(TestFooBar).findOne({ + id: ctx.request.params.testFooBarId, + owner: ctx.user + }); + + if (!testFooBar) { + return new HttpResponseNotFound(); + } + + await getRepository(TestFooBar).delete(ctx.request.params.testFooBarId); + + return new HttpResponseNoContent(); + } + +} diff --git a/packages/cli/src/generate/specs/rest-api/test-foo-bar.controller.current-dir.auth.ts b/packages/cli/src/generate/specs/rest-api/test-foo-bar.controller.current-dir.auth.ts new file mode 100644 index 0000000000..8dedd44efc --- /dev/null +++ b/packages/cli/src/generate/specs/rest-api/test-foo-bar.controller.current-dir.auth.ts @@ -0,0 +1,155 @@ +import { + ApiOperationDescription, ApiOperationId, ApiOperationSummary, ApiResponse, + ApiUseTag, Context, Delete, Get, HttpResponseCreated, + HttpResponseNoContent, HttpResponseNotFound, HttpResponseOK, Patch, Post, + Put, ValidateBody, ValidateParams, ValidateQuery +} from '@foal/core'; +import { getRepository } from 'typeorm'; + +import { TestFooBar } from './test-foo-bar.entity'; +import { User } from './user.entity'; + +const testFooBarSchema = { + additionalProperties: false, + properties: { + text: { type: 'string', maxLength: 255 }, + }, + required: [ 'text' ], + type: 'object', +}; + +@ApiUseTag('testFooBar') +export class TestFooBarController { + + @Get() + @ApiOperationId('findTestFooBars') + @ApiOperationSummary('Find testFooBars.') + @ApiOperationDescription( + 'The query parameters "skip" and "take" can be used for pagination. The first ' + + 'is the offset and the second is the number of elements to be returned.' + ) + @ApiResponse(400, { description: 'Invalid query parameters.' }) + @ApiResponse(200, { description: 'Returns a list of testFooBars.' }) + @ValidateQuery({ + properties: { + skip: { type: 'number' }, + take: { type: 'number' }, + }, + type: 'object', + }) + async findTestFooBars(ctx: Context) { + const testFooBars = await getRepository(TestFooBar).find({ + skip: ctx.request.query.skip, + take: ctx.request.query.take, + where: { + owner: ctx.user + } + }); + return new HttpResponseOK(testFooBars); + } + + @Get('/:testFooBarId') + @ApiOperationId('findTestFooBarById') + @ApiOperationSummary('Find a testFooBar by ID.') + @ApiResponse(404, { description: 'TestFooBar not found.' }) + @ApiResponse(200, { description: 'Returns the testFooBar.' }) + @ValidateParams({ properties: { testFooBarId: { type: 'number' } }, type: 'object' }) + async findTestFooBarById(ctx: Context) { + const testFooBar = await getRepository(TestFooBar).findOne({ + id: ctx.request.params.testFooBarId, + owner: ctx.user + }); + + if (!testFooBar) { + return new HttpResponseNotFound(); + } + + return new HttpResponseOK(testFooBar); + } + + @Post() + @ApiOperationId('createTestFooBar') + @ApiOperationSummary('Create a new testFooBar.') + @ApiResponse(400, { description: 'Invalid testFooBar.' }) + @ApiResponse(201, { description: 'TestFooBar successfully created. Returns the testFooBar.' }) + @ValidateBody(testFooBarSchema) + async createTestFooBar(ctx: Context) { + const testFooBar = await getRepository(TestFooBar).save({ + ...ctx.request.body, + owner: ctx.user + }); + return new HttpResponseCreated(testFooBar); + } + + @Patch('/:testFooBarId') + @ApiOperationId('modifyTestFooBar') + @ApiOperationSummary('Update/modify an existing testFooBar.') + @ApiResponse(400, { description: 'Invalid testFooBar.' }) + @ApiResponse(404, { description: 'TestFooBar not found.' }) + @ApiResponse(200, { description: 'TestFooBar successfully updated. Returns the testFooBar.' }) + @ValidateParams({ properties: { testFooBarId: { type: 'number' } }, type: 'object' }) + @ValidateBody({ ...testFooBarSchema, required: [] }) + async modifyTestFooBar(ctx: Context) { + const testFooBar = await getRepository(TestFooBar).findOne({ + id: ctx.request.params.testFooBarId, + owner: ctx.user + }); + + if (!testFooBar) { + return new HttpResponseNotFound(); + } + + Object.assign(testFooBar, ctx.request.body); + + await getRepository(TestFooBar).save(testFooBar); + + return new HttpResponseOK(testFooBar); + } + + @Put('/:testFooBarId') + @ApiOperationId('replaceTestFooBar') + @ApiOperationSummary('Update/replace an existing testFooBar.') + @ApiResponse(400, { description: 'Invalid testFooBar.' }) + @ApiResponse(404, { description: 'TestFooBar not found.' }) + @ApiResponse(200, { description: 'TestFooBar successfully updated. Returns the testFooBar.' }) + @ValidateParams({ properties: { testFooBarId: { type: 'number' } }, type: 'object' }) + @ValidateBody(testFooBarSchema) + async replaceTestFooBar(ctx: Context) { + const testFooBar = await getRepository(TestFooBar).findOne({ + id: ctx.request.params.testFooBarId, + owner: ctx.user + }); + + if (!testFooBar) { + return new HttpResponseNotFound(); + } + + Object.assign(testFooBar, ctx.request.body); + + await getRepository(TestFooBar).save(testFooBar); + + return new HttpResponseOK(testFooBar); + } + + @Delete('/:testFooBarId') + @ApiOperationId('deleteTestFooBar') + @ApiOperationSummary('Delete a testFooBar.') + @ApiResponse(404, { description: 'TestFooBar not found.' }) + @ApiResponse(204, { description: 'TestFooBar successfully deleted.' }) + @ValidateParams({ properties: { testFooBarId: { type: 'number' } }, type: 'object' }) + async deleteTestFooBar(ctx: Context) { + const testFooBar = await getRepository(TestFooBar).findOne({ + id: ctx.request.params.testFooBarId, + owner: ctx.user + }); + + if (!testFooBar) { + return new HttpResponseNotFound(); + } + + await getRepository(TestFooBar).delete(ctx.request.params.testFooBarId); + + return new HttpResponseNoContent(); + } + +} diff --git a/packages/cli/src/generate/specs/rest-api/test-foo-bar.controller.spec.auth.ts b/packages/cli/src/generate/specs/rest-api/test-foo-bar.controller.spec.auth.ts new file mode 100644 index 0000000000..e984b24978 --- /dev/null +++ b/packages/cli/src/generate/specs/rest-api/test-foo-bar.controller.spec.auth.ts @@ -0,0 +1,469 @@ +// std +import { notStrictEqual, ok, strictEqual } from 'assert'; + +// 3p +import { + Context, createController, getHttpMethod, getPath, + isHttpResponseCreated, isHttpResponseNoContent, + isHttpResponseNotFound, isHttpResponseOK +} from '@foal/core'; +import { createConnection, getConnection, getRepository } from 'typeorm'; + +// App +import { TestFooBar, User } from '../entities'; +import { TestFooBarController } from './test-foo-bar.controller'; + +describe('TestFooBarController', () => { + + let controller: TestFooBarController; + let testFooBar0: TestFooBar; + let testFooBar1: TestFooBar; + let testFooBar2: TestFooBar; + let user1: User; + let user2: User; + + before(() => createConnection()); + + after(() => getConnection().close()); + + beforeEach(async () => { + controller = createController(TestFooBarController); + + const testFooBarRepository = getRepository(TestFooBar); + const userRepository = getRepository(User); + + await testFooBarRepository.clear(); + await userRepository.clear(); + + [ user1, user2 ] = await userRepository.save([ + {}, + {}, + ]); + + [ testFooBar0, testFooBar1, testFooBar2 ] = await testFooBarRepository.save([ + { + text: 'TestFooBar 0', + owner: user1 + }, + { + text: 'TestFooBar 1', + owner: user2 + }, + { + text: 'TestFooBar 2', + owner: user2 + }, + ]); + }); + + describe('has a "findTestFooBars" method that', () => { + + it('should handle requests at GET /.', () => { + strictEqual(getHttpMethod(TestFooBarController, 'findTestFooBars'), 'GET'); + strictEqual(getPath(TestFooBarController, 'findTestFooBars'), undefined); + }); + + it('should return an HttpResponseOK object with the testFooBar list.', async () => { + const ctx = new Context({ query: {} }); + ctx.user = user2; + const response = await controller.findTestFooBars(ctx); + + if (!isHttpResponseOK(response)) { + throw new Error('The returned value should be an HttpResponseOK object.'); + } + + if (!Array.isArray(response.body)) { + throw new Error('The response body should be an array of testFooBars.'); + } + + strictEqual(response.body.length, 2); + ok(response.body.find(testFooBar => testFooBar.text === testFooBar1.text)); + ok(response.body.find(testFooBar => testFooBar.text === testFooBar2.text)); + }); + + it('should support pagination', async () => { + const testFooBar3 = await getRepository(TestFooBar).save({ + text: 'TestFooBar 3', + owner: user2 + }); + + let ctx = new Context({ + query: { + take: 2 + } + }); + ctx.user = user2; + let response = await controller.findTestFooBars(ctx); + + strictEqual(response.body.length, 2); + ok(response.body.find(testFooBar => testFooBar.id === testFooBar1.id)); + ok(response.body.find(testFooBar => testFooBar.id === testFooBar2.id)); + ok(!response.body.find(testFooBar => testFooBar.id === testFooBar3.id)); + + ctx = new Context({ + query: { + skip: 1 + } + }); + ctx.user = user2; + response = await controller.findTestFooBars(ctx); + + strictEqual(response.body.length, 2); + ok(!response.body.find(testFooBar => testFooBar.id === testFooBar1.id)); + ok(response.body.find(testFooBar => testFooBar.id === testFooBar2.id)); + ok(response.body.find(testFooBar => testFooBar.id === testFooBar3.id)); + }); + + }); + + describe('has a "findTestFooBarById" method that', () => { + + it('should handle requests at GET /:testFooBarId.', () => { + strictEqual(getHttpMethod(TestFooBarController, 'findTestFooBarById'), 'GET'); + strictEqual(getPath(TestFooBarController, 'findTestFooBarById'), '/:testFooBarId'); + }); + + it('should return an HttpResponseOK object if the testFooBar was found.', async () => { + const ctx = new Context({ + params: { + testFooBarId: testFooBar2.id + } + }); + ctx.user = user2; + const response = await controller.findTestFooBarById(ctx); + + if (!isHttpResponseOK(response)) { + throw new Error('The returned value should be an HttpResponseOK object.'); + } + + strictEqual(response.body.id, testFooBar2.id); + strictEqual(response.body.text, testFooBar2.text); + }); + + it('should return an HttpResponseNotFound object if the testFooBar was not found.', async () => { + const ctx = new Context({ + params: { + testFooBarId: -1 + } + }); + ctx.user = user2; + const response = await controller.findTestFooBarById(ctx); + + if (!isHttpResponseNotFound(response)) { + throw new Error('The returned value should be an HttpResponseNotFound object.'); + } + }); + + it('should return an HttpResponseNotFound object if the testFooBar belongs to another user.', async () => { + const ctx = new Context({ + params: { + testFooBarId: testFooBar0.id + } + }); + ctx.user = user2; + const response = await controller.findTestFooBarById(ctx); + + if (!isHttpResponseNotFound(response)) { + throw new Error('The returned value should be an HttpResponseNotFound object.'); + } + }); + + }); + + describe('has a "createTestFooBar" method that', () => { + + it('should handle requests at POST /.', () => { + strictEqual(getHttpMethod(TestFooBarController, 'createTestFooBar'), 'POST'); + strictEqual(getPath(TestFooBarController, 'createTestFooBar'), undefined); + }); + + it('should create the testFooBar in the database and return it through ' + + 'an HttpResponseCreated object.', async () => { + const ctx = new Context({ + body: { + text: 'TestFooBar 3', + } + }); + ctx.user = user2; + const response = await controller.createTestFooBar(ctx); + + if (!isHttpResponseCreated(response)) { + throw new Error('The returned value should be an HttpResponseCreated object.'); + } + + const testFooBar = await getRepository(TestFooBar).findOne({ + where: { text: 'TestFooBar 3' }, + relations: [ 'owner' ] + }); + + if (!testFooBar) { + throw new Error('No testFooBar 3 was found in the database.'); + } + + strictEqual(testFooBar.text, 'TestFooBar 3'); + strictEqual(testFooBar.owner.id, user2.id); + + strictEqual(response.body.id, testFooBar.id); + strictEqual(response.body.text, testFooBar.text); + }); + + }); + + describe('has a "modifyTestFooBar" method that', () => { + + it('should handle requests at PATCH /:testFooBarId.', () => { + strictEqual(getHttpMethod(TestFooBarController, 'modifyTestFooBar'), 'PATCH'); + strictEqual(getPath(TestFooBarController, 'modifyTestFooBar'), '/:testFooBarId'); + }); + + it('should update the testFooBar in the database and return it through an HttpResponseOK object.', async () => { + const ctx = new Context({ + body: { + text: 'TestFooBar 2 (version 2)', + }, + params: { + testFooBarId: testFooBar2.id + } + }); + ctx.user = user2; + const response = await controller.modifyTestFooBar(ctx); + + if (!isHttpResponseOK(response)) { + throw new Error('The returned value should be an HttpResponseOK object.'); + } + + const testFooBar = await getRepository(TestFooBar).findOne(testFooBar2.id); + + if (!testFooBar) { + throw new Error(); + } + + strictEqual(testFooBar.text, 'TestFooBar 2 (version 2)'); + + strictEqual(response.body.id, testFooBar.id); + strictEqual(response.body.text, testFooBar.text); + }); + + it('should not update the other testFooBars.', async () => { + const ctx = new Context({ + body: { + text: 'TestFooBar 2 (version 2)', + }, + params: { + testFooBarId: testFooBar2.id + } + }); + ctx.user = user2; + await controller.modifyTestFooBar(ctx); + + const testFooBar = await getRepository(TestFooBar).findOne(testFooBar1.id); + + if (!testFooBar) { + throw new Error(); + } + + notStrictEqual(testFooBar.text, 'TestFooBar 2 (version 2)'); + }); + + it('should return an HttpResponseNotFound if the object does not exist.', async () => { + const ctx = new Context({ + body: { + text: '', + }, + params: { + testFooBarId: -1 + } + }); + ctx.user = user2; + const response = await controller.modifyTestFooBar(ctx); + + if (!isHttpResponseNotFound(response)) { + throw new Error('The returned value should be an HttpResponseNotFound object.'); + } + }); + + it('should return an HttpResponseNotFound if the object belongs to another user.', async () => { + const ctx = new Context({ + body: { + text: '', + }, + params: { + testFooBarId: testFooBar0.id + } + }); + ctx.user = user2; + const response = await controller.modifyTestFooBar(ctx); + + if (!isHttpResponseNotFound(response)) { + throw new Error('The returned value should be an HttpResponseNotFound object.'); + } + }); + + }); + + describe('has a "replaceTestFooBar" method that', () => { + + it('should handle requests at PUT /:testFooBarId.', () => { + strictEqual(getHttpMethod(TestFooBarController, 'replaceTestFooBar'), 'PUT'); + strictEqual(getPath(TestFooBarController, 'replaceTestFooBar'), '/:testFooBarId'); + }); + + it('should update the testFooBar in the database and return it through an HttpResponseOK object.', async () => { + const ctx = new Context({ + body: { + text: 'TestFooBar 2 (version 2)', + }, + params: { + testFooBarId: testFooBar2.id + } + }); + ctx.user = user2; + const response = await controller.replaceTestFooBar(ctx); + + if (!isHttpResponseOK(response)) { + throw new Error('The returned value should be an HttpResponseOK object.'); + } + + const testFooBar = await getRepository(TestFooBar).findOne(testFooBar2.id); + + if (!testFooBar) { + throw new Error(); + } + + strictEqual(testFooBar.text, 'TestFooBar 2 (version 2)'); + + strictEqual(response.body.id, testFooBar.id); + strictEqual(response.body.text, testFooBar.text); + }); + + it('should not update the other testFooBars.', async () => { + const ctx = new Context({ + body: { + text: 'TestFooBar 2 (version 2)', + }, + params: { + testFooBarId: testFooBar2.id + } + }); + ctx.user = user2; + await controller.replaceTestFooBar(ctx); + + const testFooBar = await getRepository(TestFooBar).findOne(testFooBar1.id); + + if (!testFooBar) { + throw new Error(); + } + + notStrictEqual(testFooBar.text, 'TestFooBar 2 (version 2)'); + }); + + it('should return an HttpResponseNotFound if the object does not exist.', async () => { + const ctx = new Context({ + body: { + text: '', + }, + params: { + testFooBarId: -1 + } + }); + ctx.user = user2; + const response = await controller.replaceTestFooBar(ctx); + + if (!isHttpResponseNotFound(response)) { + throw new Error('The returned value should be an HttpResponseNotFound object.'); + } + }); + + it('should return an HttpResponseNotFound if the object belongs to another user.', async () => { + const ctx = new Context({ + body: { + text: '', + }, + params: { + testFooBarId: testFooBar0.id + } + }); + ctx.user = user2; + const response = await controller.replaceTestFooBar(ctx); + + if (!isHttpResponseNotFound(response)) { + throw new Error('The returned value should be an HttpResponseNotFound object.'); + } + }); + + }); + + describe('has a "deleteTestFooBar" method that', () => { + + it('should handle requests at DELETE /:testFooBarId.', () => { + strictEqual(getHttpMethod(TestFooBarController, 'deleteTestFooBar'), 'DELETE'); + strictEqual(getPath(TestFooBarController, 'deleteTestFooBar'), '/:testFooBarId'); + }); + + it('should delete the testFooBar and return an HttpResponseNoContent object.', async () => { + const ctx = new Context({ + params: { + testFooBarId: testFooBar2.id + } + }); + ctx.user = user2; + const response = await controller.deleteTestFooBar(ctx); + + if (!isHttpResponseNoContent(response)) { + throw new Error('The returned value should be an HttpResponseNoContent object.'); + } + + const testFooBar = await getRepository(TestFooBar).findOne(testFooBar2.id); + + strictEqual(testFooBar, undefined); + }); + + it('should not delete the other testFooBars.', async () => { + const ctx = new Context({ + params: { + testFooBarId: testFooBar2.id + } + }); + ctx.user = user2; + const response = await controller.deleteTestFooBar(ctx); + + if (!isHttpResponseNoContent(response)) { + throw new Error('The returned value should be an HttpResponseNoContent object.'); + } + + const testFooBar = await getRepository(TestFooBar).findOne(testFooBar1.id); + + notStrictEqual(testFooBar, undefined); + }); + + it('should return an HttpResponseNotFound if the testFooBar was not found.', async () => { + const ctx = new Context({ + params: { + testFooBarId: -1 + } + }); + ctx.user = user2; + const response = await controller.deleteTestFooBar(ctx); + + if (!isHttpResponseNotFound(response)) { + throw new Error('The returned value should be an HttpResponseNotFound object.'); + } + }); + + it('should return an HttpResponseNotFound if the testFooBar belongs to another user.', async () => { + const ctx = new Context({ + params: { + testFooBarId: testFooBar0.id + } + }); + ctx.user = user2; + const response = await controller.deleteTestFooBar(ctx); + + if (!isHttpResponseNotFound(response)) { + throw new Error('The returned value should be an HttpResponseNotFound object.'); + } + }); + + }); + +}); diff --git a/packages/cli/src/generate/specs/rest-api/test-foo-bar.controller.spec.current-dir.auth.ts b/packages/cli/src/generate/specs/rest-api/test-foo-bar.controller.spec.current-dir.auth.ts new file mode 100644 index 0000000000..d97cbe009f --- /dev/null +++ b/packages/cli/src/generate/specs/rest-api/test-foo-bar.controller.spec.current-dir.auth.ts @@ -0,0 +1,470 @@ +// std +import { notStrictEqual, ok, strictEqual } from 'assert'; + +// 3p +import { + Context, createController, getHttpMethod, getPath, + isHttpResponseCreated, isHttpResponseNoContent, + isHttpResponseNotFound, isHttpResponseOK +} from '@foal/core'; +import { createConnection, getConnection, getRepository } from 'typeorm'; + +// App +import { TestFooBarController } from './test-foo-bar.controller'; +import { TestFooBar } from './test-foo-bar.entity'; +import { User } from './user.entity'; + +describe('TestFooBarController', () => { + + let controller: TestFooBarController; + let testFooBar0: TestFooBar; + let testFooBar1: TestFooBar; + let testFooBar2: TestFooBar; + let user1: User; + let user2: User; + + before(() => createConnection()); + + after(() => getConnection().close()); + + beforeEach(async () => { + controller = createController(TestFooBarController); + + const testFooBarRepository = getRepository(TestFooBar); + const userRepository = getRepository(User); + + await testFooBarRepository.clear(); + await userRepository.clear(); + + [ user1, user2 ] = await userRepository.save([ + {}, + {}, + ]); + + [ testFooBar0, testFooBar1, testFooBar2 ] = await testFooBarRepository.save([ + { + text: 'TestFooBar 0', + owner: user1 + }, + { + text: 'TestFooBar 1', + owner: user2 + }, + { + text: 'TestFooBar 2', + owner: user2 + }, + ]); + }); + + describe('has a "findTestFooBars" method that', () => { + + it('should handle requests at GET /.', () => { + strictEqual(getHttpMethod(TestFooBarController, 'findTestFooBars'), 'GET'); + strictEqual(getPath(TestFooBarController, 'findTestFooBars'), undefined); + }); + + it('should return an HttpResponseOK object with the testFooBar list.', async () => { + const ctx = new Context({ query: {} }); + ctx.user = user2; + const response = await controller.findTestFooBars(ctx); + + if (!isHttpResponseOK(response)) { + throw new Error('The returned value should be an HttpResponseOK object.'); + } + + if (!Array.isArray(response.body)) { + throw new Error('The response body should be an array of testFooBars.'); + } + + strictEqual(response.body.length, 2); + ok(response.body.find(testFooBar => testFooBar.text === testFooBar1.text)); + ok(response.body.find(testFooBar => testFooBar.text === testFooBar2.text)); + }); + + it('should support pagination', async () => { + const testFooBar3 = await getRepository(TestFooBar).save({ + text: 'TestFooBar 3', + owner: user2 + }); + + let ctx = new Context({ + query: { + take: 2 + } + }); + ctx.user = user2; + let response = await controller.findTestFooBars(ctx); + + strictEqual(response.body.length, 2); + ok(response.body.find(testFooBar => testFooBar.id === testFooBar1.id)); + ok(response.body.find(testFooBar => testFooBar.id === testFooBar2.id)); + ok(!response.body.find(testFooBar => testFooBar.id === testFooBar3.id)); + + ctx = new Context({ + query: { + skip: 1 + } + }); + ctx.user = user2; + response = await controller.findTestFooBars(ctx); + + strictEqual(response.body.length, 2); + ok(!response.body.find(testFooBar => testFooBar.id === testFooBar1.id)); + ok(response.body.find(testFooBar => testFooBar.id === testFooBar2.id)); + ok(response.body.find(testFooBar => testFooBar.id === testFooBar3.id)); + }); + + }); + + describe('has a "findTestFooBarById" method that', () => { + + it('should handle requests at GET /:testFooBarId.', () => { + strictEqual(getHttpMethod(TestFooBarController, 'findTestFooBarById'), 'GET'); + strictEqual(getPath(TestFooBarController, 'findTestFooBarById'), '/:testFooBarId'); + }); + + it('should return an HttpResponseOK object if the testFooBar was found.', async () => { + const ctx = new Context({ + params: { + testFooBarId: testFooBar2.id + } + }); + ctx.user = user2; + const response = await controller.findTestFooBarById(ctx); + + if (!isHttpResponseOK(response)) { + throw new Error('The returned value should be an HttpResponseOK object.'); + } + + strictEqual(response.body.id, testFooBar2.id); + strictEqual(response.body.text, testFooBar2.text); + }); + + it('should return an HttpResponseNotFound object if the testFooBar was not found.', async () => { + const ctx = new Context({ + params: { + testFooBarId: -1 + } + }); + ctx.user = user2; + const response = await controller.findTestFooBarById(ctx); + + if (!isHttpResponseNotFound(response)) { + throw new Error('The returned value should be an HttpResponseNotFound object.'); + } + }); + + it('should return an HttpResponseNotFound object if the testFooBar belongs to another user.', async () => { + const ctx = new Context({ + params: { + testFooBarId: testFooBar0.id + } + }); + ctx.user = user2; + const response = await controller.findTestFooBarById(ctx); + + if (!isHttpResponseNotFound(response)) { + throw new Error('The returned value should be an HttpResponseNotFound object.'); + } + }); + + }); + + describe('has a "createTestFooBar" method that', () => { + + it('should handle requests at POST /.', () => { + strictEqual(getHttpMethod(TestFooBarController, 'createTestFooBar'), 'POST'); + strictEqual(getPath(TestFooBarController, 'createTestFooBar'), undefined); + }); + + it('should create the testFooBar in the database and return it through ' + + 'an HttpResponseCreated object.', async () => { + const ctx = new Context({ + body: { + text: 'TestFooBar 3', + } + }); + ctx.user = user2; + const response = await controller.createTestFooBar(ctx); + + if (!isHttpResponseCreated(response)) { + throw new Error('The returned value should be an HttpResponseCreated object.'); + } + + const testFooBar = await getRepository(TestFooBar).findOne({ + where: { text: 'TestFooBar 3' }, + relations: [ 'owner' ] + }); + + if (!testFooBar) { + throw new Error('No testFooBar 3 was found in the database.'); + } + + strictEqual(testFooBar.text, 'TestFooBar 3'); + strictEqual(testFooBar.owner.id, user2.id); + + strictEqual(response.body.id, testFooBar.id); + strictEqual(response.body.text, testFooBar.text); + }); + + }); + + describe('has a "modifyTestFooBar" method that', () => { + + it('should handle requests at PATCH /:testFooBarId.', () => { + strictEqual(getHttpMethod(TestFooBarController, 'modifyTestFooBar'), 'PATCH'); + strictEqual(getPath(TestFooBarController, 'modifyTestFooBar'), '/:testFooBarId'); + }); + + it('should update the testFooBar in the database and return it through an HttpResponseOK object.', async () => { + const ctx = new Context({ + body: { + text: 'TestFooBar 2 (version 2)', + }, + params: { + testFooBarId: testFooBar2.id + } + }); + ctx.user = user2; + const response = await controller.modifyTestFooBar(ctx); + + if (!isHttpResponseOK(response)) { + throw new Error('The returned value should be an HttpResponseOK object.'); + } + + const testFooBar = await getRepository(TestFooBar).findOne(testFooBar2.id); + + if (!testFooBar) { + throw new Error(); + } + + strictEqual(testFooBar.text, 'TestFooBar 2 (version 2)'); + + strictEqual(response.body.id, testFooBar.id); + strictEqual(response.body.text, testFooBar.text); + }); + + it('should not update the other testFooBars.', async () => { + const ctx = new Context({ + body: { + text: 'TestFooBar 2 (version 2)', + }, + params: { + testFooBarId: testFooBar2.id + } + }); + ctx.user = user2; + await controller.modifyTestFooBar(ctx); + + const testFooBar = await getRepository(TestFooBar).findOne(testFooBar1.id); + + if (!testFooBar) { + throw new Error(); + } + + notStrictEqual(testFooBar.text, 'TestFooBar 2 (version 2)'); + }); + + it('should return an HttpResponseNotFound if the object does not exist.', async () => { + const ctx = new Context({ + body: { + text: '', + }, + params: { + testFooBarId: -1 + } + }); + ctx.user = user2; + const response = await controller.modifyTestFooBar(ctx); + + if (!isHttpResponseNotFound(response)) { + throw new Error('The returned value should be an HttpResponseNotFound object.'); + } + }); + + it('should return an HttpResponseNotFound if the object belongs to another user.', async () => { + const ctx = new Context({ + body: { + text: '', + }, + params: { + testFooBarId: testFooBar0.id + } + }); + ctx.user = user2; + const response = await controller.modifyTestFooBar(ctx); + + if (!isHttpResponseNotFound(response)) { + throw new Error('The returned value should be an HttpResponseNotFound object.'); + } + }); + + }); + + describe('has a "replaceTestFooBar" method that', () => { + + it('should handle requests at PUT /:testFooBarId.', () => { + strictEqual(getHttpMethod(TestFooBarController, 'replaceTestFooBar'), 'PUT'); + strictEqual(getPath(TestFooBarController, 'replaceTestFooBar'), '/:testFooBarId'); + }); + + it('should update the testFooBar in the database and return it through an HttpResponseOK object.', async () => { + const ctx = new Context({ + body: { + text: 'TestFooBar 2 (version 2)', + }, + params: { + testFooBarId: testFooBar2.id + } + }); + ctx.user = user2; + const response = await controller.replaceTestFooBar(ctx); + + if (!isHttpResponseOK(response)) { + throw new Error('The returned value should be an HttpResponseOK object.'); + } + + const testFooBar = await getRepository(TestFooBar).findOne(testFooBar2.id); + + if (!testFooBar) { + throw new Error(); + } + + strictEqual(testFooBar.text, 'TestFooBar 2 (version 2)'); + + strictEqual(response.body.id, testFooBar.id); + strictEqual(response.body.text, testFooBar.text); + }); + + it('should not update the other testFooBars.', async () => { + const ctx = new Context({ + body: { + text: 'TestFooBar 2 (version 2)', + }, + params: { + testFooBarId: testFooBar2.id + } + }); + ctx.user = user2; + await controller.replaceTestFooBar(ctx); + + const testFooBar = await getRepository(TestFooBar).findOne(testFooBar1.id); + + if (!testFooBar) { + throw new Error(); + } + + notStrictEqual(testFooBar.text, 'TestFooBar 2 (version 2)'); + }); + + it('should return an HttpResponseNotFound if the object does not exist.', async () => { + const ctx = new Context({ + body: { + text: '', + }, + params: { + testFooBarId: -1 + } + }); + ctx.user = user2; + const response = await controller.replaceTestFooBar(ctx); + + if (!isHttpResponseNotFound(response)) { + throw new Error('The returned value should be an HttpResponseNotFound object.'); + } + }); + + it('should return an HttpResponseNotFound if the object belongs to another user.', async () => { + const ctx = new Context({ + body: { + text: '', + }, + params: { + testFooBarId: testFooBar0.id + } + }); + ctx.user = user2; + const response = await controller.replaceTestFooBar(ctx); + + if (!isHttpResponseNotFound(response)) { + throw new Error('The returned value should be an HttpResponseNotFound object.'); + } + }); + + }); + + describe('has a "deleteTestFooBar" method that', () => { + + it('should handle requests at DELETE /:testFooBarId.', () => { + strictEqual(getHttpMethod(TestFooBarController, 'deleteTestFooBar'), 'DELETE'); + strictEqual(getPath(TestFooBarController, 'deleteTestFooBar'), '/:testFooBarId'); + }); + + it('should delete the testFooBar and return an HttpResponseNoContent object.', async () => { + const ctx = new Context({ + params: { + testFooBarId: testFooBar2.id + } + }); + ctx.user = user2; + const response = await controller.deleteTestFooBar(ctx); + + if (!isHttpResponseNoContent(response)) { + throw new Error('The returned value should be an HttpResponseNoContent object.'); + } + + const testFooBar = await getRepository(TestFooBar).findOne(testFooBar2.id); + + strictEqual(testFooBar, undefined); + }); + + it('should not delete the other testFooBars.', async () => { + const ctx = new Context({ + params: { + testFooBarId: testFooBar2.id + } + }); + ctx.user = user2; + const response = await controller.deleteTestFooBar(ctx); + + if (!isHttpResponseNoContent(response)) { + throw new Error('The returned value should be an HttpResponseNoContent object.'); + } + + const testFooBar = await getRepository(TestFooBar).findOne(testFooBar1.id); + + notStrictEqual(testFooBar, undefined); + }); + + it('should return an HttpResponseNotFound if the testFooBar was not found.', async () => { + const ctx = new Context({ + params: { + testFooBarId: -1 + } + }); + ctx.user = user2; + const response = await controller.deleteTestFooBar(ctx); + + if (!isHttpResponseNotFound(response)) { + throw new Error('The returned value should be an HttpResponseNotFound object.'); + } + }); + + it('should return an HttpResponseNotFound if the testFooBar belongs to another user.', async () => { + const ctx = new Context({ + params: { + testFooBarId: testFooBar0.id + } + }); + ctx.user = user2; + const response = await controller.deleteTestFooBar(ctx); + + if (!isHttpResponseNotFound(response)) { + throw new Error('The returned value should be an HttpResponseNotFound object.'); + } + }); + + }); + +}); diff --git a/packages/cli/src/generate/specs/rest-api/test-foo-bar.controller.spec.current-dir.ts b/packages/cli/src/generate/specs/rest-api/test-foo-bar.controller.spec.current-dir.ts index 8fe3ed7340..8b9f0790a2 100644 --- a/packages/cli/src/generate/specs/rest-api/test-foo-bar.controller.spec.current-dir.ts +++ b/packages/cli/src/generate/specs/rest-api/test-foo-bar.controller.spec.current-dir.ts @@ -351,7 +351,7 @@ describe('TestFooBarController', () => { notStrictEqual(testFooBar, undefined); }); - it('should return an HttpResponseNotFound if the testFooBar was not fond.', async () => { + it('should return an HttpResponseNotFound if the testFooBar was not found.', async () => { const ctx = new Context({ params: { testFooBarId: -1 diff --git a/packages/cli/src/generate/specs/rest-api/test-foo-bar.controller.spec.ts b/packages/cli/src/generate/specs/rest-api/test-foo-bar.controller.spec.ts index ea85e2f578..d82a90b23d 100644 --- a/packages/cli/src/generate/specs/rest-api/test-foo-bar.controller.spec.ts +++ b/packages/cli/src/generate/specs/rest-api/test-foo-bar.controller.spec.ts @@ -351,7 +351,7 @@ describe('TestFooBarController', () => { notStrictEqual(testFooBar, undefined); }); - it('should return an HttpResponseNotFound if the testFooBar was not fond.', async () => { + it('should return an HttpResponseNotFound if the testFooBar was not found.', async () => { const ctx = new Context({ params: { testFooBarId: -1 diff --git a/packages/cli/src/generate/specs/rest-api/test-foo-bar.entity.auth.ts b/packages/cli/src/generate/specs/rest-api/test-foo-bar.entity.auth.ts new file mode 100644 index 0000000000..c5fcbc9fc4 --- /dev/null +++ b/packages/cli/src/generate/specs/rest-api/test-foo-bar.entity.auth.ts @@ -0,0 +1,17 @@ +import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'; + +import { User } from './user.entity'; + +@Entity() +export class TestFooBar { + + @PrimaryGeneratedColumn() + id: number; + + @Column() + text: string; + + @ManyToOne(() => User, { nullable: false }) + owner: User; + +} diff --git a/packages/cli/src/generate/templates/rest-api/controller.auth.ts b/packages/cli/src/generate/templates/rest-api/controller.auth.ts new file mode 100644 index 0000000000..28926eaca0 --- /dev/null +++ b/packages/cli/src/generate/templates/rest-api/controller.auth.ts @@ -0,0 +1,154 @@ +import { + ApiOperationDescription, ApiOperationId, ApiOperationSummary, ApiResponse, + ApiUseTag, Context, Delete, Get, HttpResponseCreated, + HttpResponseNoContent, HttpResponseNotFound, HttpResponseOK, Patch, Post, + Put, ValidateBody, ValidateParams, ValidateQuery +} from '@foal/core'; +import { getRepository } from 'typeorm'; + +import { /* upperFirstCamelName */, User } from '../entities'; + +const /* camelName */Schema = { + additionalProperties: false, + properties: { + text: { type: 'string', maxLength: 255 }, + }, + required: [ 'text' ], + type: 'object', +}; + +@ApiUseTag('/* camelName */') +export class /* upperFirstCamelName */Controller { + + @Get() + @ApiOperationId('find/* upperFirstCamelName */s') + @ApiOperationSummary('Find /* camelName */s.') + @ApiOperationDescription( + 'The query parameters "skip" and "take" can be used for pagination. The first ' + + 'is the offset and the second is the number of elements to be returned.' + ) + @ApiResponse(400, { description: 'Invalid query parameters.' }) + @ApiResponse(200, { description: 'Returns a list of /* camelName */s.' }) + @ValidateQuery({ + properties: { + skip: { type: 'number' }, + take: { type: 'number' }, + }, + type: 'object', + }) + async find/* upperFirstCamelName */s(ctx: Context) { + const /* camelName */s = await getRepository(/* upperFirstCamelName */).find({ + skip: ctx.request.query.skip, + take: ctx.request.query.take, + where: { + owner: ctx.user + } + }); + return new HttpResponseOK(/* camelName */s); + } + + @Get('/:/* camelName */Id') + @ApiOperationId('find/* upperFirstCamelName */ById') + @ApiOperationSummary('Find a /* camelName */ by ID.') + @ApiResponse(404, { description: '/* upperFirstCamelName */ not found.' }) + @ApiResponse(200, { description: 'Returns the /* camelName */.' }) + @ValidateParams({ properties: { /* camelName */Id: { type: 'number' } }, type: 'object' }) + async find/* upperFirstCamelName */ById(ctx: Context) { + const /* camelName */ = await getRepository(/* upperFirstCamelName */).findOne({ + id: ctx.request.params./* camelName */Id, + owner: ctx.user + }); + + if (!/* camelName */) { + return new HttpResponseNotFound(); + } + + return new HttpResponseOK(/* camelName */); + } + + @Post() + @ApiOperationId('create/* upperFirstCamelName */') + @ApiOperationSummary('Create a new /* camelName */.') + @ApiResponse(400, { description: 'Invalid /* camelName */.' }) + @ApiResponse(201, { description: '/* upperFirstCamelName */ successfully created. Returns the /* camelName */.' }) + @ValidateBody(/* camelName */Schema) + async create/* upperFirstCamelName */(ctx: Context) { + const /* camelName */ = await getRepository(/* upperFirstCamelName */).save({ + ...ctx.request.body, + owner: ctx.user + }); + return new HttpResponseCreated(/* camelName */); + } + + @Patch('/:/* camelName */Id') + @ApiOperationId('modify/* upperFirstCamelName */') + @ApiOperationSummary('Update/modify an existing /* camelName */.') + @ApiResponse(400, { description: 'Invalid /* camelName */.' }) + @ApiResponse(404, { description: '/* upperFirstCamelName */ not found.' }) + @ApiResponse(200, { description: '/* upperFirstCamelName */ successfully updated. Returns the /* camelName */.' }) + @ValidateParams({ properties: { /* camelName */Id: { type: 'number' } }, type: 'object' }) + @ValidateBody({ .../* camelName */Schema, required: [] }) + async modify/* upperFirstCamelName */(ctx: Context) { + const /* camelName */ = await getRepository(/* upperFirstCamelName */).findOne({ + id: ctx.request.params./* camelName */Id, + owner: ctx.user + }); + + if (!/* camelName */) { + return new HttpResponseNotFound(); + } + + Object.assign(/* camelName */, ctx.request.body); + + await getRepository(/* upperFirstCamelName */).save(/* camelName */); + + return new HttpResponseOK(/* camelName */); + } + + @Put('/:/* camelName */Id') + @ApiOperationId('replace/* upperFirstCamelName */') + @ApiOperationSummary('Update/replace an existing /* camelName */.') + @ApiResponse(400, { description: 'Invalid /* camelName */.' }) + @ApiResponse(404, { description: '/* upperFirstCamelName */ not found.' }) + @ApiResponse(200, { description: '/* upperFirstCamelName */ successfully updated. Returns the /* camelName */.' }) + @ValidateParams({ properties: { /* camelName */Id: { type: 'number' } }, type: 'object' }) + @ValidateBody(/* camelName */Schema) + async replace/* upperFirstCamelName */(ctx: Context) { + const /* camelName */ = await getRepository(/* upperFirstCamelName */).findOne({ + id: ctx.request.params./* camelName */Id, + owner: ctx.user + }); + + if (!/* camelName */) { + return new HttpResponseNotFound(); + } + + Object.assign(/* camelName */, ctx.request.body); + + await getRepository(/* upperFirstCamelName */).save(/* camelName */); + + return new HttpResponseOK(/* camelName */); + } + + @Delete('/:/* camelName */Id') + @ApiOperationId('delete/* upperFirstCamelName */') + @ApiOperationSummary('Delete a /* camelName */.') + @ApiResponse(404, { description: '/* upperFirstCamelName */ not found.' }) + @ApiResponse(204, { description: '/* upperFirstCamelName */ successfully deleted.' }) + @ValidateParams({ properties: { /* camelName */Id: { type: 'number' } }, type: 'object' }) + async delete/* upperFirstCamelName */(ctx: Context) { + const /* camelName */ = await getRepository(/* upperFirstCamelName */).findOne({ + id: ctx.request.params./* camelName */Id, + owner: ctx.user + }); + + if (!/* camelName */) { + return new HttpResponseNotFound(); + } + + await getRepository(/* upperFirstCamelName */).delete(ctx.request.params./* camelName */Id); + + return new HttpResponseNoContent(); + } + +} diff --git a/packages/cli/src/generate/templates/rest-api/controller.current-dir.auth.ts b/packages/cli/src/generate/templates/rest-api/controller.current-dir.auth.ts new file mode 100644 index 0000000000..4f5c383249 --- /dev/null +++ b/packages/cli/src/generate/templates/rest-api/controller.current-dir.auth.ts @@ -0,0 +1,155 @@ +import { + ApiOperationDescription, ApiOperationId, ApiOperationSummary, ApiResponse, + ApiUseTag, Context, Delete, Get, HttpResponseCreated, + HttpResponseNoContent, HttpResponseNotFound, HttpResponseOK, Patch, Post, + Put, ValidateBody, ValidateParams, ValidateQuery +} from '@foal/core'; +import { getRepository } from 'typeorm'; + +import { /* upperFirstCamelName */ } from './/* kebabName */.entity'; +import { User } from './user.entity'; + +const /* camelName */Schema = { + additionalProperties: false, + properties: { + text: { type: 'string', maxLength: 255 }, + }, + required: [ 'text' ], + type: 'object', +}; + +@ApiUseTag('/* camelName */') +export class /* upperFirstCamelName */Controller { + + @Get() + @ApiOperationId('find/* upperFirstCamelName */s') + @ApiOperationSummary('Find /* camelName */s.') + @ApiOperationDescription( + 'The query parameters "skip" and "take" can be used for pagination. The first ' + + 'is the offset and the second is the number of elements to be returned.' + ) + @ApiResponse(400, { description: 'Invalid query parameters.' }) + @ApiResponse(200, { description: 'Returns a list of /* camelName */s.' }) + @ValidateQuery({ + properties: { + skip: { type: 'number' }, + take: { type: 'number' }, + }, + type: 'object', + }) + async find/* upperFirstCamelName */s(ctx: Context) { + const /* camelName */s = await getRepository(/* upperFirstCamelName */).find({ + skip: ctx.request.query.skip, + take: ctx.request.query.take, + where: { + owner: ctx.user + } + }); + return new HttpResponseOK(/* camelName */s); + } + + @Get('/:/* camelName */Id') + @ApiOperationId('find/* upperFirstCamelName */ById') + @ApiOperationSummary('Find a /* camelName */ by ID.') + @ApiResponse(404, { description: '/* upperFirstCamelName */ not found.' }) + @ApiResponse(200, { description: 'Returns the /* camelName */.' }) + @ValidateParams({ properties: { /* camelName */Id: { type: 'number' } }, type: 'object' }) + async find/* upperFirstCamelName */ById(ctx: Context) { + const /* camelName */ = await getRepository(/* upperFirstCamelName */).findOne({ + id: ctx.request.params./* camelName */Id, + owner: ctx.user + }); + + if (!/* camelName */) { + return new HttpResponseNotFound(); + } + + return new HttpResponseOK(/* camelName */); + } + + @Post() + @ApiOperationId('create/* upperFirstCamelName */') + @ApiOperationSummary('Create a new /* camelName */.') + @ApiResponse(400, { description: 'Invalid /* camelName */.' }) + @ApiResponse(201, { description: '/* upperFirstCamelName */ successfully created. Returns the /* camelName */.' }) + @ValidateBody(/* camelName */Schema) + async create/* upperFirstCamelName */(ctx: Context) { + const /* camelName */ = await getRepository(/* upperFirstCamelName */).save({ + ...ctx.request.body, + owner: ctx.user + }); + return new HttpResponseCreated(/* camelName */); + } + + @Patch('/:/* camelName */Id') + @ApiOperationId('modify/* upperFirstCamelName */') + @ApiOperationSummary('Update/modify an existing /* camelName */.') + @ApiResponse(400, { description: 'Invalid /* camelName */.' }) + @ApiResponse(404, { description: '/* upperFirstCamelName */ not found.' }) + @ApiResponse(200, { description: '/* upperFirstCamelName */ successfully updated. Returns the /* camelName */.' }) + @ValidateParams({ properties: { /* camelName */Id: { type: 'number' } }, type: 'object' }) + @ValidateBody({ .../* camelName */Schema, required: [] }) + async modify/* upperFirstCamelName */(ctx: Context) { + const /* camelName */ = await getRepository(/* upperFirstCamelName */).findOne({ + id: ctx.request.params./* camelName */Id, + owner: ctx.user + }); + + if (!/* camelName */) { + return new HttpResponseNotFound(); + } + + Object.assign(/* camelName */, ctx.request.body); + + await getRepository(/* upperFirstCamelName */).save(/* camelName */); + + return new HttpResponseOK(/* camelName */); + } + + @Put('/:/* camelName */Id') + @ApiOperationId('replace/* upperFirstCamelName */') + @ApiOperationSummary('Update/replace an existing /* camelName */.') + @ApiResponse(400, { description: 'Invalid /* camelName */.' }) + @ApiResponse(404, { description: '/* upperFirstCamelName */ not found.' }) + @ApiResponse(200, { description: '/* upperFirstCamelName */ successfully updated. Returns the /* camelName */.' }) + @ValidateParams({ properties: { /* camelName */Id: { type: 'number' } }, type: 'object' }) + @ValidateBody(/* camelName */Schema) + async replace/* upperFirstCamelName */(ctx: Context) { + const /* camelName */ = await getRepository(/* upperFirstCamelName */).findOne({ + id: ctx.request.params./* camelName */Id, + owner: ctx.user + }); + + if (!/* camelName */) { + return new HttpResponseNotFound(); + } + + Object.assign(/* camelName */, ctx.request.body); + + await getRepository(/* upperFirstCamelName */).save(/* camelName */); + + return new HttpResponseOK(/* camelName */); + } + + @Delete('/:/* camelName */Id') + @ApiOperationId('delete/* upperFirstCamelName */') + @ApiOperationSummary('Delete a /* camelName */.') + @ApiResponse(404, { description: '/* upperFirstCamelName */ not found.' }) + @ApiResponse(204, { description: '/* upperFirstCamelName */ successfully deleted.' }) + @ValidateParams({ properties: { /* camelName */Id: { type: 'number' } }, type: 'object' }) + async delete/* upperFirstCamelName */(ctx: Context) { + const /* camelName */ = await getRepository(/* upperFirstCamelName */).findOne({ + id: ctx.request.params./* camelName */Id, + owner: ctx.user + }); + + if (!/* camelName */) { + return new HttpResponseNotFound(); + } + + await getRepository(/* upperFirstCamelName */).delete(ctx.request.params./* camelName */Id); + + return new HttpResponseNoContent(); + } + +} diff --git a/packages/cli/src/generate/templates/rest-api/controller.spec.auth.ts b/packages/cli/src/generate/templates/rest-api/controller.spec.auth.ts new file mode 100644 index 0000000000..58fba43aca --- /dev/null +++ b/packages/cli/src/generate/templates/rest-api/controller.spec.auth.ts @@ -0,0 +1,469 @@ +// std +import { notStrictEqual, ok, strictEqual } from 'assert'; + +// 3p +import { + Context, createController, getHttpMethod, getPath, + isHttpResponseCreated, isHttpResponseNoContent, + isHttpResponseNotFound, isHttpResponseOK +} from '@foal/core'; +import { createConnection, getConnection, getRepository } from 'typeorm'; + +// App +import { /* upperFirstCamelName */, User } from '../entities'; +import { /* upperFirstCamelName */Controller } from './/* kebabName */.controller'; + +describe('/* upperFirstCamelName */Controller', () => { + + let controller: /* upperFirstCamelName */Controller; + let /* camelName */0: /* upperFirstCamelName */; + let /* camelName */1: /* upperFirstCamelName */; + let /* camelName */2: /* upperFirstCamelName */; + let user1: User; + let user2: User; + + before(() => createConnection()); + + after(() => getConnection().close()); + + beforeEach(async () => { + controller = createController(/* upperFirstCamelName */Controller); + + const /* camelName */Repository = getRepository(/* upperFirstCamelName */); + const userRepository = getRepository(User); + + await /* camelName */Repository.clear(); + await userRepository.clear(); + + [ user1, user2 ] = await userRepository.save([ + {}, + {}, + ]); + + [ /* camelName */0, /* camelName */1, /* camelName */2 ] = await /* camelName */Repository.save([ + { + text: '/* upperFirstCamelName */ 0', + owner: user1 + }, + { + text: '/* upperFirstCamelName */ 1', + owner: user2 + }, + { + text: '/* upperFirstCamelName */ 2', + owner: user2 + }, + ]); + }); + + describe('has a "find/* upperFirstCamelName */s" method that', () => { + + it('should handle requests at GET /.', () => { + strictEqual(getHttpMethod(/* upperFirstCamelName */Controller, 'find/* upperFirstCamelName */s'), 'GET'); + strictEqual(getPath(/* upperFirstCamelName */Controller, 'find/* upperFirstCamelName */s'), undefined); + }); + + it('should return an HttpResponseOK object with the /* camelName */ list.', async () => { + const ctx = new Context({ query: {} }); + ctx.user = user2; + const response = await controller.find/* upperFirstCamelName */s(ctx); + + if (!isHttpResponseOK(response)) { + throw new Error('The returned value should be an HttpResponseOK object.'); + } + + if (!Array.isArray(response.body)) { + throw new Error('The response body should be an array of /* camelName */s.'); + } + + strictEqual(response.body.length, 2); + ok(response.body.find(/* camelName */ => /* camelName */.text === /* camelName */1.text)); + ok(response.body.find(/* camelName */ => /* camelName */.text === /* camelName */2.text)); + }); + + it('should support pagination', async () => { + const /* camelName */3 = await getRepository(/* upperFirstCamelName */).save({ + text: '/* upperFirstCamelName */ 3', + owner: user2 + }); + + let ctx = new Context({ + query: { + take: 2 + } + }); + ctx.user = user2; + let response = await controller.find/* upperFirstCamelName */s(ctx); + + strictEqual(response.body.length, 2); + ok(response.body.find(/* camelName */ => /* camelName */.id === /* camelName */1.id)); + ok(response.body.find(/* camelName */ => /* camelName */.id === /* camelName */2.id)); + ok(!response.body.find(/* camelName */ => /* camelName */.id === /* camelName */3.id)); + + ctx = new Context({ + query: { + skip: 1 + } + }); + ctx.user = user2; + response = await controller.find/* upperFirstCamelName */s(ctx); + + strictEqual(response.body.length, 2); + ok(!response.body.find(/* camelName */ => /* camelName */.id === /* camelName */1.id)); + ok(response.body.find(/* camelName */ => /* camelName */.id === /* camelName */2.id)); + ok(response.body.find(/* camelName */ => /* camelName */.id === /* camelName */3.id)); + }); + + }); + + describe('has a "find/* upperFirstCamelName */ById" method that', () => { + + it('should handle requests at GET /:/* camelName */Id.', () => { + strictEqual(getHttpMethod(/* upperFirstCamelName */Controller, 'find/* upperFirstCamelName */ById'), 'GET'); + strictEqual(getPath(/* upperFirstCamelName */Controller, 'find/* upperFirstCamelName */ById'), '/:/* camelName */Id'); + }); + + it('should return an HttpResponseOK object if the /* camelName */ was found.', async () => { + const ctx = new Context({ + params: { + /* camelName */Id: /* camelName */2.id + } + }); + ctx.user = user2; + const response = await controller.find/* upperFirstCamelName */ById(ctx); + + if (!isHttpResponseOK(response)) { + throw new Error('The returned value should be an HttpResponseOK object.'); + } + + strictEqual(response.body.id, /* camelName */2.id); + strictEqual(response.body.text, /* camelName */2.text); + }); + + it('should return an HttpResponseNotFound object if the /* camelName */ was not found.', async () => { + const ctx = new Context({ + params: { + /* camelName */Id: -1 + } + }); + ctx.user = user2; + const response = await controller.find/* upperFirstCamelName */ById(ctx); + + if (!isHttpResponseNotFound(response)) { + throw new Error('The returned value should be an HttpResponseNotFound object.'); + } + }); + + it('should return an HttpResponseNotFound object if the /* camelName */ belongs to another user.', async () => { + const ctx = new Context({ + params: { + /* camelName */Id: /* camelName */0.id + } + }); + ctx.user = user2; + const response = await controller.find/* upperFirstCamelName */ById(ctx); + + if (!isHttpResponseNotFound(response)) { + throw new Error('The returned value should be an HttpResponseNotFound object.'); + } + }); + + }); + + describe('has a "create/* upperFirstCamelName */" method that', () => { + + it('should handle requests at POST /.', () => { + strictEqual(getHttpMethod(/* upperFirstCamelName */Controller, 'create/* upperFirstCamelName */'), 'POST'); + strictEqual(getPath(/* upperFirstCamelName */Controller, 'create/* upperFirstCamelName */'), undefined); + }); + + it('should create the /* camelName */ in the database and return it through ' + + 'an HttpResponseCreated object.', async () => { + const ctx = new Context({ + body: { + text: '/* upperFirstCamelName */ 3', + } + }); + ctx.user = user2; + const response = await controller.create/* upperFirstCamelName */(ctx); + + if (!isHttpResponseCreated(response)) { + throw new Error('The returned value should be an HttpResponseCreated object.'); + } + + const /* camelName */ = await getRepository(/* upperFirstCamelName */).findOne({ + where: { text: '/* upperFirstCamelName */ 3' }, + relations: [ 'owner' ] + }); + + if (!/* camelName */) { + throw new Error('No /* camelName */ 3 was found in the database.'); + } + + strictEqual(/* camelName */.text, '/* upperFirstCamelName */ 3'); + strictEqual(/* camelName */.owner.id, user2.id); + + strictEqual(response.body.id, /* camelName */.id); + strictEqual(response.body.text, /* camelName */.text); + }); + + }); + + describe('has a "modify/* upperFirstCamelName */" method that', () => { + + it('should handle requests at PATCH /:/* camelName */Id.', () => { + strictEqual(getHttpMethod(/* upperFirstCamelName */Controller, 'modify/* upperFirstCamelName */'), 'PATCH'); + strictEqual(getPath(/* upperFirstCamelName */Controller, 'modify/* upperFirstCamelName */'), '/:/* camelName */Id'); + }); + + it('should update the /* camelName */ in the database and return it through an HttpResponseOK object.', async () => { + const ctx = new Context({ + body: { + text: '/* upperFirstCamelName */ 2 (version 2)', + }, + params: { + /* camelName */Id: /* camelName */2.id + } + }); + ctx.user = user2; + const response = await controller.modify/* upperFirstCamelName */(ctx); + + if (!isHttpResponseOK(response)) { + throw new Error('The returned value should be an HttpResponseOK object.'); + } + + const /* camelName */ = await getRepository(/* upperFirstCamelName */).findOne(/* camelName */2.id); + + if (!/* camelName */) { + throw new Error(); + } + + strictEqual(/* camelName */.text, '/* upperFirstCamelName */ 2 (version 2)'); + + strictEqual(response.body.id, /* camelName */.id); + strictEqual(response.body.text, /* camelName */.text); + }); + + it('should not update the other /* camelName */s.', async () => { + const ctx = new Context({ + body: { + text: '/* upperFirstCamelName */ 2 (version 2)', + }, + params: { + /* camelName */Id: /* camelName */2.id + } + }); + ctx.user = user2; + await controller.modify/* upperFirstCamelName */(ctx); + + const /* camelName */ = await getRepository(/* upperFirstCamelName */).findOne(/* camelName */1.id); + + if (!/* camelName */) { + throw new Error(); + } + + notStrictEqual(/* camelName */.text, '/* upperFirstCamelName */ 2 (version 2)'); + }); + + it('should return an HttpResponseNotFound if the object does not exist.', async () => { + const ctx = new Context({ + body: { + text: '', + }, + params: { + /* camelName */Id: -1 + } + }); + ctx.user = user2; + const response = await controller.modify/* upperFirstCamelName */(ctx); + + if (!isHttpResponseNotFound(response)) { + throw new Error('The returned value should be an HttpResponseNotFound object.'); + } + }); + + it('should return an HttpResponseNotFound if the object belongs to another user.', async () => { + const ctx = new Context({ + body: { + text: '', + }, + params: { + /* camelName */Id: /* camelName */0.id + } + }); + ctx.user = user2; + const response = await controller.modify/* upperFirstCamelName */(ctx); + + if (!isHttpResponseNotFound(response)) { + throw new Error('The returned value should be an HttpResponseNotFound object.'); + } + }); + + }); + + describe('has a "replace/* upperFirstCamelName */" method that', () => { + + it('should handle requests at PUT /:/* camelName */Id.', () => { + strictEqual(getHttpMethod(/* upperFirstCamelName */Controller, 'replace/* upperFirstCamelName */'), 'PUT'); + strictEqual(getPath(/* upperFirstCamelName */Controller, 'replace/* upperFirstCamelName */'), '/:/* camelName */Id'); + }); + + it('should update the /* camelName */ in the database and return it through an HttpResponseOK object.', async () => { + const ctx = new Context({ + body: { + text: '/* upperFirstCamelName */ 2 (version 2)', + }, + params: { + /* camelName */Id: /* camelName */2.id + } + }); + ctx.user = user2; + const response = await controller.replace/* upperFirstCamelName */(ctx); + + if (!isHttpResponseOK(response)) { + throw new Error('The returned value should be an HttpResponseOK object.'); + } + + const /* camelName */ = await getRepository(/* upperFirstCamelName */).findOne(/* camelName */2.id); + + if (!/* camelName */) { + throw new Error(); + } + + strictEqual(/* camelName */.text, '/* upperFirstCamelName */ 2 (version 2)'); + + strictEqual(response.body.id, /* camelName */.id); + strictEqual(response.body.text, /* camelName */.text); + }); + + it('should not update the other /* camelName */s.', async () => { + const ctx = new Context({ + body: { + text: '/* upperFirstCamelName */ 2 (version 2)', + }, + params: { + /* camelName */Id: /* camelName */2.id + } + }); + ctx.user = user2; + await controller.replace/* upperFirstCamelName */(ctx); + + const /* camelName */ = await getRepository(/* upperFirstCamelName */).findOne(/* camelName */1.id); + + if (!/* camelName */) { + throw new Error(); + } + + notStrictEqual(/* camelName */.text, '/* upperFirstCamelName */ 2 (version 2)'); + }); + + it('should return an HttpResponseNotFound if the object does not exist.', async () => { + const ctx = new Context({ + body: { + text: '', + }, + params: { + /* camelName */Id: -1 + } + }); + ctx.user = user2; + const response = await controller.replace/* upperFirstCamelName */(ctx); + + if (!isHttpResponseNotFound(response)) { + throw new Error('The returned value should be an HttpResponseNotFound object.'); + } + }); + + it('should return an HttpResponseNotFound if the object belongs to another user.', async () => { + const ctx = new Context({ + body: { + text: '', + }, + params: { + /* camelName */Id: /* camelName */0.id + } + }); + ctx.user = user2; + const response = await controller.replace/* upperFirstCamelName */(ctx); + + if (!isHttpResponseNotFound(response)) { + throw new Error('The returned value should be an HttpResponseNotFound object.'); + } + }); + + }); + + describe('has a "delete/* upperFirstCamelName */" method that', () => { + + it('should handle requests at DELETE /:/* camelName */Id.', () => { + strictEqual(getHttpMethod(/* upperFirstCamelName */Controller, 'delete/* upperFirstCamelName */'), 'DELETE'); + strictEqual(getPath(/* upperFirstCamelName */Controller, 'delete/* upperFirstCamelName */'), '/:/* camelName */Id'); + }); + + it('should delete the /* camelName */ and return an HttpResponseNoContent object.', async () => { + const ctx = new Context({ + params: { + /* camelName */Id: /* camelName */2.id + } + }); + ctx.user = user2; + const response = await controller.delete/* upperFirstCamelName */(ctx); + + if (!isHttpResponseNoContent(response)) { + throw new Error('The returned value should be an HttpResponseNoContent object.'); + } + + const /* camelName */ = await getRepository(/* upperFirstCamelName */).findOne(/* camelName */2.id); + + strictEqual(/* camelName */, undefined); + }); + + it('should not delete the other /* camelName */s.', async () => { + const ctx = new Context({ + params: { + /* camelName */Id: /* camelName */2.id + } + }); + ctx.user = user2; + const response = await controller.delete/* upperFirstCamelName */(ctx); + + if (!isHttpResponseNoContent(response)) { + throw new Error('The returned value should be an HttpResponseNoContent object.'); + } + + const /* camelName */ = await getRepository(/* upperFirstCamelName */).findOne(/* camelName */1.id); + + notStrictEqual(/* camelName */, undefined); + }); + + it('should return an HttpResponseNotFound if the /* camelName */ was not found.', async () => { + const ctx = new Context({ + params: { + /* camelName */Id: -1 + } + }); + ctx.user = user2; + const response = await controller.delete/* upperFirstCamelName */(ctx); + + if (!isHttpResponseNotFound(response)) { + throw new Error('The returned value should be an HttpResponseNotFound object.'); + } + }); + + it('should return an HttpResponseNotFound if the /* camelName */ belongs to another user.', async () => { + const ctx = new Context({ + params: { + /* camelName */Id: /* camelName */0.id + } + }); + ctx.user = user2; + const response = await controller.delete/* upperFirstCamelName */(ctx); + + if (!isHttpResponseNotFound(response)) { + throw new Error('The returned value should be an HttpResponseNotFound object.'); + } + }); + + }); + +}); diff --git a/packages/cli/src/generate/templates/rest-api/controller.spec.current-dir.auth.ts b/packages/cli/src/generate/templates/rest-api/controller.spec.current-dir.auth.ts new file mode 100644 index 0000000000..c027aeae24 --- /dev/null +++ b/packages/cli/src/generate/templates/rest-api/controller.spec.current-dir.auth.ts @@ -0,0 +1,470 @@ +// std +import { notStrictEqual, ok, strictEqual } from 'assert'; + +// 3p +import { + Context, createController, getHttpMethod, getPath, + isHttpResponseCreated, isHttpResponseNoContent, + isHttpResponseNotFound, isHttpResponseOK +} from '@foal/core'; +import { createConnection, getConnection, getRepository } from 'typeorm'; + +// App +import { /* upperFirstCamelName */Controller } from './/* kebabName */.controller'; +import { /* upperFirstCamelName */ } from './/* kebabName */.entity'; +import { User } from './user.entity'; + +describe('/* upperFirstCamelName */Controller', () => { + + let controller: /* upperFirstCamelName */Controller; + let /* camelName */0: /* upperFirstCamelName */; + let /* camelName */1: /* upperFirstCamelName */; + let /* camelName */2: /* upperFirstCamelName */; + let user1: User; + let user2: User; + + before(() => createConnection()); + + after(() => getConnection().close()); + + beforeEach(async () => { + controller = createController(/* upperFirstCamelName */Controller); + + const /* camelName */Repository = getRepository(/* upperFirstCamelName */); + const userRepository = getRepository(User); + + await /* camelName */Repository.clear(); + await userRepository.clear(); + + [ user1, user2 ] = await userRepository.save([ + {}, + {}, + ]); + + [ /* camelName */0, /* camelName */1, /* camelName */2 ] = await /* camelName */Repository.save([ + { + text: '/* upperFirstCamelName */ 0', + owner: user1 + }, + { + text: '/* upperFirstCamelName */ 1', + owner: user2 + }, + { + text: '/* upperFirstCamelName */ 2', + owner: user2 + }, + ]); + }); + + describe('has a "find/* upperFirstCamelName */s" method that', () => { + + it('should handle requests at GET /.', () => { + strictEqual(getHttpMethod(/* upperFirstCamelName */Controller, 'find/* upperFirstCamelName */s'), 'GET'); + strictEqual(getPath(/* upperFirstCamelName */Controller, 'find/* upperFirstCamelName */s'), undefined); + }); + + it('should return an HttpResponseOK object with the /* camelName */ list.', async () => { + const ctx = new Context({ query: {} }); + ctx.user = user2; + const response = await controller.find/* upperFirstCamelName */s(ctx); + + if (!isHttpResponseOK(response)) { + throw new Error('The returned value should be an HttpResponseOK object.'); + } + + if (!Array.isArray(response.body)) { + throw new Error('The response body should be an array of /* camelName */s.'); + } + + strictEqual(response.body.length, 2); + ok(response.body.find(/* camelName */ => /* camelName */.text === /* camelName */1.text)); + ok(response.body.find(/* camelName */ => /* camelName */.text === /* camelName */2.text)); + }); + + it('should support pagination', async () => { + const /* camelName */3 = await getRepository(/* upperFirstCamelName */).save({ + text: '/* upperFirstCamelName */ 3', + owner: user2 + }); + + let ctx = new Context({ + query: { + take: 2 + } + }); + ctx.user = user2; + let response = await controller.find/* upperFirstCamelName */s(ctx); + + strictEqual(response.body.length, 2); + ok(response.body.find(/* camelName */ => /* camelName */.id === /* camelName */1.id)); + ok(response.body.find(/* camelName */ => /* camelName */.id === /* camelName */2.id)); + ok(!response.body.find(/* camelName */ => /* camelName */.id === /* camelName */3.id)); + + ctx = new Context({ + query: { + skip: 1 + } + }); + ctx.user = user2; + response = await controller.find/* upperFirstCamelName */s(ctx); + + strictEqual(response.body.length, 2); + ok(!response.body.find(/* camelName */ => /* camelName */.id === /* camelName */1.id)); + ok(response.body.find(/* camelName */ => /* camelName */.id === /* camelName */2.id)); + ok(response.body.find(/* camelName */ => /* camelName */.id === /* camelName */3.id)); + }); + + }); + + describe('has a "find/* upperFirstCamelName */ById" method that', () => { + + it('should handle requests at GET /:/* camelName */Id.', () => { + strictEqual(getHttpMethod(/* upperFirstCamelName */Controller, 'find/* upperFirstCamelName */ById'), 'GET'); + strictEqual(getPath(/* upperFirstCamelName */Controller, 'find/* upperFirstCamelName */ById'), '/:/* camelName */Id'); + }); + + it('should return an HttpResponseOK object if the /* camelName */ was found.', async () => { + const ctx = new Context({ + params: { + /* camelName */Id: /* camelName */2.id + } + }); + ctx.user = user2; + const response = await controller.find/* upperFirstCamelName */ById(ctx); + + if (!isHttpResponseOK(response)) { + throw new Error('The returned value should be an HttpResponseOK object.'); + } + + strictEqual(response.body.id, /* camelName */2.id); + strictEqual(response.body.text, /* camelName */2.text); + }); + + it('should return an HttpResponseNotFound object if the /* camelName */ was not found.', async () => { + const ctx = new Context({ + params: { + /* camelName */Id: -1 + } + }); + ctx.user = user2; + const response = await controller.find/* upperFirstCamelName */ById(ctx); + + if (!isHttpResponseNotFound(response)) { + throw new Error('The returned value should be an HttpResponseNotFound object.'); + } + }); + + it('should return an HttpResponseNotFound object if the /* camelName */ belongs to another user.', async () => { + const ctx = new Context({ + params: { + /* camelName */Id: /* camelName */0.id + } + }); + ctx.user = user2; + const response = await controller.find/* upperFirstCamelName */ById(ctx); + + if (!isHttpResponseNotFound(response)) { + throw new Error('The returned value should be an HttpResponseNotFound object.'); + } + }); + + }); + + describe('has a "create/* upperFirstCamelName */" method that', () => { + + it('should handle requests at POST /.', () => { + strictEqual(getHttpMethod(/* upperFirstCamelName */Controller, 'create/* upperFirstCamelName */'), 'POST'); + strictEqual(getPath(/* upperFirstCamelName */Controller, 'create/* upperFirstCamelName */'), undefined); + }); + + it('should create the /* camelName */ in the database and return it through ' + + 'an HttpResponseCreated object.', async () => { + const ctx = new Context({ + body: { + text: '/* upperFirstCamelName */ 3', + } + }); + ctx.user = user2; + const response = await controller.create/* upperFirstCamelName */(ctx); + + if (!isHttpResponseCreated(response)) { + throw new Error('The returned value should be an HttpResponseCreated object.'); + } + + const /* camelName */ = await getRepository(/* upperFirstCamelName */).findOne({ + where: { text: '/* upperFirstCamelName */ 3' }, + relations: [ 'owner' ] + }); + + if (!/* camelName */) { + throw new Error('No /* camelName */ 3 was found in the database.'); + } + + strictEqual(/* camelName */.text, '/* upperFirstCamelName */ 3'); + strictEqual(/* camelName */.owner.id, user2.id); + + strictEqual(response.body.id, /* camelName */.id); + strictEqual(response.body.text, /* camelName */.text); + }); + + }); + + describe('has a "modify/* upperFirstCamelName */" method that', () => { + + it('should handle requests at PATCH /:/* camelName */Id.', () => { + strictEqual(getHttpMethod(/* upperFirstCamelName */Controller, 'modify/* upperFirstCamelName */'), 'PATCH'); + strictEqual(getPath(/* upperFirstCamelName */Controller, 'modify/* upperFirstCamelName */'), '/:/* camelName */Id'); + }); + + it('should update the /* camelName */ in the database and return it through an HttpResponseOK object.', async () => { + const ctx = new Context({ + body: { + text: '/* upperFirstCamelName */ 2 (version 2)', + }, + params: { + /* camelName */Id: /* camelName */2.id + } + }); + ctx.user = user2; + const response = await controller.modify/* upperFirstCamelName */(ctx); + + if (!isHttpResponseOK(response)) { + throw new Error('The returned value should be an HttpResponseOK object.'); + } + + const /* camelName */ = await getRepository(/* upperFirstCamelName */).findOne(/* camelName */2.id); + + if (!/* camelName */) { + throw new Error(); + } + + strictEqual(/* camelName */.text, '/* upperFirstCamelName */ 2 (version 2)'); + + strictEqual(response.body.id, /* camelName */.id); + strictEqual(response.body.text, /* camelName */.text); + }); + + it('should not update the other /* camelName */s.', async () => { + const ctx = new Context({ + body: { + text: '/* upperFirstCamelName */ 2 (version 2)', + }, + params: { + /* camelName */Id: /* camelName */2.id + } + }); + ctx.user = user2; + await controller.modify/* upperFirstCamelName */(ctx); + + const /* camelName */ = await getRepository(/* upperFirstCamelName */).findOne(/* camelName */1.id); + + if (!/* camelName */) { + throw new Error(); + } + + notStrictEqual(/* camelName */.text, '/* upperFirstCamelName */ 2 (version 2)'); + }); + + it('should return an HttpResponseNotFound if the object does not exist.', async () => { + const ctx = new Context({ + body: { + text: '', + }, + params: { + /* camelName */Id: -1 + } + }); + ctx.user = user2; + const response = await controller.modify/* upperFirstCamelName */(ctx); + + if (!isHttpResponseNotFound(response)) { + throw new Error('The returned value should be an HttpResponseNotFound object.'); + } + }); + + it('should return an HttpResponseNotFound if the object belongs to another user.', async () => { + const ctx = new Context({ + body: { + text: '', + }, + params: { + /* camelName */Id: /* camelName */0.id + } + }); + ctx.user = user2; + const response = await controller.modify/* upperFirstCamelName */(ctx); + + if (!isHttpResponseNotFound(response)) { + throw new Error('The returned value should be an HttpResponseNotFound object.'); + } + }); + + }); + + describe('has a "replace/* upperFirstCamelName */" method that', () => { + + it('should handle requests at PUT /:/* camelName */Id.', () => { + strictEqual(getHttpMethod(/* upperFirstCamelName */Controller, 'replace/* upperFirstCamelName */'), 'PUT'); + strictEqual(getPath(/* upperFirstCamelName */Controller, 'replace/* upperFirstCamelName */'), '/:/* camelName */Id'); + }); + + it('should update the /* camelName */ in the database and return it through an HttpResponseOK object.', async () => { + const ctx = new Context({ + body: { + text: '/* upperFirstCamelName */ 2 (version 2)', + }, + params: { + /* camelName */Id: /* camelName */2.id + } + }); + ctx.user = user2; + const response = await controller.replace/* upperFirstCamelName */(ctx); + + if (!isHttpResponseOK(response)) { + throw new Error('The returned value should be an HttpResponseOK object.'); + } + + const /* camelName */ = await getRepository(/* upperFirstCamelName */).findOne(/* camelName */2.id); + + if (!/* camelName */) { + throw new Error(); + } + + strictEqual(/* camelName */.text, '/* upperFirstCamelName */ 2 (version 2)'); + + strictEqual(response.body.id, /* camelName */.id); + strictEqual(response.body.text, /* camelName */.text); + }); + + it('should not update the other /* camelName */s.', async () => { + const ctx = new Context({ + body: { + text: '/* upperFirstCamelName */ 2 (version 2)', + }, + params: { + /* camelName */Id: /* camelName */2.id + } + }); + ctx.user = user2; + await controller.replace/* upperFirstCamelName */(ctx); + + const /* camelName */ = await getRepository(/* upperFirstCamelName */).findOne(/* camelName */1.id); + + if (!/* camelName */) { + throw new Error(); + } + + notStrictEqual(/* camelName */.text, '/* upperFirstCamelName */ 2 (version 2)'); + }); + + it('should return an HttpResponseNotFound if the object does not exist.', async () => { + const ctx = new Context({ + body: { + text: '', + }, + params: { + /* camelName */Id: -1 + } + }); + ctx.user = user2; + const response = await controller.replace/* upperFirstCamelName */(ctx); + + if (!isHttpResponseNotFound(response)) { + throw new Error('The returned value should be an HttpResponseNotFound object.'); + } + }); + + it('should return an HttpResponseNotFound if the object belongs to another user.', async () => { + const ctx = new Context({ + body: { + text: '', + }, + params: { + /* camelName */Id: /* camelName */0.id + } + }); + ctx.user = user2; + const response = await controller.replace/* upperFirstCamelName */(ctx); + + if (!isHttpResponseNotFound(response)) { + throw new Error('The returned value should be an HttpResponseNotFound object.'); + } + }); + + }); + + describe('has a "delete/* upperFirstCamelName */" method that', () => { + + it('should handle requests at DELETE /:/* camelName */Id.', () => { + strictEqual(getHttpMethod(/* upperFirstCamelName */Controller, 'delete/* upperFirstCamelName */'), 'DELETE'); + strictEqual(getPath(/* upperFirstCamelName */Controller, 'delete/* upperFirstCamelName */'), '/:/* camelName */Id'); + }); + + it('should delete the /* camelName */ and return an HttpResponseNoContent object.', async () => { + const ctx = new Context({ + params: { + /* camelName */Id: /* camelName */2.id + } + }); + ctx.user = user2; + const response = await controller.delete/* upperFirstCamelName */(ctx); + + if (!isHttpResponseNoContent(response)) { + throw new Error('The returned value should be an HttpResponseNoContent object.'); + } + + const /* camelName */ = await getRepository(/* upperFirstCamelName */).findOne(/* camelName */2.id); + + strictEqual(/* camelName */, undefined); + }); + + it('should not delete the other /* camelName */s.', async () => { + const ctx = new Context({ + params: { + /* camelName */Id: /* camelName */2.id + } + }); + ctx.user = user2; + const response = await controller.delete/* upperFirstCamelName */(ctx); + + if (!isHttpResponseNoContent(response)) { + throw new Error('The returned value should be an HttpResponseNoContent object.'); + } + + const /* camelName */ = await getRepository(/* upperFirstCamelName */).findOne(/* camelName */1.id); + + notStrictEqual(/* camelName */, undefined); + }); + + it('should return an HttpResponseNotFound if the /* camelName */ was not found.', async () => { + const ctx = new Context({ + params: { + /* camelName */Id: -1 + } + }); + ctx.user = user2; + const response = await controller.delete/* upperFirstCamelName */(ctx); + + if (!isHttpResponseNotFound(response)) { + throw new Error('The returned value should be an HttpResponseNotFound object.'); + } + }); + + it('should return an HttpResponseNotFound if the /* camelName */ belongs to another user.', async () => { + const ctx = new Context({ + params: { + /* camelName */Id: /* camelName */0.id + } + }); + ctx.user = user2; + const response = await controller.delete/* upperFirstCamelName */(ctx); + + if (!isHttpResponseNotFound(response)) { + throw new Error('The returned value should be an HttpResponseNotFound object.'); + } + }); + + }); + +}); diff --git a/packages/cli/src/generate/templates/rest-api/controller.spec.current-dir.ts b/packages/cli/src/generate/templates/rest-api/controller.spec.current-dir.ts index d84be4f740..c2ac55f4fa 100644 --- a/packages/cli/src/generate/templates/rest-api/controller.spec.current-dir.ts +++ b/packages/cli/src/generate/templates/rest-api/controller.spec.current-dir.ts @@ -351,7 +351,7 @@ describe('/* upperFirstCamelName */Controller', () => { notStrictEqual(/* camelName */, undefined); }); - it('should return an HttpResponseNotFound if the /* camelName */ was not fond.', async () => { + it('should return an HttpResponseNotFound if the /* camelName */ was not found.', async () => { const ctx = new Context({ params: { /* camelName */Id: -1 diff --git a/packages/cli/src/generate/templates/rest-api/controller.spec.ts b/packages/cli/src/generate/templates/rest-api/controller.spec.ts index 3f1708712d..3dfb623764 100644 --- a/packages/cli/src/generate/templates/rest-api/controller.spec.ts +++ b/packages/cli/src/generate/templates/rest-api/controller.spec.ts @@ -351,7 +351,7 @@ describe('/* upperFirstCamelName */Controller', () => { notStrictEqual(/* camelName */, undefined); }); - it('should return an HttpResponseNotFound if the /* camelName */ was not fond.', async () => { + it('should return an HttpResponseNotFound if the /* camelName */ was not found.', async () => { const ctx = new Context({ params: { /* camelName */Id: -1 diff --git a/packages/cli/src/generate/templates/rest-api/entity.auth.ts b/packages/cli/src/generate/templates/rest-api/entity.auth.ts new file mode 100644 index 0000000000..00eb22a700 --- /dev/null +++ b/packages/cli/src/generate/templates/rest-api/entity.auth.ts @@ -0,0 +1,17 @@ +import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'; + +import { User } from './user.entity'; + +@Entity() +export class /* upperFirstCamelName */ { + + @PrimaryGeneratedColumn() + id: number; + + @Column() + text: string; + + @ManyToOne(() => User, { nullable: false }) + owner: User; + +} diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 82bb56605b..9361b15866 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -106,14 +106,15 @@ const generateTypes: GenerateType[] = [ program .command('generate [name]') .description('Generate and/or modify files.') - .option('-r, --register', 'Register the controller into app.controller.ts (only available if type=controller)', false) + .option('-r, --register', 'Register the controller into app.controller.ts (only available if type=controller|rest-api)', false) + .option('-a, --auth', 'Add an owner to the entities of the generated REST API (only available if type=rest-api)', false) .alias('g') .on('--help', () => { console.log(); console.log('Available types:'); generateTypes.forEach(t => console.log(` ${t}`)); }) - .action(async (type: GenerateType, name: string, options: { register: boolean }) => { + .action(async (type: GenerateType, name: string, options: { register: boolean, auth: boolean }) => { if (!name && type !== 'vscode-config') { console.error(); console.error(red(`Argument "name" is required when creating a ${type}. Please provide one.`)); @@ -129,7 +130,7 @@ program createEntity({ name }); break; case 'rest-api': - createRestApi({ name, register: options.register }); + createRestApi({ name, register: options.register, auth: options.auth }); break; case 'hook': createHook({ name }); From c60f9640a41f7fc35321c9cac655b6612d894abd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Thu, 21 May 2020 14:58:20 +0200 Subject: [PATCH 82/92] [CLI] Fix linting --- .../generators/rest-api/create-rest-api.ts | 27 ++++++++++++++----- .../test-foo-bar.controller.spec.auth.ts | 10 +++---- ...oo-bar.controller.spec.current-dir.auth.ts | 10 +++---- .../rest-api/controller.spec.auth.ts | 10 +++---- .../controller.spec.current-dir.auth.ts | 10 +++---- packages/cli/src/index.ts | 12 +++++++-- 6 files changed, 50 insertions(+), 29 deletions(-) diff --git a/packages/cli/src/generate/generators/rest-api/create-rest-api.ts b/packages/cli/src/generate/generators/rest-api/create-rest-api.ts index 845afac582..0b7ac027ad 100644 --- a/packages/cli/src/generate/generators/rest-api/create-rest-api.ts +++ b/packages/cli/src/generate/generators/rest-api/create-rest-api.ts @@ -7,7 +7,7 @@ import { getNames } from '../../utils'; export function createRestApi({ name, register, auth }: { name: string, register: boolean, auth?: boolean }) { auth = auth || false; - + const fs = new FileSystem(); if (fs.projectHasDependency('mongoose')) { @@ -44,14 +44,27 @@ export function createRestApi({ name, register, auth }: { name: string, register .renderOnlyIf(!isCurrentDir && !auth, 'rest-api/controller.ts', `${names.kebabName}.controller.ts`, names) .renderOnlyIf(!isCurrentDir && auth, 'rest-api/controller.auth.ts', `${names.kebabName}.controller.ts`, names) - .renderOnlyIf(isCurrentDir && !auth, 'rest-api/controller.current-dir.ts', `${names.kebabName}.controller.ts`, names) - .renderOnlyIf(isCurrentDir && auth, 'rest-api/controller.current-dir.auth.ts', `${names.kebabName}.controller.ts`, names) + .renderOnlyIf( + isCurrentDir && !auth, 'rest-api/controller.current-dir.ts', `${names.kebabName}.controller.ts`, names + ) + .renderOnlyIf( + isCurrentDir && auth, 'rest-api/controller.current-dir.auth.ts', `${names.kebabName}.controller.ts`, names + ) .renderOnlyIf(!isCurrentDir && !auth, 'rest-api/controller.spec.ts', `${names.kebabName}.controller.spec.ts`, names) - .renderOnlyIf(!isCurrentDir && auth, 'rest-api/controller.spec.auth.ts', `${names.kebabName}.controller.spec.ts`, names) - .renderOnlyIf(isCurrentDir && !auth, 'rest-api/controller.spec.current-dir.ts', `${names.kebabName}.controller.spec.ts`, names) - .renderOnlyIf(isCurrentDir && auth, 'rest-api/controller.spec.current-dir.auth.ts', `${names.kebabName}.controller.spec.ts`, names) - + .renderOnlyIf( + !isCurrentDir && auth, 'rest-api/controller.spec.auth.ts', `${names.kebabName}.controller.spec.ts`, names + ) + .renderOnlyIf( + isCurrentDir && !auth, 'rest-api/controller.spec.current-dir.ts', `${names.kebabName}.controller.spec.ts`, names + ) + .renderOnlyIf( + isCurrentDir && auth, + 'rest-api/controller.spec.current-dir.auth.ts', + `${names.kebabName}.controller.spec.ts`, + names + ) + .ensureFile('index.ts') .addNamedExportIn('index.ts', className, `./${names.kebabName}.controller`); diff --git a/packages/cli/src/generate/specs/rest-api/test-foo-bar.controller.spec.auth.ts b/packages/cli/src/generate/specs/rest-api/test-foo-bar.controller.spec.auth.ts index e984b24978..df3512e375 100644 --- a/packages/cli/src/generate/specs/rest-api/test-foo-bar.controller.spec.auth.ts +++ b/packages/cli/src/generate/specs/rest-api/test-foo-bar.controller.spec.auth.ts @@ -42,16 +42,16 @@ describe('TestFooBarController', () => { [ testFooBar0, testFooBar1, testFooBar2 ] = await testFooBarRepository.save([ { + owner: user1, text: 'TestFooBar 0', - owner: user1 }, { + owner: user2, text: 'TestFooBar 1', - owner: user2 }, { + owner: user2, text: 'TestFooBar 2', - owner: user2 }, ]); }); @@ -83,8 +83,8 @@ describe('TestFooBarController', () => { it('should support pagination', async () => { const testFooBar3 = await getRepository(TestFooBar).save({ + owner: user2, text: 'TestFooBar 3', - owner: user2 }); let ctx = new Context({ @@ -192,8 +192,8 @@ describe('TestFooBarController', () => { } const testFooBar = await getRepository(TestFooBar).findOne({ + relations: [ 'owner' ], where: { text: 'TestFooBar 3' }, - relations: [ 'owner' ] }); if (!testFooBar) { diff --git a/packages/cli/src/generate/specs/rest-api/test-foo-bar.controller.spec.current-dir.auth.ts b/packages/cli/src/generate/specs/rest-api/test-foo-bar.controller.spec.current-dir.auth.ts index d97cbe009f..4f6f07a056 100644 --- a/packages/cli/src/generate/specs/rest-api/test-foo-bar.controller.spec.current-dir.auth.ts +++ b/packages/cli/src/generate/specs/rest-api/test-foo-bar.controller.spec.current-dir.auth.ts @@ -43,16 +43,16 @@ describe('TestFooBarController', () => { [ testFooBar0, testFooBar1, testFooBar2 ] = await testFooBarRepository.save([ { + owner: user1, text: 'TestFooBar 0', - owner: user1 }, { + owner: user2, text: 'TestFooBar 1', - owner: user2 }, { + owner: user2, text: 'TestFooBar 2', - owner: user2 }, ]); }); @@ -84,8 +84,8 @@ describe('TestFooBarController', () => { it('should support pagination', async () => { const testFooBar3 = await getRepository(TestFooBar).save({ + owner: user2, text: 'TestFooBar 3', - owner: user2 }); let ctx = new Context({ @@ -193,8 +193,8 @@ describe('TestFooBarController', () => { } const testFooBar = await getRepository(TestFooBar).findOne({ + relations: [ 'owner' ], where: { text: 'TestFooBar 3' }, - relations: [ 'owner' ] }); if (!testFooBar) { diff --git a/packages/cli/src/generate/templates/rest-api/controller.spec.auth.ts b/packages/cli/src/generate/templates/rest-api/controller.spec.auth.ts index 58fba43aca..30d1035bd3 100644 --- a/packages/cli/src/generate/templates/rest-api/controller.spec.auth.ts +++ b/packages/cli/src/generate/templates/rest-api/controller.spec.auth.ts @@ -42,16 +42,16 @@ describe('/* upperFirstCamelName */Controller', () => { [ /* camelName */0, /* camelName */1, /* camelName */2 ] = await /* camelName */Repository.save([ { + owner: user1, text: '/* upperFirstCamelName */ 0', - owner: user1 }, { + owner: user2, text: '/* upperFirstCamelName */ 1', - owner: user2 }, { + owner: user2, text: '/* upperFirstCamelName */ 2', - owner: user2 }, ]); }); @@ -83,8 +83,8 @@ describe('/* upperFirstCamelName */Controller', () => { it('should support pagination', async () => { const /* camelName */3 = await getRepository(/* upperFirstCamelName */).save({ + owner: user2, text: '/* upperFirstCamelName */ 3', - owner: user2 }); let ctx = new Context({ @@ -192,8 +192,8 @@ describe('/* upperFirstCamelName */Controller', () => { } const /* camelName */ = await getRepository(/* upperFirstCamelName */).findOne({ + relations: [ 'owner' ], where: { text: '/* upperFirstCamelName */ 3' }, - relations: [ 'owner' ] }); if (!/* camelName */) { diff --git a/packages/cli/src/generate/templates/rest-api/controller.spec.current-dir.auth.ts b/packages/cli/src/generate/templates/rest-api/controller.spec.current-dir.auth.ts index c027aeae24..5d7751bd1b 100644 --- a/packages/cli/src/generate/templates/rest-api/controller.spec.current-dir.auth.ts +++ b/packages/cli/src/generate/templates/rest-api/controller.spec.current-dir.auth.ts @@ -43,16 +43,16 @@ describe('/* upperFirstCamelName */Controller', () => { [ /* camelName */0, /* camelName */1, /* camelName */2 ] = await /* camelName */Repository.save([ { + owner: user1, text: '/* upperFirstCamelName */ 0', - owner: user1 }, { + owner: user2, text: '/* upperFirstCamelName */ 1', - owner: user2 }, { + owner: user2, text: '/* upperFirstCamelName */ 2', - owner: user2 }, ]); }); @@ -84,8 +84,8 @@ describe('/* upperFirstCamelName */Controller', () => { it('should support pagination', async () => { const /* camelName */3 = await getRepository(/* upperFirstCamelName */).save({ + owner: user2, text: '/* upperFirstCamelName */ 3', - owner: user2 }); let ctx = new Context({ @@ -193,8 +193,8 @@ describe('/* upperFirstCamelName */Controller', () => { } const /* camelName */ = await getRepository(/* upperFirstCamelName */).findOne({ + relations: [ 'owner' ], where: { text: '/* upperFirstCamelName */ 3' }, - relations: [ 'owner' ] }); if (!/* camelName */) { diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 9361b15866..441cdacdcf 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -106,8 +106,16 @@ const generateTypes: GenerateType[] = [ program .command('generate [name]') .description('Generate and/or modify files.') - .option('-r, --register', 'Register the controller into app.controller.ts (only available if type=controller|rest-api)', false) - .option('-a, --auth', 'Add an owner to the entities of the generated REST API (only available if type=rest-api)', false) + .option( + '-r, --register', + 'Register the controller into app.controller.ts (only available if type=controller|rest-api)', + false + ) + .option( + '-a, --auth', + 'Add an owner to the entities of the generated REST API (only available if type=rest-api)', + false + ) .alias('g') .on('--help', () => { console.log(); From 7dbf2775102b332cb4650c0429e6556a688e835d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Fri, 22 May 2020 09:16:48 +0200 Subject: [PATCH 83/92] [Docs] Add --auth to "foal g rest-api" --- docs/api-section/rest-blueprints.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/docs/api-section/rest-blueprints.md b/docs/api-section/rest-blueprints.md index 361bfb008d..75433a454c 100644 --- a/docs/api-section/rest-blueprints.md +++ b/docs/api-section/rest-blueprints.md @@ -5,10 +5,10 @@ foal generate rest-api product --register ``` -Building a REST API is often a common task when creating an application. To avoid reinventing the wheel, FoalTS provides an integrated command to achieve this. +Building a REST API is often a common task when creating an application. To avoid reinventing the wheel, FoalTS provides a CLI command to achieve this. ``` -foal generate rest-api [--register] +foal generate rest-api [--register] [--auth] ``` This command generates three files: an entity, a controller and the controller's test. Depending on your directory structure, they may be generated in different locations: @@ -116,7 +116,18 @@ export const productSchhema = { }; ``` -## Generate OpenAPI documentation +## Using Authentication + +If you wish to attach a user to the resource, you can use the `--auth` flag to do so. + +*Example:* +``` +foal generate rest-api product --auth +``` + +This flags adds an `owner: User` column to your entity and uses it in the API. + +## Generating OpenAPI documentation The generated controllers also have OpenAPI decorators on their methods to document the API. From b12df47a337da9cf676984fbaeec1e08f61bc368 Mon Sep 17 00:00:00 2001 From: kingdun3284 Date: Sat, 23 May 2020 00:46:09 +0800 Subject: [PATCH 84/92] Update contexts.ts --- packages/core/src/core/http/contexts.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/core/http/contexts.ts b/packages/core/src/core/http/contexts.ts index 9d220b790b..306f38fd83 100644 --- a/packages/core/src/core/http/contexts.ts +++ b/packages/core/src/core/http/contexts.ts @@ -12,8 +12,8 @@ import { Session } from '../../sessions'; * @class Context * @template User */ -export class Context { - state: { [key: string]: any } = {}; +export class Context { + state: ContextState = {} as ContextState; user: User; session: ContextSession; request: Request; From f0e6fb2d0499ef8388f4d8ecfa7cf4502f938c8c Mon Sep 17 00:00:00 2001 From: "Dotan J. Nahum" Date: Fri, 22 May 2020 21:18:43 +0300 Subject: [PATCH 85/92] fix broken docs link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 54c618beb8..777996b0a6 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@

Website - - Documentation + Documentation - Twitter - From 428f97f35964d169db88c2155f7c26cbc1a5f03d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Sat, 23 May 2020 07:11:15 +0200 Subject: [PATCH 86/92] Fix other broken docs links --- README.md | 2 +- packages/aws-s3/README.md | 2 +- packages/cli/README.md | 2 +- packages/core/README.md | 2 +- packages/csrf/README.md | 2 +- packages/ejs/README.md | 2 +- packages/formidable/README.md | 2 +- packages/graphql/README.md | 2 +- packages/jwks-rsa/README.md | 2 +- packages/jwt/README.md | 2 +- packages/mongodb/README.md | 2 +- packages/mongoose/README.md | 2 +- packages/password/README.md | 2 +- packages/redis/README.md | 2 +- packages/social/README.md | 2 +- packages/storage/README.md | 2 +- packages/swagger/README.md | 2 +- packages/typeorm/README.md | 2 +- packages/typestack/README.md | 2 +- 19 files changed, 19 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 777996b0a6..1aa5595f5b 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ $ npm run develop The development server is started! Go to `http://localhost:3001` and find our welcoming page! -:point_right: [Continue with the tutorial](https://foalts.gitbook.io/docs/content/) :seedling: +:point_right: [Continue with the tutorial](https://foalts.gitbook.io/docs/) :seedling: ![Screenshot](./docs/screenshot.png) diff --git a/packages/aws-s3/README.md b/packages/aws-s3/README.md index f84d0b4f32..5a8954f1fc 100644 --- a/packages/aws-s3/README.md +++ b/packages/aws-s3/README.md @@ -55,7 +55,7 @@ $ npm run develop The development server is started! Go to `http://localhost:3001` and find our welcoming page! -[=> Continue with the tutorial](https://foalts.gitbook.io/docs/content/) +[=> Continue with the tutorial](https://foalts.gitbook.io/docs/) ## Why? diff --git a/packages/cli/README.md b/packages/cli/README.md index 04f9499d8c..45a72e6364 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -55,7 +55,7 @@ $ npm run develop The development server is started! Go to `http://localhost:3001` and find our welcoming page! -[=> Continue with the tutorial](https://foalts.gitbook.io/docs/content/) +[=> Continue with the tutorial](https://foalts.gitbook.io/docs/) ## Why? diff --git a/packages/core/README.md b/packages/core/README.md index a3ff755804..1ea3fbd523 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -58,7 +58,7 @@ $ npm run develop The development server is started! Go to `http://localhost:3001` and find our welcoming page! -[=> Continue with the tutorial](https://foalts.gitbook.io/docs/content/) +[=> Continue with the tutorial](https://foalts.gitbook.io/docs/) ## Why? diff --git a/packages/csrf/README.md b/packages/csrf/README.md index ad37f816fb..3e4feb80ed 100644 --- a/packages/csrf/README.md +++ b/packages/csrf/README.md @@ -55,7 +55,7 @@ $ npm run develop The development server is started! Go to `http://localhost:3001` and find our welcoming page! -[=> Continue with the tutorial](https://foalts.gitbook.io/docs/content/) +[=> Continue with the tutorial](https://foalts.gitbook.io/docs/) ## Why? diff --git a/packages/ejs/README.md b/packages/ejs/README.md index 15a5776968..8cec98e5b5 100644 --- a/packages/ejs/README.md +++ b/packages/ejs/README.md @@ -55,7 +55,7 @@ $ npm run develop The development server is started! Go to `http://localhost:3001` and find our welcoming page! -[=> Continue with the tutorial](https://foalts.gitbook.io/docs/content/) +[=> Continue with the tutorial](https://foalts.gitbook.io/docs/) ## Why? diff --git a/packages/formidable/README.md b/packages/formidable/README.md index 14f13a7c1d..39d425d85b 100644 --- a/packages/formidable/README.md +++ b/packages/formidable/README.md @@ -55,7 +55,7 @@ $ npm run develop The development server is started! Go to `http://localhost:3001` and find our welcoming page! -[=> Continue with the tutorial](https://foalts.gitbook.io/docs/content/) +[=> Continue with the tutorial](https://foalts.gitbook.io/docs/) ## Why? diff --git a/packages/graphql/README.md b/packages/graphql/README.md index 3bafee5186..0fe6a907b6 100644 --- a/packages/graphql/README.md +++ b/packages/graphql/README.md @@ -55,7 +55,7 @@ $ npm run develop The development server is started! Go to `http://localhost:3001` and find our welcoming page! -[=> Continue with the tutorial](https://foalts.gitbook.io/docs/content/) +[=> Continue with the tutorial](https://foalts.gitbook.io/docs/) ## Why? diff --git a/packages/jwks-rsa/README.md b/packages/jwks-rsa/README.md index 581cd58d2f..91fa481d92 100644 --- a/packages/jwks-rsa/README.md +++ b/packages/jwks-rsa/README.md @@ -55,7 +55,7 @@ $ npm run develop The development server is started! Go to `http://localhost:3001` and find our welcoming page! -[=> Continue with the tutorial](https://foalts.gitbook.io/docs/content/) +[=> Continue with the tutorial](https://foalts.gitbook.io/docs/) ## Why? diff --git a/packages/jwt/README.md b/packages/jwt/README.md index 3ca564c2aa..77fa4d5642 100644 --- a/packages/jwt/README.md +++ b/packages/jwt/README.md @@ -55,7 +55,7 @@ $ npm run develop The development server is started! Go to `http://localhost:3001` and find our welcoming page! -[=> Continue with the tutorial](https://foalts.gitbook.io/docs/content/) +[=> Continue with the tutorial](https://foalts.gitbook.io/docs/) ## Why? diff --git a/packages/mongodb/README.md b/packages/mongodb/README.md index 677456be07..92baaf8180 100644 --- a/packages/mongodb/README.md +++ b/packages/mongodb/README.md @@ -55,7 +55,7 @@ $ npm run develop The development server is started! Go to `http://localhost:3001` and find our welcoming page! -[=> Continue with the tutorial](https://foalts.gitbook.io/docs/content/) +[=> Continue with the tutorial](https://foalts.gitbook.io/docs/) ## Why? diff --git a/packages/mongoose/README.md b/packages/mongoose/README.md index ad3d5d6360..957d689fc6 100644 --- a/packages/mongoose/README.md +++ b/packages/mongoose/README.md @@ -55,7 +55,7 @@ $ npm run develop The development server is started! Go to `http://localhost:3001` and find our welcoming page! -[=> Continue with the tutorial](https://foalts.gitbook.io/docs/content/) +[=> Continue with the tutorial](https://foalts.gitbook.io/docs/) ## Why? diff --git a/packages/password/README.md b/packages/password/README.md index 520ce2b008..d14689b272 100644 --- a/packages/password/README.md +++ b/packages/password/README.md @@ -55,7 +55,7 @@ $ npm run develop The development server is started! Go to `http://localhost:3001` and find our welcoming page! -[=> Continue with the tutorial](https://foalts.gitbook.io/docs/content/) +[=> Continue with the tutorial](https://foalts.gitbook.io/docs/) ## Why? diff --git a/packages/redis/README.md b/packages/redis/README.md index 62c2fa21ff..cbbf3b42b8 100644 --- a/packages/redis/README.md +++ b/packages/redis/README.md @@ -55,7 +55,7 @@ $ npm run develop The development server is started! Go to `http://localhost:3001` and find our welcoming page! -[=> Continue with the tutorial](https://foalts.gitbook.io/docs/content/) +[=> Continue with the tutorial](https://foalts.gitbook.io/docs/) ## Why? diff --git a/packages/social/README.md b/packages/social/README.md index 8a16f5aaa3..df616bdaa6 100644 --- a/packages/social/README.md +++ b/packages/social/README.md @@ -55,7 +55,7 @@ $ npm run develop The development server is started! Go to `http://localhost:3001` and find our welcoming page! -[=> Continue with the tutorial](https://foalts.gitbook.io/docs/content/) +[=> Continue with the tutorial](https://foalts.gitbook.io/docs/) ## Why? diff --git a/packages/storage/README.md b/packages/storage/README.md index bdfb932e27..17d33326ad 100644 --- a/packages/storage/README.md +++ b/packages/storage/README.md @@ -55,7 +55,7 @@ $ npm run develop The development server is started! Go to `http://localhost:3001` and find our welcoming page! -[=> Continue with the tutorial](https://foalts.gitbook.io/docs/content/) +[=> Continue with the tutorial](https://foalts.gitbook.io/docs/) ## Why? diff --git a/packages/swagger/README.md b/packages/swagger/README.md index aff5813dfb..d46376f8ee 100644 --- a/packages/swagger/README.md +++ b/packages/swagger/README.md @@ -55,7 +55,7 @@ $ npm run develop The development server is started! Go to `http://localhost:3001` and find our welcoming page! -[=> Continue with the tutorial](https://foalts.gitbook.io/docs/content/) +[=> Continue with the tutorial](https://foalts.gitbook.io/docs/) ## Why? diff --git a/packages/typeorm/README.md b/packages/typeorm/README.md index 76a1c848d3..a161f11cf0 100644 --- a/packages/typeorm/README.md +++ b/packages/typeorm/README.md @@ -55,7 +55,7 @@ $ npm run develop The development server is started! Go to `http://localhost:3001` and find our welcoming page! -[=> Continue with the tutorial](https://foalts.gitbook.io/docs/content/) +[=> Continue with the tutorial](https://foalts.gitbook.io/docs/) ## Why? diff --git a/packages/typestack/README.md b/packages/typestack/README.md index b5fde83a92..6ca83a1566 100644 --- a/packages/typestack/README.md +++ b/packages/typestack/README.md @@ -52,7 +52,7 @@ $ npm run develop The development server is started! Go to `http://localhost:3001` and find our welcoming page! -[=> Continue with the tutorial](https://foalts.gitbook.io/docs/content/) +[=> Continue with the tutorial](https://foalts.gitbook.io/docs/) ## Why? From 92cf1ab4fb2a1ac7bbf0c0467c3c0847ad5b0a74 Mon Sep 17 00:00:00 2001 From: Matt Harvey Date: Sat, 23 May 2020 17:18:15 +1000 Subject: [PATCH 87/92] Fix linter errors in tutorial shell scripts fixes #720 --- docs/tutorials/multi-user-todo-list/3-the-shell-scripts.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorials/multi-user-todo-list/3-the-shell-scripts.md b/docs/tutorials/multi-user-todo-list/3-the-shell-scripts.md index ac32a00951..d5035a4183 100644 --- a/docs/tutorials/multi-user-todo-list/3-the-shell-scripts.md +++ b/docs/tutorials/multi-user-todo-list/3-the-shell-scripts.md @@ -26,7 +26,7 @@ export const schema = { type: 'object', }; -export async function main(args: { email: string, password: string }) { +export async function main(args: { email: string; password: string }) { const connection = await createConnection(); try { const user = new User(); @@ -100,7 +100,7 @@ export const schema = { type: 'object', }; -export async function main(args: { owner: string, text: string }) { +export async function main(args: { owner: string; text: string }) { const connection = await createConnection(); try { const user = await connection.getRepository(User).findOne({ email: args.owner }); From 10ede8dc8c949406fba9f805cf00f4fab64ea7db Mon Sep 17 00:00:00 2001 From: Amin Taghikhani Date: Mon, 25 May 2020 10:34:28 +0430 Subject: [PATCH 88/92] fix typo --- packages/core/src/core/http/http-responses.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/core/src/core/http/http-responses.ts b/packages/core/src/core/http/http-responses.ts index 13afc11066..b839551ae8 100644 --- a/packages/core/src/core/http/http-responses.ts +++ b/packages/core/src/core/http/http-responses.ts @@ -25,7 +25,7 @@ export interface CookieOptions { } /** - * Reprensent an HTTP response. This class must be extended. + * Represent an HTTP response. This class must be extended. * Instances of HttpResponse are returned in hooks and controller * methods. * @@ -233,7 +233,7 @@ export function isHttpResponseSuccess(obj: any): obj is HttpResponseSuccess { */ export class HttpResponseOK extends HttpResponseSuccess { /** - * Property used internally by isHttpResponOK. + * Property used internally by isHttpResponseOK. * * @memberof HttpResponseOK */ @@ -309,7 +309,7 @@ export async function createHttpResponseFile(options: .setHeader('Content-Length', stats.size.toString()) .setHeader( 'Content-Disposition', - (options.forceDownload ? 'attachement' : 'inline') + (options.forceDownload ? 'attachment' : 'inline') + `; filename="${options.filename || file}"` ); @@ -417,7 +417,7 @@ export function isHttpResponseNoContent(obj: any): obj is HttpResponseNoContent */ export abstract class HttpResponseRedirection extends HttpResponse { /** - * Property used internally by isHttpResponseRediction. + * Property used internally by isHttpResponseRedirection. * * @memberof HttpResponseRedirection */ From 554a3953bc1f6a216fd8496a15c79236cec81824 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Mon, 25 May 2020 08:30:24 +0200 Subject: [PATCH 89/92] [Download] attachement -> attachment --- packages/acceptance-tests/src/upload-and-download.spec.ts | 2 +- .../acceptance-tests/src/upload-and-download.typeorm.spec.ts | 2 +- packages/core/src/core/http/http-responses.spec.ts | 4 ++-- packages/storage/src/abstract-disk.service.spec.ts | 4 ++-- packages/storage/src/abstract-disk.service.ts | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/acceptance-tests/src/upload-and-download.spec.ts b/packages/acceptance-tests/src/upload-and-download.spec.ts index e4d7f438c2..16affc2570 100644 --- a/packages/acceptance-tests/src/upload-and-download.spec.ts +++ b/packages/acceptance-tests/src/upload-and-download.spec.ts @@ -77,7 +77,7 @@ describe('Upload & Download Files', () => { .expect(200) .expect('Content-Type', 'image/png') .expect('Content-Length', image.length.toString()) - .expect('Content-Disposition', 'attachement; filename="download.png"') + .expect('Content-Disposition', 'attachment; filename="download.png"') .expect(image); }); diff --git a/packages/acceptance-tests/src/upload-and-download.typeorm.spec.ts b/packages/acceptance-tests/src/upload-and-download.typeorm.spec.ts index 7b5b483bc8..1c44ad08b3 100644 --- a/packages/acceptance-tests/src/upload-and-download.typeorm.spec.ts +++ b/packages/acceptance-tests/src/upload-and-download.typeorm.spec.ts @@ -134,7 +134,7 @@ describe('Upload & Download Files (TypoORM)', () => { .expect(200) .expect('Content-Type', 'image/png') .expect('Content-Length', image.length.toString()) - .expect('Content-Disposition', 'attachement; filename="download.png"') + .expect('Content-Disposition', 'attachment; filename="download.png"') .expect(image); }); diff --git a/packages/core/src/core/http/http-responses.spec.ts b/packages/core/src/core/http/http-responses.spec.ts index a9c67c0da5..ca3cf85ead 100644 --- a/packages/core/src/core/http/http-responses.spec.ts +++ b/packages/core/src/core/http/http-responses.spec.ts @@ -302,14 +302,14 @@ describe('createHttpResponseFile', () => { ...pngFileOptions, forceDownload: true }); - strictEqual(httpResponse.getHeader('Content-Disposition'), 'attachement; filename="test-file.png"'); + strictEqual(httpResponse.getHeader('Content-Disposition'), 'attachment; filename="test-file.png"'); httpResponse = await createHttpResponseFile({ ...pngFileOptions, filename: 'download.png', forceDownload: true, }); - strictEqual(httpResponse.getHeader('Content-Disposition'), 'attachement; filename="download.png"'); + strictEqual(httpResponse.getHeader('Content-Disposition'), 'attachment; filename="download.png"'); }); it('should sanitize the "file" option to only keep the base name.', async () => { diff --git a/packages/storage/src/abstract-disk.service.spec.ts b/packages/storage/src/abstract-disk.service.spec.ts index e1abde31e1..75231a6702 100644 --- a/packages/storage/src/abstract-disk.service.spec.ts +++ b/packages/storage/src/abstract-disk.service.spec.ts @@ -132,13 +132,13 @@ describe('AbstractDisk', () => { response = await disk.createHttpResponse(path, { forceDownload: true }); - strictEqual(response.getHeader('Content-Disposition'), 'attachement; filename="test-file.png"'); + strictEqual(response.getHeader('Content-Disposition'), 'attachment; filename="test-file.png"'); response = await disk.createHttpResponse(path, { filename: 'download.png', forceDownload: true, }); - strictEqual(response.getHeader('Content-Disposition'), 'attachement; filename="download.png"'); + strictEqual(response.getHeader('Content-Disposition'), 'attachment; filename="download.png"'); }); }); diff --git a/packages/storage/src/abstract-disk.service.ts b/packages/storage/src/abstract-disk.service.ts index ad27f2486a..e8d4a534f6 100644 --- a/packages/storage/src/abstract-disk.service.ts +++ b/packages/storage/src/abstract-disk.service.ts @@ -134,7 +134,7 @@ export abstract class AbstractDisk { .setHeader('Content-Length', size.toString()) .setHeader( 'Content-Disposition', - (options.forceDownload ? 'attachement' : 'inline') + (options.forceDownload ? 'attachment' : 'inline') + `; filename="${options.filename || basename(path)}"` ); } From 4f288e61bb81e889761e30d36e93993296694486 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Mon, 25 May 2020 08:52:50 +0200 Subject: [PATCH 90/92] [JWT] Remove Auth0 et Cognito tests --- .github/workflows/test.yml | 3 - .../src/authentication/jwt.jwks.spec.ts | 106 ------------------ 2 files changed, 109 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d6ca60b536..3c992d7d57 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,9 +16,6 @@ jobs: node-version: [8, 10] env: - AUTH0_DOMAIN: ${{ secrets.AUTH0_DOMAIN }} - AUTH0_AUDIENCE: ${{ secrets.AUTH0_AUDIENCE }} - AUTH0_TOKEN: ${{ secrets.AUTH0_TOKEN }} SETTINGS_AWS_ACCESS_KEY_ID: ${{ secrets.SETTINGS_AWS_ACCESS_KEY_ID }} SETTINGS_AWS_SECRET_ACCESS_KEY: ${{ secrets.SETTINGS_AWS_SECRET_ACCESS_KEY }} NODE_VERSION: ${{ matrix.node-version }} diff --git a/packages/acceptance-tests/src/authentication/jwt.jwks.spec.ts b/packages/acceptance-tests/src/authentication/jwt.jwks.spec.ts index d6ced48784..977da75c5d 100644 --- a/packages/acceptance-tests/src/authentication/jwt.jwks.spec.ts +++ b/packages/acceptance-tests/src/authentication/jwt.jwks.spec.ts @@ -82,110 +82,4 @@ describe('[Authentication|JWT|JWKS] Users can be authenticated with a JWKS retre } }); - it('from Auth0.', () => { - const domain = Config.get2('auth0.domain', 'string'); - const audience = Config.get2('auth0.audience', 'string'); - const token = Config.get2('auth0.token', 'string'); - - if (token === undefined) { - console.warn('AUTH0_TOKEN not defined. Skipping this test...'); - return; - } - - class AppController { - - @Get('/api/users/me') - @JWTRequired({ - secretOrPublicKey: getRSAPublicKeyFromJWKS({ - cache: true, - jwksRequestsPerMinute: 5, - jwksUri: `https://${domain}/.well-known/jwks.json`, - rateLimit: true, - }) - }, { - algorithms: [ 'RS256' ], - audience, - issuer: `https://${domain}/`, - }) - getUser() { - return new HttpResponseOK({ - name: 'Alix' - }); - } - - } - - const app = createApp(AppController); - - return request(app) - .get('/api/users/me') - .set('Authorization', 'Bearer ' + token) - .expect(200) - .then(response => { - deepStrictEqual(response.body, { - name: 'Alix' - }); - }); - }); - - it('from AWS Cognito.', async () => { - const clientId = Config.get2('cognito.clientId', 'string'); - const domain = Config.get2('cognito.domain', 'string'); - const refreshToken = Config.get2('cognito.refreshToken', 'string'); - let token: string; - const region = Config.get2('cognito.region', 'string'); - const userPoolId = Config.get2('cognito.userPoolId', 'string'); - - if (refreshToken === undefined) { - console.warn('COGNITO_REFRESH_TOKEN not defined. Skipping this test...'); - return; - } - - try { - const { body } = await superagent - .post(`https://${domain}.auth.${region}.amazoncognito.com/oauth2/token`) - .send('grant_type=refresh_token') - .send(`client_id=${clientId}`) - .send(`refresh_token=${refreshToken}`); - token = body.id_token; - } catch (error) { - throw new Error('Requesting a new access token failed.'); - } - - class AppController { - - @Get('/api/users/me') - @JWTRequired({ - secretOrPublicKey: getRSAPublicKeyFromJWKS({ - cache: true, - jwksRequestsPerMinute: 5, - jwksUri: `https://cognito-idp.${region}.amazonaws.com/${userPoolId}/.well-known/jwks.json`, - rateLimit: true, - }) - }, { - algorithms: [ 'RS256' ], - audience: clientId, - issuer: `https://cognito-idp.${region}.amazonaws.com/${userPoolId}`, - }) - getUser() { - return new HttpResponseOK({ - name: 'Alix' - }); - } - - } - - const app = createApp(AppController); - - return request(app) - .get('/api/users/me') - .set('Authorization', 'Bearer ' + token) - .expect(200) - .then(response => { - deepStrictEqual(response.body, { - name: 'Alix' - }); - }); - }); - }); From 87fb9d315240302bad96a91b626633f67d1357bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Mon, 25 May 2020 12:07:55 +0200 Subject: [PATCH 91/92] Fix linting --- packages/acceptance-tests/src/authentication/jwt.jwks.spec.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/acceptance-tests/src/authentication/jwt.jwks.spec.ts b/packages/acceptance-tests/src/authentication/jwt.jwks.spec.ts index 977da75c5d..44a1015f20 100644 --- a/packages/acceptance-tests/src/authentication/jwt.jwks.spec.ts +++ b/packages/acceptance-tests/src/authentication/jwt.jwks.spec.ts @@ -7,10 +7,9 @@ import { join } from 'path'; // 3p import { sign } from 'jsonwebtoken'; import * as superagent from 'superagent'; -import * as request from 'supertest'; // FoalTS -import { Config, createApp, Get, HttpResponseOK } from '@foal/core'; +import { createApp, Get, HttpResponseOK } from '@foal/core'; import { getRSAPublicKeyFromJWKS } from '@foal/jwks-rsa'; import { JWTRequired } from '@foal/jwt'; From c0e648721ef9e8cd5a3f645d06ea660605f44ddb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Thu, 28 May 2020 08:47:21 +0200 Subject: [PATCH 92/92] v1.9.0 --- lerna.json | 2 +- packages/acceptance-tests/package-lock.json | 2 +- packages/acceptance-tests/package.json | 22 ++++++++++----------- packages/aws-s3/package-lock.json | 2 +- packages/aws-s3/package.json | 6 +++--- packages/cli/package-lock.json | 2 +- packages/cli/package.json | 2 +- packages/core/package-lock.json | 2 +- packages/core/package.json | 4 ++-- packages/csrf/package-lock.json | 2 +- packages/csrf/package.json | 4 ++-- packages/ejs/package-lock.json | 2 +- packages/ejs/package.json | 2 +- packages/examples/package-lock.json | 2 +- packages/examples/package.json | 16 +++++++-------- packages/formidable/package-lock.json | 2 +- packages/formidable/package.json | 4 ++-- packages/graphql/package-lock.json | 2 +- packages/graphql/package.json | 4 ++-- packages/internal-test/package-lock.json | 2 +- packages/internal-test/package.json | 2 +- packages/jwks-rsa/package-lock.json | 2 +- packages/jwks-rsa/package.json | 6 +++--- packages/jwt/package-lock.json | 2 +- packages/jwt/package.json | 4 ++-- packages/mongodb/package-lock.json | 2 +- packages/mongodb/package.json | 4 ++-- packages/mongoose/package-lock.json | 2 +- packages/mongoose/package.json | 2 +- packages/password/package-lock.json | 2 +- packages/password/package.json | 2 +- packages/redis/package-lock.json | 2 +- packages/redis/package.json | 4 ++-- packages/social/package-lock.json | 2 +- packages/social/package.json | 4 ++-- packages/storage/package-lock.json | 2 +- packages/storage/package.json | 6 +++--- packages/swagger/package-lock.json | 2 +- packages/swagger/package.json | 4 ++-- packages/typeorm/package-lock.json | 2 +- packages/typeorm/package.json | 6 +++--- packages/typestack/package-lock.json | 2 +- packages/typestack/package.json | 4 ++-- 43 files changed, 78 insertions(+), 78 deletions(-) diff --git a/lerna.json b/lerna.json index 19b3475be8..9c9da19d33 100644 --- a/lerna.json +++ b/lerna.json @@ -3,5 +3,5 @@ "packages": [ "packages/*" ], - "version": "1.8.1" + "version": "1.9.0" } diff --git a/packages/acceptance-tests/package-lock.json b/packages/acceptance-tests/package-lock.json index 28991bb44e..1dbf6c7250 100644 --- a/packages/acceptance-tests/package-lock.json +++ b/packages/acceptance-tests/package-lock.json @@ -1,6 +1,6 @@ { "name": "@foal/acceptance-tests", - "version": "1.8.1", + "version": "1.9.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/acceptance-tests/package.json b/packages/acceptance-tests/package.json index fa208ae842..cf88b99e40 100644 --- a/packages/acceptance-tests/package.json +++ b/packages/acceptance-tests/package.json @@ -1,7 +1,7 @@ { "name": "@foal/acceptance-tests", "private": true, - "version": "1.8.1", + "version": "1.9.0", "description": "Acceptance tests of the framework", "scripts": { "test": "mocha --require ts-node/register \"./src/**/*.spec.{ts,tsx}\"", @@ -11,16 +11,16 @@ "url": "https://github.com/sponsors/LoicPoullain" }, "dependencies": { - "@foal/core": "^1.8.1", - "@foal/csrf": "^1.8.1", - "@foal/formidable": "^1.8.1", - "@foal/jwks-rsa": "^1.8.1", - "@foal/jwt": "^1.8.1", - "@foal/mongodb": "^1.8.1", - "@foal/mongoose": "^1.8.1", - "@foal/redis": "^1.8.1", - "@foal/typeorm": "^1.8.1", - "@foal/typestack": "^1.8.1", + "@foal/core": "^1.9.0", + "@foal/csrf": "^1.9.0", + "@foal/formidable": "^1.9.0", + "@foal/jwks-rsa": "^1.9.0", + "@foal/jwt": "^1.9.0", + "@foal/mongodb": "^1.9.0", + "@foal/mongoose": "^1.9.0", + "@foal/redis": "^1.9.0", + "@foal/typeorm": "^1.9.0", + "@foal/typestack": "^1.9.0", "@types/express": "~4.17.2", "@types/express-rate-limit": "~3.3.3", "@types/formidable": "~1.0.31", diff --git a/packages/aws-s3/package-lock.json b/packages/aws-s3/package-lock.json index ab6d38946f..ea628a301a 100644 --- a/packages/aws-s3/package-lock.json +++ b/packages/aws-s3/package-lock.json @@ -1,6 +1,6 @@ { "name": "@foal/aws-s3", - "version": "1.8.1", + "version": "1.9.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/aws-s3/package.json b/packages/aws-s3/package.json index ed7b6b0df0..9ae8ee3ec4 100644 --- a/packages/aws-s3/package.json +++ b/packages/aws-s3/package.json @@ -1,6 +1,6 @@ { "name": "@foal/aws-s3", - "version": "1.8.1", + "version": "1.9.0", "description": "AWS S3 storage components for FoalTS", "main": "./lib/index.js", "types": "./lib/index.d.ts", @@ -44,8 +44,8 @@ "lib/" ], "dependencies": { - "@foal/core": "^1.8.1", - "@foal/storage": "^1.8.1", + "@foal/core": "^1.9.0", + "@foal/storage": "^1.9.0", "aws-sdk": "~2.640.0" }, "devDependencies": { diff --git a/packages/cli/package-lock.json b/packages/cli/package-lock.json index 86fbca8700..026a943a8b 100644 --- a/packages/cli/package-lock.json +++ b/packages/cli/package-lock.json @@ -1,6 +1,6 @@ { "name": "@foal/cli", - "version": "1.8.1", + "version": "1.9.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/cli/package.json b/packages/cli/package.json index d702baa1e2..52b97e0b53 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@foal/cli", - "version": "1.8.1", + "version": "1.9.0", "description": "CLI tool for FoalTS", "main": "./lib/index.js", "types": "./lib/index.d.ts", diff --git a/packages/core/package-lock.json b/packages/core/package-lock.json index 80d2f8f5b6..942a5ecfd7 100644 --- a/packages/core/package-lock.json +++ b/packages/core/package-lock.json @@ -1,6 +1,6 @@ { "name": "@foal/core", - "version": "1.8.1", + "version": "1.9.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/core/package.json b/packages/core/package.json index e055ba550f..6a61fe9805 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@foal/core", - "version": "1.8.1", + "version": "1.9.0", "description": "A Node.js and TypeScript framework, all-inclusive.", "main": "./lib/index.js", "types": "./lib/index.d.ts", @@ -88,7 +88,7 @@ "reflect-metadata": "~0.1.13" }, "devDependencies": { - "@foal/ejs": "^1.8.1", + "@foal/ejs": "^1.9.0", "@types/mocha": "~2.2.43", "@types/node": "~10.1.2", "@types/supertest": "~2.0.5", diff --git a/packages/csrf/package-lock.json b/packages/csrf/package-lock.json index fb7b5ab8cb..16777a0cdd 100644 --- a/packages/csrf/package-lock.json +++ b/packages/csrf/package-lock.json @@ -1,6 +1,6 @@ { "name": "@foal/csrf", - "version": "1.8.1", + "version": "1.9.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/csrf/package.json b/packages/csrf/package.json index e2cbffe397..0a3d5224c9 100644 --- a/packages/csrf/package.json +++ b/packages/csrf/package.json @@ -1,6 +1,6 @@ { "name": "@foal/csrf", - "version": "1.8.1", + "version": "1.9.0", "description": "CSRF protection for FoalTS", "main": "./lib/index.js", "types": "./lib/index.d.ts", @@ -53,6 +53,6 @@ "typescript": "~3.5.3" }, "dependencies": { - "@foal/core": "^1.8.1" + "@foal/core": "^1.9.0" } } diff --git a/packages/ejs/package-lock.json b/packages/ejs/package-lock.json index 4dbc915daa..9b7d592cf3 100644 --- a/packages/ejs/package-lock.json +++ b/packages/ejs/package-lock.json @@ -1,6 +1,6 @@ { "name": "@foal/ejs", - "version": "1.8.1", + "version": "1.9.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/ejs/package.json b/packages/ejs/package.json index a0209c6171..5d3b8dd239 100644 --- a/packages/ejs/package.json +++ b/packages/ejs/package.json @@ -1,6 +1,6 @@ { "name": "@foal/ejs", - "version": "1.8.1", + "version": "1.9.0", "description": "EJS template package for FoalTS", "main": "./lib/index.js", "types": "./lib/index.d.ts", diff --git a/packages/examples/package-lock.json b/packages/examples/package-lock.json index 32a380f36b..883a38bbc6 100644 --- a/packages/examples/package-lock.json +++ b/packages/examples/package-lock.json @@ -1,6 +1,6 @@ { "name": "@foal/examples", - "version": "1.8.1", + "version": "1.9.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/examples/package.json b/packages/examples/package.json index c44142f463..b45dade55f 100644 --- a/packages/examples/package.json +++ b/packages/examples/package.json @@ -1,6 +1,6 @@ { "name": "@foal/examples", - "version": "1.8.1", + "version": "1.9.0", "description": "FoalTs examples", "scripts": { "build": "tsc && copy-cli \"src/**/*.html\" build", @@ -43,19 +43,19 @@ }, "license": "MIT", "dependencies": { - "@foal/aws-s3": "^1.8.1", - "@foal/core": "^1.8.1", - "@foal/social": "^1.8.1", - "@foal/storage": "^1.8.1", - "@foal/swagger": "^1.8.1", - "@foal/typeorm": "^1.8.1", + "@foal/aws-s3": "^1.9.0", + "@foal/core": "^1.9.0", + "@foal/social": "^1.9.0", + "@foal/storage": "^1.9.0", + "@foal/swagger": "^1.9.0", + "@foal/typeorm": "^1.9.0", "source-map-support": "~0.5.16", "sqlite3": "~4.1.0", "typeorm": "~0.2.20", "yamljs": "~0.3.0" }, "devDependencies": { - "@foal/cli": "^1.8.1", + "@foal/cli": "^1.9.0", "@types/mocha": "~2.2.43", "@types/node": "~10.1.1", "concurrently": "~3.5.1", diff --git a/packages/formidable/package-lock.json b/packages/formidable/package-lock.json index ce2693683b..32d8947f8d 100644 --- a/packages/formidable/package-lock.json +++ b/packages/formidable/package-lock.json @@ -1,6 +1,6 @@ { "name": "@foal/formidable", - "version": "1.8.1", + "version": "1.9.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/formidable/package.json b/packages/formidable/package.json index f9c7ad58ae..89dd4b909d 100644 --- a/packages/formidable/package.json +++ b/packages/formidable/package.json @@ -1,6 +1,6 @@ { "name": "@foal/formidable", - "version": "1.8.1", + "version": "1.9.0", "description": "Small package to use formidable with promises", "main": "./lib/index.js", "types": "./lib/index.d.ts", @@ -43,7 +43,7 @@ "lib/" ], "dependencies": { - "@foal/core": "^1.8.1" + "@foal/core": "^1.9.0" }, "devDependencies": { "@types/mocha": "~2.2.43", diff --git a/packages/graphql/package-lock.json b/packages/graphql/package-lock.json index 2cdf41a0a4..ea3b32fb06 100644 --- a/packages/graphql/package-lock.json +++ b/packages/graphql/package-lock.json @@ -1,6 +1,6 @@ { "name": "@foal/graphql", - "version": "1.8.1", + "version": "1.9.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/graphql/package.json b/packages/graphql/package.json index b32f78de86..befe860cee 100644 --- a/packages/graphql/package.json +++ b/packages/graphql/package.json @@ -1,6 +1,6 @@ { "name": "@foal/graphql", - "version": "1.8.1", + "version": "1.9.0", "description": "GraphQL integration for FoalTS", "main": "./lib/index.js", "types": "./lib/index.d.ts", @@ -41,7 +41,7 @@ "lib/" ], "dependencies": { - "@foal/core": "^1.8.1", + "@foal/core": "^1.9.0", "ajv": "~6.12.0", "glob": "~7.1.4" }, diff --git a/packages/internal-test/package-lock.json b/packages/internal-test/package-lock.json index 6c3596a84a..444d11746d 100644 --- a/packages/internal-test/package-lock.json +++ b/packages/internal-test/package-lock.json @@ -1,6 +1,6 @@ { "name": "@foal/internal-test", - "version": "1.8.1", + "version": "1.9.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/internal-test/package.json b/packages/internal-test/package.json index 57447848e5..6015cbf713 100644 --- a/packages/internal-test/package.json +++ b/packages/internal-test/package.json @@ -1,7 +1,7 @@ { "name": "@foal/internal-test", "private": true, - "version": "1.8.1", + "version": "1.9.0", "description": "Unpublished package used to run some tests.", "main": "./lib/index.js", "types": "./lib/index.d.ts", diff --git a/packages/jwks-rsa/package-lock.json b/packages/jwks-rsa/package-lock.json index 1f037fb821..134cd9541e 100644 --- a/packages/jwks-rsa/package-lock.json +++ b/packages/jwks-rsa/package-lock.json @@ -1,6 +1,6 @@ { "name": "@foal/jwks-rsa", - "version": "1.8.1", + "version": "1.9.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/jwks-rsa/package.json b/packages/jwks-rsa/package.json index 2893772bdc..02d1e164cc 100644 --- a/packages/jwks-rsa/package.json +++ b/packages/jwks-rsa/package.json @@ -1,6 +1,6 @@ { "name": "@foal/jwks-rsa", - "version": "1.8.1", + "version": "1.9.0", "description": "Integration of the library jwks-rsa with FoalTS", "main": "./lib/index.js", "types": "./lib/index.d.ts", @@ -51,8 +51,8 @@ "@foal/jwt": "^1.2.0" }, "devDependencies": { - "@foal/core": "^1.8.1", - "@foal/jwt": "^1.8.1", + "@foal/core": "^1.9.0", + "@foal/jwt": "^1.9.0", "@types/mocha": "~2.2.43", "@types/node": "~10.5.6", "mocha": "~5.2.0", diff --git a/packages/jwt/package-lock.json b/packages/jwt/package-lock.json index 84983b6aea..8dbc4ba5e4 100644 --- a/packages/jwt/package-lock.json +++ b/packages/jwt/package-lock.json @@ -1,6 +1,6 @@ { "name": "@foal/jwt", - "version": "1.8.1", + "version": "1.9.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/jwt/package.json b/packages/jwt/package.json index 5663cb1356..4f321348db 100644 --- a/packages/jwt/package.json +++ b/packages/jwt/package.json @@ -1,6 +1,6 @@ { "name": "@foal/jwt", - "version": "1.8.1", + "version": "1.9.0", "description": "Authentication with JWT for FoalTS", "main": "./lib/index.js", "types": "./lib/index.d.ts", @@ -43,7 +43,7 @@ "lib/" ], "dependencies": { - "@foal/core": "^1.8.1", + "@foal/core": "^1.9.0", "@types/jsonwebtoken": "~8.3.0", "jsonwebtoken": "~8.5.0" }, diff --git a/packages/mongodb/package-lock.json b/packages/mongodb/package-lock.json index f3a5528830..4d95003424 100644 --- a/packages/mongodb/package-lock.json +++ b/packages/mongodb/package-lock.json @@ -1,6 +1,6 @@ { "name": "@foal/mongodb", - "version": "1.8.1", + "version": "1.9.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/mongodb/package.json b/packages/mongodb/package.json index c3e0f33d3c..e1ec8ef85b 100644 --- a/packages/mongodb/package.json +++ b/packages/mongodb/package.json @@ -1,6 +1,6 @@ { "name": "@foal/mongodb", - "version": "1.8.1", + "version": "1.9.0", "description": "MongoDB package for FoalTS session", "main": "./lib/index.js", "types": "./lib/index.d.ts", @@ -46,7 +46,7 @@ "lib/" ], "dependencies": { - "@foal/core": "^1.8.1", + "@foal/core": "^1.9.0", "mongodb": "~3.5.0" }, "devDependencies": { diff --git a/packages/mongoose/package-lock.json b/packages/mongoose/package-lock.json index f1b5f7a1ab..82196b0b40 100644 --- a/packages/mongoose/package-lock.json +++ b/packages/mongoose/package-lock.json @@ -1,6 +1,6 @@ { "name": "@foal/mongoose", - "version": "1.8.1", + "version": "1.9.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/mongoose/package.json b/packages/mongoose/package.json index fa6ae4144b..a9b3ebc548 100644 --- a/packages/mongoose/package.json +++ b/packages/mongoose/package.json @@ -1,6 +1,6 @@ { "name": "@foal/mongoose", - "version": "1.8.1", + "version": "1.9.0", "description": "FoalTS integration of Mongoose", "main": "./lib/index.js", "types": "./lib/index.d.ts", diff --git a/packages/password/package-lock.json b/packages/password/package-lock.json index c834f6b048..80fa699a48 100644 --- a/packages/password/package-lock.json +++ b/packages/password/package-lock.json @@ -1,6 +1,6 @@ { "name": "@foal/password", - "version": "1.8.1", + "version": "1.9.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/password/package.json b/packages/password/package.json index 85620046bc..af65d74e8f 100644 --- a/packages/password/package.json +++ b/packages/password/package.json @@ -1,6 +1,6 @@ { "name": "@foal/password", - "version": "1.8.1", + "version": "1.9.0", "description": "Password utilities for FoalTS", "main": "./lib/index.js", "types": "./lib/index.d.ts", diff --git a/packages/redis/package-lock.json b/packages/redis/package-lock.json index 53760e2d7b..29cecce30d 100644 --- a/packages/redis/package-lock.json +++ b/packages/redis/package-lock.json @@ -1,6 +1,6 @@ { "name": "@foal/redis", - "version": "1.8.1", + "version": "1.9.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/redis/package.json b/packages/redis/package.json index 01f065ca35..49381239d6 100644 --- a/packages/redis/package.json +++ b/packages/redis/package.json @@ -1,6 +1,6 @@ { "name": "@foal/redis", - "version": "1.8.1", + "version": "1.9.0", "description": "Redis sessions for FoalTS", "main": "./lib/index.js", "types": "./lib/index.d.ts", @@ -42,7 +42,7 @@ "lib/" ], "dependencies": { - "@foal/core": "^1.8.1", + "@foal/core": "^1.9.0", "redis": "~3.0.2" }, "devDependencies": { diff --git a/packages/social/package-lock.json b/packages/social/package-lock.json index 8be1578ecc..b8c3c18f96 100644 --- a/packages/social/package-lock.json +++ b/packages/social/package-lock.json @@ -1,6 +1,6 @@ { "name": "@foal/social", - "version": "1.8.1", + "version": "1.9.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/social/package.json b/packages/social/package.json index 1e6bcd1add..d54cfe9ef0 100644 --- a/packages/social/package.json +++ b/packages/social/package.json @@ -1,6 +1,6 @@ { "name": "@foal/social", - "version": "1.8.1", + "version": "1.9.0", "description": "Social authentication for FoalTS", "main": "./lib/index.js", "types": "./lib/index.d.ts", @@ -53,7 +53,7 @@ "lib/" ], "dependencies": { - "@foal/core": "^1.8.1", + "@foal/core": "^1.9.0", "node-fetch": "~2.6.0" }, "devDependencies": { diff --git a/packages/storage/package-lock.json b/packages/storage/package-lock.json index 194d908430..a0de06e843 100644 --- a/packages/storage/package-lock.json +++ b/packages/storage/package-lock.json @@ -1,6 +1,6 @@ { "name": "@foal/storage", - "version": "1.8.1", + "version": "1.9.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/storage/package.json b/packages/storage/package.json index fa05b9630b..4faf423627 100644 --- a/packages/storage/package.json +++ b/packages/storage/package.json @@ -1,6 +1,6 @@ { "name": "@foal/storage", - "version": "1.8.1", + "version": "1.9.0", "description": "Storage components for FoalTS", "main": "./lib/index.js", "types": "./lib/index.d.ts", @@ -45,14 +45,14 @@ "lib/" ], "dependencies": { - "@foal/core": "^1.8.1", + "@foal/core": "^1.9.0", "@types/busboy": "~0.2.3", "busboy": "~0.3.1", "mime": "~2.4.4", "pump": "~3.0.0" }, "devDependencies": { - "@foal/internal-test": "^1.8.1", + "@foal/internal-test": "^1.9.0", "@types/mocha": "~2.2.43", "@types/node": "~10.5.6", "@types/supertest": "~2.0.8", diff --git a/packages/swagger/package-lock.json b/packages/swagger/package-lock.json index 688be2af27..277fd8f901 100644 --- a/packages/swagger/package-lock.json +++ b/packages/swagger/package-lock.json @@ -1,6 +1,6 @@ { "name": "@foal/swagger", - "version": "1.8.1", + "version": "1.9.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/swagger/package.json b/packages/swagger/package.json index 40b6700e56..c93a37f9aa 100644 --- a/packages/swagger/package.json +++ b/packages/swagger/package.json @@ -1,6 +1,6 @@ { "name": "@foal/swagger", - "version": "1.8.1", + "version": "1.9.0", "description": "Swagger UI for FoalTS", "main": "./lib/index.js", "types": "./lib/index.d.ts", @@ -44,7 +44,7 @@ "lib/" ], "dependencies": { - "@foal/core": "^1.8.1", + "@foal/core": "^1.9.0", "swagger-ui-dist": "~3.25.0" }, "devDependencies": { diff --git a/packages/typeorm/package-lock.json b/packages/typeorm/package-lock.json index abf90a2031..0eb57ec96d 100644 --- a/packages/typeorm/package-lock.json +++ b/packages/typeorm/package-lock.json @@ -1,6 +1,6 @@ { "name": "@foal/typeorm", - "version": "1.8.1", + "version": "1.9.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/typeorm/package.json b/packages/typeorm/package.json index d3b268b954..58aeb352c2 100644 --- a/packages/typeorm/package.json +++ b/packages/typeorm/package.json @@ -1,6 +1,6 @@ { "name": "@foal/typeorm", - "version": "1.8.1", + "version": "1.9.0", "description": "FoalTS integration of TypeORM", "main": "./lib/index.js", "types": "./lib/index.d.ts", @@ -49,7 +49,7 @@ "lib/" ], "dependencies": { - "@foal/core": "^1.8.1" + "@foal/core": "^1.9.0" }, "peerDependencies": { "typeorm": "^0.2.6" @@ -58,9 +58,9 @@ "@types/mocha": "~2.2.43", "@types/node": "~10.5.6", "mocha": "~5.2.0", + "mongodb": "~3.5.0", "mysql": "~2.16.0", "pg": "~7.7.1", - "mongodb": "~3.5.0", "rimraf": "~2.6.2", "sqlite3": "~4.0.4", "ts-node": "~3.3.0", diff --git a/packages/typestack/package-lock.json b/packages/typestack/package-lock.json index 6a3e8a07f1..31c6c0b027 100644 --- a/packages/typestack/package-lock.json +++ b/packages/typestack/package-lock.json @@ -1,6 +1,6 @@ { "name": "@foal/typestack", - "version": "1.8.1", + "version": "1.9.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/typestack/package.json b/packages/typestack/package.json index 124724f6c2..fcf777b474 100644 --- a/packages/typestack/package.json +++ b/packages/typestack/package.json @@ -1,6 +1,6 @@ { "name": "@foal/typestack", - "version": "1.8.1", + "version": "1.9.0", "description": "FoalTS for validation and serialization using TypeStack classes", "main": "./lib/index.js", "types": "./lib/index.d.ts", @@ -49,7 +49,7 @@ "class-validator": "^0.10.0" }, "dependencies": { - "@foal/core": "^1.8.1" + "@foal/core": "^1.9.0" }, "devDependencies": { "@types/mocha": "~2.2.43",