diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index dfa7fa6c..00000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,13 +0,0 @@ -version: 2 -updates: - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "monthly" - open-pull-requests-limit: 10 - - - package-ecosystem: "npm" - directory: "/" - schedule: - interval: "weekly" - open-pull-requests-limit: 10 diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index d51ce639..00000000 --- a/.github/stale.yml +++ /dev/null @@ -1,21 +0,0 @@ -# Number of days of inactivity before an issue becomes stale -daysUntilStale: 15 -# Number of days of inactivity before a stale issue is closed -daysUntilClose: 7 -# Issues with these labels will never be considered stale -exemptLabels: - - "discussion" - - "feature request" - - "bug" - - "help wanted" - - "plugin suggestion" - - "good first issue" -# Label to use when marking an issue as stale -staleLabel: stale -# Comment to post when marking an issue as stale. Set to `false` to disable -markComment: > - This issue has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs. Thank you - for your contributions. -# Comment to post when closing a stale issue. Set to `false` to disable -closeComment: false diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 0dd8ed37..00000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: CI - -on: - push: - paths-ignore: - - 'docs/**' - - '*.md' - pull_request: - paths-ignore: - - 'docs/**' - - '*.md' - -jobs: - test: - uses: fastify/workflows/.github/workflows/plugins-ci.yml@04a2d6ae69784b9dd665456e01b45e3b1b839a53 - with: - lint: true - auto-merge-exclude: 'help-me' diff --git a/.github/workflows/onPushToMain.yml b/.github/workflows/onPushToMain.yml new file mode 100644 index 00000000..b6faa520 --- /dev/null +++ b/.github/workflows/onPushToMain.yml @@ -0,0 +1,56 @@ +# test +name: version, tag and github release + +on: + push: + branches: [main] + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + - name: Check if version already exists + id: version-check + run: | + package_version=$(node -p "require('./package.json').version") + exists=$(gh api repos/${{ github.repository }}/releases/tags/v$package_version >/dev/null 2>&1 && echo "true" || echo "") + + if [ -n "$exists" ]; + then + echo "Version v$package_version already exists" + echo "::warning file=package.json,line=1::Version v$package_version already exists - no release will be created. If you want to create a new release, please update the version in package.json and push again." + echo "skipped=true" >> $GITHUB_OUTPUT + else + echo "Version v$package_version does not exist. Creating release..." + echo "skipped=false" >> $GITHUB_OUTPUT + echo "tag=v$package_version" >> $GITHUB_OUTPUT + fi + env: + GH_TOKEN: ${{ secrets.GH_TOKEN }} + - name: Setup git + if: ${{ steps.version-check.outputs.skipped == 'false' }} + run: | + git config --global user.email ${{ secrets.GH_EMAIL }} + git config --global user.name ${{ secrets.GH_USERNAME }} + - name: Generate oclif README + if: ${{ steps.version-check.outputs.skipped == 'false' }} + id: oclif-readme + run: | + npm install + npm exec oclif readme + if [ -n "$(git status --porcelain)" ]; then + git add . + git commit -am "chore: update README.md" + git push -u origin ${{ github.ref_name }} + fi + - name: Create Github Release + uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 + if: ${{ steps.version-check.outputs.skipped == 'false' }} + with: + name: ${{ steps.version-check.outputs.tag }} + tag: ${{ steps.version-check.outputs.tag }} + commit: ${{ github.ref_name }} + token: ${{ secrets.GH_TOKEN }} + skipIfReleaseExists: true diff --git a/.github/workflows/onRelease.yml b/.github/workflows/onRelease.yml new file mode 100644 index 00000000..16277a26 --- /dev/null +++ b/.github/workflows/onRelease.yml @@ -0,0 +1,18 @@ +name: publish + +on: + release: + types: [released] + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: latest + - run: npm install + - uses: JS-DevTools/npm-publish@19c28f1ef146469e409470805ea4279d47c3d35c + with: + token: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..1d156cf6 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,23 @@ +name: tests +on: + push: + branches-ignore: [main] + workflow_dispatch: + +jobs: + unit-tests: + strategy: + matrix: + os: ['ubuntu-latest', 'windows-latest'] + node_version: [lts/-1, lts/*, latest] + fail-fast: false + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node_version }} + cache: npm + - run: npm install + - run: npm run build + - run: npm run test diff --git a/.gitignore b/.gitignore index 0e3c9522..e1ed0c11 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ + # Logs logs *.log @@ -159,4 +160,8 @@ test/workdir* test/fixtures/*.js .vscode/launch.json .vscode/settings.json -templates/app-ts/dist + +/.idea +/dist +/tmp +oclif.manifest.json diff --git a/.taprc b/.taprc deleted file mode 100644 index 85101415..00000000 --- a/.taprc +++ /dev/null @@ -1,6 +0,0 @@ -ts: true -jsx: false -jobs: 1 -reporter: terse -check-coverage: false -timeout: 60 diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..b4ac0dac --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,22 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "attach", + "name": "Attach", + "port": 9229, + "skipFiles": ["/**"] + }, + { + "type": "node", + "request": "launch", + "name": "Execute Command", + "skipFiles": ["/**"], + "runtimeExecutable": "node", + "runtimeArgs": ["--loader", "ts-node/esm", "--no-warnings=ExperimentalWarning"], + "program": "${workspaceFolder}/bin/dev.js", + "args": ["hello", "world"] + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..000bd6a3 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "editor.formatOnSave": true, + "[javascript]": { + "editor.defaultFormatter": "dbaeumer.vscode-eslint" + }, + "[typescript]": { + "editor.defaultFormatter": "dbaeumer.vscode-eslint" + } +} diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 085f5264..00000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2016-2017 Tomas Della Vedova - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md deleted file mode 100644 index 288bd673..00000000 --- a/README.md +++ /dev/null @@ -1,408 +0,0 @@ -# fastify-cli - -![CI](https://github.com/fastify/fastify-cli/workflows/CI/badge.svg) -[![NPM version](https://img.shields.io/npm/v/fastify-cli.svg?style=flat)](https://www.npmjs.com/package/fastify-cli) -[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://standardjs.com/) - -Command line tools for [Fastify](https://github.com/fastify/fastify). -Generate, write, and run an application with one single command! - -## Install -```bash -npm install fastify-cli --global -``` - -## Usage - -`fastify-cli` offers a single command line interface for your Fastify -project: - -```bash -$ fastify -``` - -Will print an help: - -``` -Fastify command line interface, available commands are: - - * start start a server - * eject turns your application into a standalone executable with a server.(js|ts) file being added - * generate generate a new project - * generate-plugin generate a new plugin project - * generate-swagger generate Swagger/OpenAPI schema for a project using @fastify/swagger - * readme generate a README.md for the plugin - * print-routes prints the representation of the internal radix tree used by the router, useful for debugging. - * print-plugins prints the representation of the internal plugin tree used by avvio, useful for debugging. - * version the current fastify-cli version - * help help about commands - -Launch 'fastify help [command]' to know more about the commands. - -The default command is start, you can hit - - fastify start plugin.js - -to start plugin.js. -``` - -### start - -You can start any Fastify plugin with: - -```bash -$ fastify start plugin.js -``` - -A plugin can be as simple as: - -```js -// plugin.js -module.exports = function (fastify, options, next) { - fastify.get('/', function (req, reply) { - reply.send({ hello: 'world' }) - }) - next() -} -``` - -If you are using Node 8+, you can use `Promises` or `async` functions too: - -```js -// async-await-plugin.js -module.exports = async function (fastify, options) { - fastify.get('/', async function (req, reply) { - return { hello: 'world' } - }) -} -``` - -For a list of available flags for `fastify start` see the help: `fastify help start`. - -If you want to use custom options for the server creation, just export an options object with your route and run the cli command with the `--options` flag. -These options also get passed to your plugin via the `options` argument. - -```js -// plugin.js -module.exports = function (fastify, options, next) { - fastify.get('/', function (req, reply) { - reply.send({ hello: 'world' }) - }) - next() -} - -module.exports.options = { - https: { - key: 'key', - cert: 'cert' - } -} -``` - -And if you are using EcmaScript Module format: - -```javascript -export default async function plugin (fastify, options) { - // Both `/foo` and `/foo/` are registered - fastify.get('/foo/', async function (req, reply) { - return 'foo' - }) -} - -export const options = { - ignoreTrailingSlash: true -} -``` - -If you want to use custom options for your plugin, just add them after the `--` terminator. If used in conjunction with the `--options` argument, the CLI -arguments take precedence. - -```js -// plugin.js -module.exports = function (fastify, options, next) { - if (option.one) { - //... - } - //... - next() -} -``` - -```bash -$ fastify start plugin.js -- --one -``` - -Modules in EcmaScript Module format can be used on Node.js >= 14 or >= 12.17.0 but < 13.0.0' -```js -// plugin.js -export default async function plugin (fastify, options) { - fastify.get('/', async function (req, reply) { - return options - }) -} -``` - -This works with a `.js` extension if you are using Node.js >= 14 and the nearest parent `package.json` has `"type": "module"` -([more info here](https://nodejs.medium.com/announcing-core-node-js-support-for-ecmascript-modules-c5d6dc29b663)). -If your `package.json` does not have `"type": "module"`, use `.mjs` for the extension (`plugin.mjs` in the above example). - -#### Options -You can pass the following options via CLI arguments. You can also use `--config` or `-c` flag to pass a configuration file that exports all the properties listed below in camelCase convention. In case of collision (i.e., An argument existing in both the configuration file and as a command-line argument, the command-line argument is given the priority). Every option has a corresponding environment variable: - -| Description | Short command | Full command | Environment variable | -| --------------------------------------------------------------------------------------------------------------------------------------- | ------------- | ------------------ | ------------------------ | -| Path to configuration file that can be used to manage the options listed below | `-c` | `--config` | `FASTIFY_CONFIG or CONFIG` | -| Port to listen on (default to 3000) | `-p` | `--port` | `FASTIFY_PORT or PORT` | -| Address to listen on | `-a` | `--address` | `FASTIFY_ADDRESS` | -| Socket to listen on | `-s` | `--socket` | `FASTIFY_SOCKET` | -| Module to preload | `-r` | `--require` | `FASTIFY_REQUIRE` | -| ES Module to preload | `-i` | `--import` | `FASTIFY_IMPORT` | -| Log level (default to fatal) | `-l` | `--log-level` | `FASTIFY_LOG_LEVEL` | -| Path to logging configuration module to use | `-L` | `--logging-module` | `FASTIFY_LOGGING_MODULE` | -| Start Fastify app in debug mode with nodejs inspector | `-d` | `--debug` | `FASTIFY_DEBUG` | -| Set the inspector port (default: 9320) | `-I` | `--debug-port` | `FASTIFY_DEBUG_PORT` | -| Set the inspector host to listen on (default: loopback address or `0.0.0.0` inside Docker or Kubernetes) | | `--debug-host` | `FASTIFY_DEBUG_HOST` | -| Prints pretty logs | `-P` | `--pretty-logs` | `FASTIFY_PRETTY_LOGS` | -| Watch process.cwd() directory for changes, recursively; when that happens, the process will auto reload | `-w` | `--watch` | `FASTIFY_WATCH` | -| Ignore changes to the specified files or directories when watch is enabled. (e.g. `--ignore-watch='node_modules .git logs/error.log'` ) | | `--ignore-watch` | `FASTIFY_IGNORE_WATCH` | -| Prints events triggered by watch listener (useful to debug unexpected reload when using `--watch` ) | `-V` | `--verbose-watch` | `FASTIFY_VERBOSE_WATCH` | -| Use custom options | `-o` | `--options` | `FASTIFY_OPTIONS` | -| Set the prefix | `-x` | `--prefix` | `FASTIFY_PREFIX` | -| Set the plugin timeout | `-T` | `--plugin-timeout` | `FASTIFY_PLUGIN_TIMEOUT` | -| Defines the maximum payload, in bytes,
that the server is allowed to accept | | `--body-limit` | `FASTIFY_BODY_LIMIT` | -| Set the maximum ms delay before forcefully closing pending requests after receiving SIGTERM or SIGINT signals; and uncaughtException or unhandledRejection errors (default: 500) | `-g` | `--close-grace-delay` | `FASTIFY_CLOSE_GRACE_DELAY` | - -By default `fastify-cli` runs [`dotenv`](https://www.npmjs.com/package/dotenv), so it will load all the env variables stored in `.env` in your current working directory. - -The default value for `--plugin-timeout` is 10 seconds. -By default `--ignore-watch` flag is set to ignore `node_modules build dist .git bower_components logs .swp' files. - -#### Containerization - -When deploying to a Docker container, and potentially other, containers, it is advisable to set a fastify address of `0.0.0.0` because these containers do not default to exposing mapped ports to localhost. - -For containers built and run specifically by the Docker Daemon or inside a Kubernetes cluster, fastify-cli is able to detect that the server process is running within a container and the `0.0.0.0` listen address is set automatically. - -Other containerization tools (eg. Buildah and Podman) are not detected automatically, so the `0.0.0.0` listen address must be set explicitly with either the `--address` flag or the `FASTIFY_ADDRESS` environment variable. - -#### Fastify version discovery - -If Fastify is installed as a project dependency (with `npm install --save fastify`), -then `fastify-cli` will use that version of Fastify when running the server. -Otherwise, `fastify-cli` will use the version of Fastify included within `fastify-cli`. - -#### Migrating out of fastify-cli start - -If you would like to turn your application into a standalone executable, -just add the following `server.js`: - -```js -'use strict' - -// Read the .env file. -require('dotenv').config() - -// Require the framework -const Fastify = require('fastify') - -// Require library to exit fastify process, gracefully (if possible) -const closeWithGrace = require('close-with-grace') - -// Instantiate Fastify with some config -const app = Fastify({ - logger: true -}) - -// Register your application as a normal plugin. -const appService = require('./app.js') -app.register(appService) - -// delay is the number of milliseconds for the graceful close to finish -closeWithGrace({ delay: process.env.FASTIFY_CLOSE_GRACE_DELAY || 500 }, async function ({ signal, err, manual }) { - if (err) { - app.log.error(err) - } - await app.close() -}) - -// Start listening. -app.listen({ port: process.env.PORT || 3000 }, (err) => { - if (err) { - app.log.error(err) - process.exit(1) - } -}) -``` - -### generate - -`fastify-cli` can also help with generating some project scaffolding to -kickstart the development of your next Fastify application. To use it: - -1. `fastify generate ` -2. `cd yourapp` -3. `npm install` - -The sample code offers you the following npm tasks: - -* `npm start` - starts the application -* `npm run dev` - starts the application with - [`pino-pretty`](https://github.com/pinojs/pino-pretty) pretty logging - (not suitable for production) -* `npm test` - runs the tests -* `npm run lint` - fixes files accordingly to linter rules, for templates generated with `--standardlint` - -You will find three different folders: -- `plugins`: the folder where you will place all your custom plugins -- `routes`: the folder where you will declare all your endpoints -- `test`: the folder where you will declare all your test - -Finally, there will be an `app.js` file, which is your entry point. -It is a standard Fastify plugin and you will not need to add the `listen` method to run the server, just run it with one of the scripts above. - -If the target directory exists `fastify generate` will fail unless the target directory is `.`, as in the current directory. - -If the target directory is the current directory (`.`) and it already contains a `package.json` file, `fastify generate` will fail. This can -be overidden with the `--integrate` flag: - -`fastify generate . --integrate` - -This will add or alter the `main`, `scripts`, `dependencies` and `devDependencies` fields on the `package.json`. In cases of file name collisions -for any files being added, the file will be overwritten with the new file added by `fastify generate`. If there is an existing `app.js` in this scenario, -it will be overwritten. Use the `--integrate` flag with care. - -#### Options - -| Description | Full command | -| --- | --- | -| To generate ESM based JavaScript template | `--esm` | -| Use the TypeScript template | `--lang=ts`, `--lang=typescript` | -| Overwrite it when the target directory is the current directory (`.`) | `--integrate`| -| For JavaScript template, optionally includes Standard linter to fix code style issues | `--standardlint`| - -### generate-plugin - -`fastify-cli` can help you improve your plugin development by generating a scaffolding project: - -1. `fastify generate-plugin ` -2. `cd yourplugin` -3. `npm install` - -The boilerplate provides some useful npm scripts: -* `npm run unit`: runs all unit tests -* `npm run lint`: to check your project's code style -* `npm run test:typescript`: runs types tests -* `npm test`: runs all the checks at once - -### readme - -`fastify-cli` can also help with generating a concise and informative readme for your plugin. If no `package.json` is provided a new one is generated automatically. -To use it: - -1. `cd yourplugin` -2. `fastify readme ` - -Finally, there will be a new `README.md` file, which provides internal information about your plugin e.g: - -* Install instructions -* Example usage -* Plugin dependencies -* Exposed decorators -* Encapsulation semantics -* Compatible Fastify version - -### generate-swagger - -if your project uses `@fastify/swagger`, `fastify-cli` can generate and write out the resulting Swagger/OpenAPI schema for you. - -`fastify generate-swagger app.js` - -### linting - -`fastify-cli` is unopinionated on the choice of linter. We recommend you to add a linter, like so: - -```diff -"devDependencies": { -+ "standard": "^11.0.1", -} - -"scripts": { -+ "pretest": "standard", - "test": "tap test/**/*.test.js", - "start": "fastify start -l info app.js", - "dev": "fastify start -l info -P app.js", -+ "lint": "standard --fix" -}, -``` - -## Test helpers - -When you use `fastify-cli` to run your project you need a way to load your application because you can run the CLI command. -To do so, you can use the this module to load your application and give you the control to write your assertions. -These utilities are async functions that you may use with the [`node-tap`](https://www.npmjs.com/package/tap) testing framework. - -There are two utilities provided: - -- `build`: builds your application and returns the `fastify` instance without calling the `listen` method. -- `listen`: starts your application and returns the `fastify` instance listening on the configured port. - -Both of these utilities have the `function(args, pluginOptions, serverOptions)` parameters: - -- `args`: is a string or a string array within the same arguments passed to the `fastify-cli` command. -- `pluginOptions`: is an object containing the options provided to the started plugin (eg: `app.js`). -- `serverOptions`: is an object containing the additional options provided to fastify server, similar to the `--options` command line argument - -```js -// load the utility helper functions -const { build, listen } = require('fastify-cli/helper') - -// write a test -const { test } = require('tap') -test('test my application', async t => { - const argv = ['app.js'] - const app = await build(argv, { - extraParam: 'foo' - }) - t.teardown(() => app.close()) - - // test your application here: - const res = await app.inject('/') - t.same(res.json(), { hello: 'one' }) -}) -``` - -Log output is consumed by tap. If log messages should be logged to the console -the logger needs to be configured to output to stderr instead of stdout. - -```js -const logger = { - transport: { - target: 'pino-pretty', - options: { - destination: 2, - }, - }, -} -const argv = ['app.js'] -test('test my application with logging enabled', async t => { - const app = await build(argv, {}, { logger }) - t.teardown(() => app.close()) - - // test your application here: - const res = await app.inject('/') - t.same(res.json(), { hello: 'one' }) -}) -``` - - - - -## Contributing -If you feel you can help in any way, be it with examples, extra testing, or new features please open a pull request or open an issue. - -### How to execute the CLI -Instead of using the `fastify` keyword before each command, use `node cli.js` -
Example: replace `fastify start` with `node cli.js start` - -## License -**[MIT](https://github.com/fastify/fastify-cli/blob/master/LICENSE)** diff --git a/args.js b/args.js deleted file mode 100644 index 1f842bc2..00000000 --- a/args.js +++ /dev/null @@ -1,98 +0,0 @@ -'use strict' - -const argv = require('yargs-parser') -const dotenv = require('dotenv') -const { requireModule } = require('./util') - -const DEFAULT_IGNORE = 'node_modules build dist .git bower_components logs .swp .nyc_output' - -const DEFAULT_ARGUMENTS = { - logLevel: 'fatal', - prettyLogs: false, - watch: false, - verboseWatch: false, - debug: false, - debugPort: 9320, - options: false, - pluginTimeout: 10 * 1000, // everything should load in 10 seconds - closeGraceDelay: 500, - lang: 'js', - standardlint: false, - commonPrefix: false -} - -module.exports = function parseArgs (args) { - dotenv.config() - const commandLineArguments = argv(args, { - configuration: { - 'populate--': true - }, - number: ['port', 'inspect-port', 'body-limit', 'plugin-timeout', 'close-grace-delay'], - string: ['log-level', 'address', 'socket', 'prefix', 'ignore-watch', 'logging-module', 'debug-host', 'lang', 'require', 'import', 'config', 'method'], - boolean: ['pretty-logs', 'options', 'watch', 'verbose-watch', 'debug', 'standardlint', 'common-prefix', 'include-hooks'], - envPrefix: 'FASTIFY_', - alias: { - port: ['p'], - socket: ['s'], - help: ['h'], - config: ['c'], - options: ['o'], - address: ['a'], - watch: ['w'], - prefix: ['x'], - require: ['r'], - import: ['i'], - debug: ['d'], - 'debug-port': ['I'], - 'log-level': ['l'], - 'pretty-logs': ['P'], - 'plugin-timeout': ['T'], - 'close-grace-delay': ['g'], - 'logging-module': ['L'], - 'verbose-watch': ['V'] - } - }) - - const configFileOptions = commandLineArguments.config ? requireModule(commandLineArguments.config) : undefined - - const additionalArgs = commandLineArguments['--'] || [] - const { _, ...pluginOptions } = argv(additionalArgs) - const ignoreWatchArg = commandLineArguments.ignoreWatch || configFileOptions?.ignoreWatch || '' - - let ignoreWatch = `${DEFAULT_IGNORE} ${ignoreWatchArg}`.trim() - if (ignoreWatchArg.includes('.ts$')) { - ignoreWatch = ignoreWatch.replace('dist', '') - } - - // Merge objects from lower to higher priority - const parsedArgs = { ...DEFAULT_ARGUMENTS, ...configFileOptions, ...commandLineArguments } - - return { - _: parsedArgs._, - '--': additionalArgs, - port: parsedArgs.port, - bodyLimit: parsedArgs.bodyLimit, - pluginTimeout: parsedArgs.pluginTimeout, - closeGraceDelay: parsedArgs.closeGraceDelay, - pluginOptions, - prettyLogs: parsedArgs.prettyLogs, - options: parsedArgs.options, - watch: parsedArgs.watch, - debug: parsedArgs.debug, - debugPort: parsedArgs.debugPort, - debugHost: parsedArgs.debugHost, - ignoreWatch, - verboseWatch: parsedArgs.verboseWatch, - logLevel: parsedArgs.logLevel, - address: parsedArgs.address, - socket: parsedArgs.socket, - require: parsedArgs.require, - import: parsedArgs.import, - prefix: parsedArgs.prefix, - loggingModule: parsedArgs.loggingModule, - lang: parsedArgs.lang, - method: parsedArgs.method, - commonPrefix: parsedArgs.commonPrefix, - includeHooks: parsedArgs.includeHooks - } -} diff --git a/bin/dev.cmd b/bin/dev.cmd new file mode 100644 index 00000000..cec553be --- /dev/null +++ b/bin/dev.cmd @@ -0,0 +1,3 @@ +@echo off + +node --loader ts-node/esm --no-warnings=ExperimentalWarning "%~dp0\dev" %* diff --git a/bin/dev.js b/bin/dev.js new file mode 100644 index 00000000..f1024166 --- /dev/null +++ b/bin/dev.js @@ -0,0 +1,5 @@ +#!/usr/bin/env -S node --loader ts-node/esm --no-warnings=ExperimentalWarning + +import { execute } from '@oclif/core' + +await execute({ development: true, dir: import.meta.url }) diff --git a/bin/run.cmd b/bin/run.cmd new file mode 100644 index 00000000..968fc307 --- /dev/null +++ b/bin/run.cmd @@ -0,0 +1,3 @@ +@echo off + +node "%~dp0\run" %* diff --git a/bin/run.js b/bin/run.js new file mode 100644 index 00000000..92b78ec6 --- /dev/null +++ b/bin/run.js @@ -0,0 +1,5 @@ +#!/usr/bin/env node + +import { execute } from '@oclif/core' + +await execute({ dir: import.meta.url }) diff --git a/cli.js b/cli.js deleted file mode 100755 index 3d9962a3..00000000 --- a/cli.js +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env node - -'use strict' - -const path = require('node:path') -const commist = require('commist')() -const argv = require('yargs-parser')(process.argv) -const help = require('help-me')({ - // the default - dir: path.join(path.dirname(require.main.filename), 'help') -}) -const start = require('./start') -const eject = require('./eject') -const generate = require('./generate') -const generatePlugin = require('./generate-plugin') -const generateSwagger = require('./generate-swagger') -const generateReadme = require('./generate-readme') -const printRoutes = require('./print-routes') -const printPlugins = require('./print-plugins') -commist.register('start', start.cli) -commist.register('eject', eject.cli) -commist.register('generate', generate.cli) -commist.register('generate-plugin', generatePlugin.cli) -commist.register('generate-swagger', generateSwagger.cli) -commist.register('readme', generateReadme.cli) -commist.register('help', help.toStdout) -commist.register('version', function () { - console.log(require('./package.json').version) -}) -commist.register('print-routes', printRoutes.cli) -commist.register('print-plugins', printPlugins.cli) - -if (argv.help) { - const command = argv._.splice(2)[0] - - help.toStdout(command) -} else { - const res = commist.parse(process.argv.splice(2)) - - if (res) { - // no command was recognized - help.toStdout(res) - } -} diff --git a/eject.js b/eject.js deleted file mode 100644 index 8daddc98..00000000 --- a/eject.js +++ /dev/null @@ -1,50 +0,0 @@ -'use strict' - -const path = require('node:path') -const generify = require('generify') -const argv = require('yargs-parser') -const log = require('./log') - -function eject (dir, template) { - return new Promise((resolve, reject) => { - generify(path.join(__dirname, 'templates', template), dir, {}, function (file) { - log('debug', `generated ${file}`) - }, function (err) { - if (err) { - return reject(err) - } - resolve() - }) - }) -} - -function cli (args) { - const opts = argv(args) - - let template - if (opts.lang === 'ts' || opts.lang === 'typescript') { - template = 'eject-ts' - } else { - if (opts.esm) { - template = 'eject-esm' - } else { - template = 'eject' - } - } - - return eject(process.cwd(), template).catch(function (err) { - if (err) { - log('error', err.message) - process.exit(1) - } - }) -} - -module.exports = { - eject, - cli -} - -if (require.main === module) { - cli() -} diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 00000000..2302ba5a --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,11 @@ +import neostandard from 'neostandard' + +export default [ + { + files: ['**/*.ts', '**/*.js'], + }, + { + ignores: ['dist/*'], + }, + ...neostandard({ ts: true }), +] diff --git a/examples/.env b/examples/.env deleted file mode 100644 index b39061ac..00000000 --- a/examples/.env +++ /dev/null @@ -1 +0,0 @@ -GREETING=world diff --git a/examples/async-await-plugin.js b/examples/async-await-plugin.js deleted file mode 100644 index b0629de7..00000000 --- a/examples/async-await-plugin.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict' - -module.exports = async function (fastify, options) { - fastify.get('/', async function (req, reply) { - return { hello: 'world' } - }) -} diff --git a/examples/package-type-module/CJS-plugin-with-custom-options.cjs b/examples/package-type-module/CJS-plugin-with-custom-options.cjs deleted file mode 100644 index 5d8fc7a6..00000000 --- a/examples/package-type-module/CJS-plugin-with-custom-options.cjs +++ /dev/null @@ -1,6 +0,0 @@ -'use strict' - -exports.default = function (fastify, options, next) { - fastify.get('/', (req, reply) => reply.send(options)) - next() -} diff --git a/examples/package-type-module/ESM-plugin-with-custom-options.js b/examples/package-type-module/ESM-plugin-with-custom-options.js deleted file mode 100644 index d8c0832c..00000000 --- a/examples/package-type-module/ESM-plugin-with-custom-options.js +++ /dev/null @@ -1,10 +0,0 @@ -// Module code is always strict mode code. -// http://www.ecma-international.org/ecma-262/6.0/#sec-strict-mode-code -// -// this file has a .js extension, but the package.json in this folder contains '"type":"module"' - -export default async function plugin (fastify, options) { - fastify.get('/', async function (req, reply) { - return options - }) -} diff --git a/examples/package-type-module/ESM-plugin-with-custom-server-options.js b/examples/package-type-module/ESM-plugin-with-custom-server-options.js deleted file mode 100644 index d63c2918..00000000 --- a/examples/package-type-module/ESM-plugin-with-custom-server-options.js +++ /dev/null @@ -1,14 +0,0 @@ -// Module code is always strict mode code. -// http://www.ecma-international.org/ecma-262/6.0/#sec-strict-mode-code -// -// this file has a .js extension, but the package.json in this folder contains '"type":"module"' - -export default async function plugin (fastify, options) { - fastify.get('/foo/', async function (req, reply) { - return 'foo' - }) -} - -export const options = { - ignoreTrailingSlash: true -} diff --git a/examples/package-type-module/package.json b/examples/package-type-module/package.json deleted file mode 100644 index 2e64cf34..00000000 --- a/examples/package-type-module/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "esm-plugin-as-js", - "version": "1.0.0", - "description": "example of a ESM plugin with js filetype and type module", - "main": "ESM-plugin-with-custom-options.js", - "type":"module", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "", - "license": "MIT" -} diff --git a/examples/plugin-common-prefix.js b/examples/plugin-common-prefix.js deleted file mode 100644 index 67a3dfcf..00000000 --- a/examples/plugin-common-prefix.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict' - -module.exports = function (fastify, options, next) { - fastify.decorate('test', true) - fastify.get('/hello-world', function (req, reply) { - req.log.trace('trace') - req.log.debug('debug') - reply.send({ hello: 'world' }) - }) - fastify.post('/help', function (req, reply) { - reply.send({ hello: 'world' }) - }) - next() -} diff --git a/examples/plugin-with-custom-options.js b/examples/plugin-with-custom-options.js deleted file mode 100644 index 77e15949..00000000 --- a/examples/plugin-with-custom-options.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict' - -module.exports = function (fastify, options, next) { - fastify.get('/', (req, reply) => reply.send(options)) - next() -} - -module.exports.options = { - hello: 'test' -} diff --git a/examples/plugin-with-env.js b/examples/plugin-with-env.js deleted file mode 100644 index 1c2a9c65..00000000 --- a/examples/plugin-with-env.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict' - -module.exports = async function (fastify, options) { - fastify.get('/', async function (req, reply) { - return { hello: process.env.GREETING } - }) -} diff --git a/examples/plugin-with-logger.js b/examples/plugin-with-logger.js deleted file mode 100644 index 10d342b1..00000000 --- a/examples/plugin-with-logger.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict' - -module.exports = function (fastify, options, next) { - fastify.get('/', function (req, reply) { - reply.send({ hello: 'world' }) - }) - next() -} - -module.exports.options = { - logger: { - redact: { - censor: '***', - paths: [ - 'foo' - ] - } - } -} diff --git a/examples/plugin-with-options.js b/examples/plugin-with-options.js deleted file mode 100644 index 75edbb1b..00000000 --- a/examples/plugin-with-options.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict' - -module.exports = function (fastify, options, next) { - fastify.get('/', function (req, reply) { - reply.send({ hello: 'world' }) - }) - next() -} - -module.exports.options = { - https: { - key: 'key', - cert: 'cert' - } -} diff --git a/examples/plugin-with-preloaded.js b/examples/plugin-with-preloaded.js deleted file mode 100644 index 5dc93f5a..00000000 --- a/examples/plugin-with-preloaded.js +++ /dev/null @@ -1,13 +0,0 @@ -/* global GLOBAL_MODULE_1, GLOBAL_MODULE_3 */ -'use strict' -const t = require('tap') - -module.exports = async function (fastify, options) { - fastify.get('/', async function (req, reply) { - return { hasPreloaded: GLOBAL_MODULE_1 && GLOBAL_MODULE_3 } - }) - fastify.addHook('onReady', function () { - t.ok(GLOBAL_MODULE_1) - t.ok(GLOBAL_MODULE_3) - }) -} diff --git a/examples/plugin.js b/examples/plugin.js deleted file mode 100644 index ff5dce2a..00000000 --- a/examples/plugin.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict' - -module.exports = function (fastify, options, next) { - fastify.decorate('test', true) - fastify.get('/', function (req, reply) { - req.log.trace('trace') - req.log.debug('debug') - reply.send({ hello: 'world' }) - }) - fastify.post('/', function (req, reply) { - reply.send({ hello: 'world' }) - }) - next() -} diff --git a/examples/ts-plugin-with-custom-options.js b/examples/ts-plugin-with-custom-options.js deleted file mode 100644 index 050fcb08..00000000 --- a/examples/ts-plugin-with-custom-options.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict' - -exports.default = function (fastify, options, next) { - fastify.get('/', (req, reply) => reply.send(options)) - next() -} - -exports.options = { - hello: 'test' -} diff --git a/examples/ts-plugin-with-custom-options.mjs b/examples/ts-plugin-with-custom-options.mjs deleted file mode 100644 index fbebebd7..00000000 --- a/examples/ts-plugin-with-custom-options.mjs +++ /dev/null @@ -1,12 +0,0 @@ -// Module code is always strict mode code. -// http://www.ecma-international.org/ecma-262/6.0/#sec-strict-mode-code - -export default async function plugin (fastify, options) { - fastify.get('/', async function (req, reply) { - return options - }) -} - -export const options = { - hello: 'test' -} diff --git a/examples/ts-plugin-with-options.js b/examples/ts-plugin-with-options.js deleted file mode 100644 index be33ef30..00000000 --- a/examples/ts-plugin-with-options.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict' - -exports.default = function (fastify, options, next) { - fastify.get('/', function (req, reply) { - reply.send({ hello: 'world' }) - }) - next() -} - -exports.options = { - https: { - key: 'key', - cert: 'cert' - } -} diff --git a/examples/ts-plugin.js b/examples/ts-plugin.js deleted file mode 100644 index 5463c6ad..00000000 --- a/examples/ts-plugin.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict' - -exports.default = function (fastify, options, next) { - fastify.decorate('test', true) - fastify.get('/', function (req, reply) { - reply.send({ hello: 'world' }) - }) - fastify.post('/', function (req, reply) { - reply.send({ hello: 'world' }) - }) - next() -} diff --git a/generate-plugin.js b/generate-plugin.js deleted file mode 100755 index 237706e7..00000000 --- a/generate-plugin.js +++ /dev/null @@ -1,116 +0,0 @@ -'use strict' - -const { - readFile, - writeFile -} = require('node:fs').promises -const { existsSync } = require('node:fs') -const path = require('node:path') -const chalk = require('chalk') -const generify = require('generify') -const argv = require('yargs-parser') -const cliPkg = require('./package') -const { execSync } = require('node:child_process') -const { promisify } = require('node:util') -const log = require('./log') - -const pluginTemplate = { - dir: 'plugin', - main: 'index.js', - types: 'index.d.ts', - scripts: { - lint: 'standard && npm run lint:typescript', - 'lint:typescript': 'ts-standard', - test: 'npm run lint && npm run unit && npm run test:typescript', - 'test:typescript': 'tsd', - unit: 'node --test' - }, - dependencies: { - 'fastify-plugin': cliPkg.dependencies['fastify-plugin'] - }, - devDependencies: { - '@types/node': cliPkg.devDependencies['@types/node'], - fastify: cliPkg.devDependencies.fastify, - 'fastify-tsconfig': cliPkg.devDependencies['fastify-tsconfig'], - standard: cliPkg.devDependencies.standard, - 'ts-standard': cliPkg.devDependencies['ts-standard'], - tsd: cliPkg.devDependencies.tsd, - typescript: cliPkg.devDependencies.typescript - }, - tsd: { - directory: 'test' - }, - logInstructions: function (pkg) { - log('debug', 'saved package.json') - log('info', `project ${pkg.name} generated successfully`) - log('debug', `run '${chalk.bold('npm install')}' to install the dependencies`) - log('debug', `run '${chalk.bold('npm test')}' to execute the tests`) - } -} - -async function generate (dir, template) { - const generifyPromise = promisify(generify) - await generifyPromise( - path.join(__dirname, 'templates', template.dir), - dir, - {} - ) - - process.chdir(dir) - execSync('npm init -y') - - log('info', `reading package.json in ${dir}`) - - const pkg = await readFile('package.json').then(JSON.parse) - pkg.main = template.main - pkg.types = template.types - pkg.description = '' - pkg.license = 'MIT' - pkg.scripts = Object.assign(pkg.scripts || {}, template.scripts) - pkg.dependencies = Object.assign(pkg.dependencies || {}, template.dependencies) - pkg.devDependencies = Object.assign(pkg.devDependencies || {}, template.devDependencies) - pkg.tsd = Object.assign(pkg.tsd || {}, template.tsd) - - log('debug', 'edited package.json, saving') - - await writeFile('package.json', JSON.stringify(pkg, null, 2)) - - template.logInstructions(pkg) -} - -function cli (args) { - const opts = argv(args) - const dir = opts._[0] - - if (dir && existsSync(dir)) { - if (dir !== '.' && dir !== './') { - log('error', 'directory ' + opts._[0] + ' already exists') - process.exit(1) - } - } - if (dir === undefined) { - log('error', 'must specify a directory to \'fastify generate\'') - process.exit(1) - } - if (!opts.integrate && existsSync(path.join(dir, 'package.json'))) { - log('error', 'a package.json file already exists in target directory. retry with the --integrate flag to proceed') - process.exit(1) - } - - generate(dir, pluginTemplate).catch(function (err) { - if (err) { - log('error', err.message) - process.exit(1) - } - }) -} - -module.exports = { - generate, - cli, - pluginTemplate -} - -if (require.main === module) { - cli(process.argv.slice(2)) -} diff --git a/generate-readme.js b/generate-readme.js deleted file mode 100644 index 410c49f4..00000000 --- a/generate-readme.js +++ /dev/null @@ -1,138 +0,0 @@ -'use strict' - -const { readFileSync, existsSync } = require('node:fs') -const path = require('node:path') -const generify = require('generify') -const argv = require('yargs-parser') -const { execSync } = require('node:child_process') -const log = require('./log') - -function toMarkdownList (a) { - return a.map(d => `- ${d}`).join('\n') -} -function generate (dir, { pluginMeta, encapsulated, pluginFileName }) { - process.chdir(dir) - return new Promise((resolve, reject) => { - if (!existsSync(path.join(dir, 'package.json'))) { - execSync('npm init -y') - log('info', `generated package.json in ${dir}`) - } - - log('info', `reading package.json in ${dir}`) - let pkg = readFileSync('package.json') - try { - pkg = JSON.parse(pkg) - } catch (err) { - return reject(err) - } - - pluginMeta.decorators = pluginMeta.decorators ? pluginMeta.decorators : { fastify: [], reply: [] } - pluginMeta.dependencies = pluginMeta.dependencies ? pluginMeta.dependencies : [] - - const peerDepFastify = pkg.peerDependencies ? pkg.peerDependencies.fastify : '' - const depFastify = pkg.dependencies ? pkg.dependencies.fastify : '' - const minFastify = pluginMeta.fastify || peerDepFastify || depFastify - - let accessibilityTemplate = '' - if (!encapsulated) { - accessibilityTemplate = '- [X] Accessible in the same context where you require them\n- [ ] Accessible only in a child context\n' - } else { - accessibilityTemplate = '- [ ] Accessible in the same context where you require them\n- [X] Accessible only in a child context\n' - } - - const fastifyDecorators = toMarkdownList(pluginMeta.decorators.fastify) - const replyDecorators = toMarkdownList(pluginMeta.decorators.reply) - const pluginDeps = toMarkdownList(pluginMeta.dependencies) - - generify( - path.join(__dirname, 'templates', 'readme'), - dir, - { - accessibilityTemplate, - fastifyDecorators, - replyDecorators, - pluginDeps, - packageName: pkg.name, - pluginFileName, - minFastify - }, - function (file) { - log('debug', `generated ${file}`) - }, - function (err) { - if (err) { - return reject(err) - } - log('info', `README for plugin ${pkg.name} generated successfully`) - resolve() - } - ) - }) -} - -function stop (error) { - if (error) { - console.log(error) - process.exit(1) - } - process.exit() -} - -function showHelp () { - console.log( - readFileSync(path.join(__dirname, 'help', 'readme.txt'), 'utf8') - ) - return stop() -} - -function cli (args) { - const opts = argv(args) - - const dir = process.cwd() - - if (opts._.length !== 1) { - log('error', 'Missing the required file parameter\n') - return showHelp() - } - - if (existsSync(path.join(dir, 'README.md'))) { - log('error', 'a README.md file already exists in target directory') - process.exit(1) - } - - const pluginPath = path.join(dir, path.basename(opts._[0], '.js')) - - let plugin - try { - plugin = require(pluginPath) - } catch (err) { - log('error', 'plugin could not be loaded', err) - process.exit(1) - } - - const pluginMeta = plugin[Symbol.for('plugin-meta')] - - if (!pluginMeta) { - log('error', 'no plugin metadata could be found. Are you sure that you use https://github.com/fastify/fastify-plugin ?') - process.exit(1) - } - - const encapsulated = !plugin[Symbol.for('skip-override')] - const pluginFileName = path.basename(opts._[0]) - - generate(dir, { pluginMeta, encapsulated, pluginFileName }).catch(function (err) { - if (err) { - log('error', err.message) - process.exit(1) - } - }) -} - -module.exports = { - generate, - cli -} - -if (require.main === module) { - cli(process.argv.slice(2)) -} diff --git a/generate-swagger.js b/generate-swagger.js deleted file mode 100644 index 54c2c180..00000000 --- a/generate-swagger.js +++ /dev/null @@ -1,95 +0,0 @@ -#! /usr/bin/env node - -'use strict' - -const parseArgs = require('./args') -const argv = require('yargs-parser') -const log = require('./log') -const { - exit, - requireFastifyForModule, - requireServerPluginFromPath, - showHelpForCommand -} = require('./util') -const fp = require('fastify-plugin') - -let Fastify = null - -function loadModules (opts) { - try { - Fastify = requireFastifyForModule(opts._[0]).module - } catch (e) { - module.exports.stop(e) - } -} - -async function generateSwagger (args) { - const opts = parseArgs(args) - const extraOpts = argv(args) - if (opts.help) { - return showHelpForCommand('generate-swagger') - } - - if (opts._.length !== 1) { - console.error('Missing the required file parameter\n') - return showHelpForCommand('generate-swagger') - } - - loadModules(opts) - - const fastify = await runFastify(opts) - try { - if (fastify.swagger == null) { - log('error', '@fastify/swagger plugin not installed') - process.exit(1) - } - - if (extraOpts.yaml) { - return fastify.swagger({ yaml: true }) - } else { - return JSON.stringify(fastify.swagger(), undefined, 2) - } - } finally { - fastify.close() - } -} - -async function runFastify (opts) { - require('dotenv').config() - - let file = null - - try { - file = await requireServerPluginFromPath(opts._[0]) - } catch (e) { - return module.exports.stop(e) - } - - const fastify = Fastify(opts.options) - - const pluginOptions = {} - if (opts.prefix) { - pluginOptions.prefix = opts.prefix - } - - await fastify.register(fp(file), pluginOptions) - await fastify.ready() - - return fastify -} - -function stop (message) { - exit(message) -} - -function cli (args) { - return generateSwagger(args).then(swagger => { - process.stdout.write(swagger + '\n') - }) -} - -module.exports = { cli, stop, generateSwagger } - -if (require.main === module) { - cli(process.argv.slice(2)) -} diff --git a/generate.js b/generate.js deleted file mode 100755 index e3165944..00000000 --- a/generate.js +++ /dev/null @@ -1,208 +0,0 @@ -'use strict' - -const { - readFile, - writeFile, - existsSync -} = require('node:fs') -const path = require('node:path') -const chalk = require('chalk') -const generify = require('generify') -const argv = require('yargs-parser') -const cliPkg = require('./package') -const { execSync } = require('node:child_process') -const log = require('./log') - -const javascriptTemplate = { - dir: 'app', - main: 'app.js', - scripts: { - test: 'node --test test/**/*.test.js', - start: 'fastify start -l info app.js', - dev: 'fastify start -w -l info -P app.js' - }, - dependencies: { - fastify: cliPkg.dependencies.fastify, - 'fastify-plugin': cliPkg.devDependencies['fastify-plugin'] || cliPkg.dependencies['fastify-plugin'], - '@fastify/autoload': cliPkg.devDependencies['@fastify/autoload'], - '@fastify/sensible': cliPkg.devDependencies['@fastify/sensible'], - 'fastify-cli': '^' + cliPkg.version - }, - devDependencies: {}, - logInstructions: function (pkg) { - log('debug', 'saved package.json') - log('info', `project ${pkg.name} generated successfully`) - log('debug', `run '${chalk.bold('npm install')}' to install the dependencies`) - log('debug', `run '${chalk.bold('npm start')}' to start the application`) - log('debug', `run '${chalk.bold('npm run dev')}' to start the application with pino-pretty pretty logging (not suitable for production)`) - log('debug', `run '${chalk.bold('npm test')}' to execute the unit tests`) - - if (pkg.scripts.lint) { - log('debug', `run '${chalk.bold('npm lint')}' to run linter and fix code style issues`) - } - } -} - -const typescriptTemplate = { - dir: 'app-ts', - main: 'app.ts', - scripts: { - test: 'npm run build:ts && tsc -p test/tsconfig.json && c8 node --test -r ts-node/register "test/**/*.ts"', - start: 'npm run build:ts && fastify start -l info dist/app.js', - 'build:ts': 'tsc', - 'watch:ts': 'tsc -w', - dev: 'npm run build:ts && concurrently -k -p "[{name}]" -n "TypeScript,App" -c "yellow.bold,cyan.bold" "npm:watch:ts" "npm:dev:start"', - 'dev:start': 'fastify start --ignore-watch=.ts$ -w -l info -P dist/app.js' - }, - dependencies: { - fastify: cliPkg.dependencies.fastify, - 'fastify-plugin': cliPkg.devDependencies['fastify-plugin'] || cliPkg.dependencies['fastify-plugin'], - '@fastify/autoload': cliPkg.devDependencies['@fastify/autoload'], - '@fastify/sensible': cliPkg.devDependencies['@fastify/sensible'], - 'fastify-cli': '^' + cliPkg.version - }, - devDependencies: { - '@types/node': cliPkg.devDependencies['@types/node'], - c8: cliPkg.devDependencies.c8, - 'ts-node': cliPkg.devDependencies['ts-node'], - concurrently: cliPkg.devDependencies.concurrently, - 'fastify-tsconfig': cliPkg.devDependencies['fastify-tsconfig'], - typescript: cliPkg.devDependencies.typescript - }, - nodemonConfig: { - watch: ['src/'], - ignore: ['dist/*'] - }, - logInstructions: function (pkg) { - log('debug', 'saved package.json') - log('info', `project ${pkg.name} generated successfully`) - log('debug', `run '${chalk.bold('npm install')}' to install the dependencies`) - log('debug', `run '${chalk.bold('npm start')}' to start the application`) - log('debug', `run '${chalk.bold('npm build:ts')}' to compile the typescript application`) - log('debug', `run '${chalk.bold('npm run dev')}' to start the application with pino-pretty pretty logging (not suitable for production)`) - log('debug', `run '${chalk.bold('npm test')}' to execute the unit tests`) - } -} - -function generate (dir, template) { - return new Promise((resolve, reject) => { - generify(path.join(__dirname, 'templates', template.dir), dir, {}, function (file) { - log('debug', `generated ${file}`) - }, function (err) { - if (err) { - return reject(err) - } - - process.chdir(dir) - execSync('npm init -y') - - log('info', `reading package.json in ${dir}`) - readFile('package.json', (err, data) => { - if (err) { - return reject(err) - } - - let pkg - try { - pkg = JSON.parse(data) - } catch (err) { - return reject(err) - } - - pkg.main = template.main - - pkg.type = template.type - - pkg.scripts = Object.assign(pkg.scripts || {}, template.scripts) - - pkg.dependencies = Object.assign(pkg.dependencies || {}, template.dependencies) - - pkg.devDependencies = Object.assign(pkg.devDependencies || {}, template.devDependencies) - - log('debug', 'edited package.json, saving') - writeFile('package.json', JSON.stringify(pkg, null, 2), (err) => { - if (err) { - return reject(err) - } - - template.logInstructions(pkg) - resolve() - }) - }) - }) - }) -} - -function cli (args) { - const opts = argv(args) - const dir = opts._[0] - - if (dir && existsSync(dir)) { - if (dir !== '.' && dir !== './') { - log('error', 'directory ' + opts._[0] + ' already exists') - process.exit(1) - } - } - if (dir === undefined) { - log('error', 'must specify a directory to \'fastify generate\'') - process.exit(1) - } - if (!opts.integrate && existsSync(path.join(dir, 'package.json'))) { - log('error', 'a package.json file already exists in target directory. retry with the --integrate flag to proceed') - process.exit(1) - } - - let template - if (opts.lang === 'ts' || opts.lang === 'typescript') { - template = { ...typescriptTemplate } - - if (opts.esm) { - template.dir = 'app-ts-esm' - template.type = 'module' - - template.devDependencies.c8 = cliPkg.devDependencies.c8 - template.scripts.test = 'npm run build:ts && tsc -p test/tsconfig.json && FASTIFY_AUTOLOAD_TYPESCRIPT=1 node --test --experimental-test-coverage --loader ts-node/esm test/**/*.ts' - } - } else { - template = { ...javascriptTemplate } - - if (opts.esm) { - template.dir = 'app-esm' - template.type = 'module' - - template.devDependencies.c8 = cliPkg.devDependencies.c8 - template.scripts.test = 'node --test test/**/*.test.js' - } - - if (opts.standardlint) { - template.scripts = { - ...template.scripts, - pretest: 'standard', - lint: 'standard --fix' - } - - template.devDependencies = { - ...template.devDependencies, - standard: cliPkg.devDependencies.standard - } - } - } - - generate(dir, template).catch(function (err) { - if (err) { - log('error', err.message) - process.exit(1) - } - }) -} - -module.exports = { - generate, - cli, - javascriptTemplate, - typescriptTemplate -} - -if (require.main === module) { - cli(process.argv.slice(2)) -} diff --git a/help/eject.txt b/help/eject.txt deleted file mode 100644 index f610be48..00000000 --- a/help/eject.txt +++ /dev/null @@ -1,13 +0,0 @@ -Usage: fastify eject - -Turns your application into a standalone executable with a server.js file being added. - -You can set the port to listen on with the PORT environment variable (default to 3000). - -OPTS - - --esm - use the ESM template - - --lang=ts, --lang=typescript - use the TypeScript template diff --git a/help/generate-plugin.txt b/help/generate-plugin.txt deleted file mode 100644 index acef1b87..00000000 --- a/help/generate-plugin.txt +++ /dev/null @@ -1,5 +0,0 @@ -Usage: fastify generate-plugin - -Sets up plugin project with `npm init -y` and -generates a sample Fastify plugin project -in the . Specify `.` to create files in the current working directory. diff --git a/help/generate-swagger.txt b/help/generate-swagger.txt deleted file mode 100644 index 06ebf355..00000000 --- a/help/generate-swagger.txt +++ /dev/null @@ -1,8 +0,0 @@ -Usage: fastify generate-plugin [opts] [--] [] - -Generate Swagger/OpenAPI schema for a project using @fastify/swagger. - -OPTS - - --yaml=true - generate in YAML format diff --git a/help/generate.txt b/help/generate.txt deleted file mode 100644 index bb5ded3d..00000000 --- a/help/generate.txt +++ /dev/null @@ -1,19 +0,0 @@ -Usage: fastify generate - -Sets up project with `npm init -y` and -generates a sample Fastify project -in the . Specify `.` to create files in the current working directory. - -OPTS - - --esm - use the ESM template - - --integrate - overwrite it when the target directory is the current directory (.) - - --lang=ts, --lang=typescript - use the TypeScript template - - --standardlint - for JavaScript template, optionally includes linter to fix code style issues diff --git a/help/help.txt b/help/help.txt deleted file mode 100644 index fc106f18..00000000 --- a/help/help.txt +++ /dev/null @@ -1,15 +0,0 @@ -Fastify command line interface available commands are: - - * start start a server - * eject turns your application into a standalone executable with a server.js file being added. - * eject --lang=ts turns your application into a standalone executable with a server.ts file being added. - * generate generate a new project - * generate-plugin generate a new plugin project - * generate-swagger generate Swagger/OpenAPI schema for a project using @fastify/swagger - * readme generate a README.md for the plugin - * print-routes prints the representation of the internal radix tree used by the router, useful for debugging. - * print-plugins prints the representation of the internal plugin tree used by avvio, useful for debugging. - * version the current fastify-cli version - * help help about commands - -Launch 'fastify help [command]' to learn more about each command. diff --git a/help/print-plugins.txt b/help/print-plugins.txt deleted file mode 100644 index 22ff3162..00000000 --- a/help/print-plugins.txt +++ /dev/null @@ -1 +0,0 @@ -Usage: fastify print-plugins diff --git a/help/print-routes.txt b/help/print-routes.txt deleted file mode 100644 index 72212160..00000000 --- a/help/print-routes.txt +++ /dev/null @@ -1,12 +0,0 @@ -Usage: fastify print-routes - -OPTS - - --method - print debugging safe internal router tree for a given method - - --common-prefix - print uncompressed radix tree - - --include-hooks - display all properties from the route.store object for each displayed route diff --git a/help/readme.txt b/help/readme.txt deleted file mode 100644 index 6e2fe972..00000000 --- a/help/readme.txt +++ /dev/null @@ -1,5 +0,0 @@ -Usage: fastify readme - -Sets up project with `npm init -y` and -generates a sample README.md file -from the . \ No newline at end of file diff --git a/help/start.txt b/help/start.txt deleted file mode 100644 index 95b526bf..00000000 --- a/help/start.txt +++ /dev/null @@ -1,76 +0,0 @@ -Usage: fastify start [opts] [--] [] - -OPTS - - -p, --port - [env: FASTIFY_PORT or PORT] - Port to listen on (default to 3000) - - -a, --address - [env: FASTIFY_ADDRESS] - Address to listen on - - -s, --socket - [env: FASTIFY_SOCKET] - Socket to listen on - - -l, --log-level - [env: FASTIFY_LOG_LEVEL] - Log level (default to fatal) - - -r, --require - [env: FASTIFY_REQUIRE] - Module to preload - - -i, --import - [env: FASTIFY_IMPORT] - ES Module to preload - - -L, --logging-module - [env: FASTIFY_LOGGING_MODULE] - Path to logging configuration module to use - - -P, --pretty-logs - [env: FASTIFY_PRETTY_LOGS] - Prints pretty logs - - -o, --options - [env: FASTIFY_OPTIONS] - Use custom options - - -w, --watch - [env: FASTIFY_WATCH] - Watch process.cwd() directory for changes, recursively; when that happens, the process will auto reload. - - -x, --prefix - [env: FASTIFY_PREFIX] - Set the prefix - - --body-limit - [env: FASTIFY_BODY_LIMIT] - Defines the maximum payload, in bytes, the server is allowed to accept - - -T, --plugin-timeout - The maximum amount of time that a plugin can take to load (default to 10 seconds). - - -g, --close-grace-delay - The maximum amount of time before forcefully closing pending requests (default to 500 ms) - - -d, --debug - Start Fastify app in debug mode with nodejs inspector - - -I, --debug-port - Set the inspector port (default to 9320) - - -h, --help - Show this help message - -Examples: - - start plugin.js on port 8080 - - fastify start -p 8080 plugin.js - - start plugin.js passing custom options to it - - fastify start plugin.js -- --custom-plugin-option-1 --custom-plugin-option-2 diff --git a/helper.d.ts b/helper.d.ts deleted file mode 100644 index f8642543..00000000 --- a/helper.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -import fastify from 'fastify' - -declare module 'fastify-cli/helper.js' { - module helper { - export function build(args: Array, additionalOptions?: Object, serverOptions?: Object): ReturnType; - } - - export = helper; -} diff --git a/helper.js b/helper.js deleted file mode 100644 index bfb893b9..00000000 --- a/helper.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict' - -const { runFastify } = require('./start') - -module.exports = { - build (args, additionalOptions = {}, serverOptions = {}) { - Object.defineProperty(additionalOptions, 'ready', { - value: true, - enumerable: false, - writable: false - }) - return runFastify(args, additionalOptions, serverOptions) - }, - listen: runFastify -} diff --git a/index.d.ts b/index.d.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/lib/watch/constants.js b/lib/watch/constants.js deleted file mode 100644 index 742c31bc..00000000 --- a/lib/watch/constants.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict' - -module.exports = { - GRACEFUL_SHUT: 'GRACEFUL SHUTDOWN', - READY: 'ready', - TIMEOUT: 5000 -} diff --git a/lib/watch/fork.js b/lib/watch/fork.js deleted file mode 100644 index d4ee7922..00000000 --- a/lib/watch/fork.js +++ /dev/null @@ -1,56 +0,0 @@ -'use strict' - -const chalk = require('chalk') -const { stop, runFastify } = require('../../start') - -const { - GRACEFUL_SHUT, - READY, - TIMEOUT -} = require('./constants.js') - -let fastify - -function exit () { - if (this) { console.log(chalk.red(this.message)) } - process.exit(1) -} - -process.on('message', function (event) { - if (event === GRACEFUL_SHUT) { - const message = chalk.red('[fastify-cli] process forced end') - setTimeout(exit.bind({ message }), TIMEOUT).unref() - if (fastify) { - fastify.close(() => { - process.exit(0) - }) - } else { - process.exit(0) - } - } -}) - -process.on('uncaughtException', (err) => { - console.log(chalk.red(err)) - const message = chalk.red('[fastify-cli] app crashed - waiting for file changes before starting...') - exit.bind({ message })() -}) - -const main = async () => { - fastify = await runFastify(process.argv.splice(2)) - const type = process.env.childEvent - - process.send({ type, err: null }) - - try { - await fastify.ready() - process.send({ type: READY }) - } catch (err) { - stop(err) - } -} - -main().catch((err) => { - console.error(err) - process.exit(1) -}) diff --git a/lib/watch/index.js b/lib/watch/index.js deleted file mode 100644 index 7b9589b6..00000000 --- a/lib/watch/index.js +++ /dev/null @@ -1,113 +0,0 @@ -'use strict' - -const path = require('node:path') -const cp = require('node:child_process') -const chalk = require('chalk') -const { arrayToRegExp, logWatchVerbose } = require('./utils') -const { GRACEFUL_SHUT } = require('./constants.js') - -const EventEmitter = require('node:events') -const chokidar = require('chokidar') -const forkPath = path.join(__dirname, './fork.js') - -const watch = function (args, ignoreWatch, verboseWatch) { - const emitter = new EventEmitter() - let allStop = false - let childs = [] - - const stop = (watcher = null, err = null) => { - childs.forEach(function (child) { - child.kill() - }) - - childs = [] - if (err) { - console.log(chalk.red(err)) - } - if (watcher) { - allStop = true - return watcher.close() - } - } - - process.on('uncaughtException', () => { - stop() - childs.push(run('restart')) - }) - - let readyEmitted = false - - const run = (event) => { - const childEvent = { childEvent: event } - const env = Object.assign({}, require('dotenv').config().parsed, process.env, childEvent) - const _child = cp.fork(forkPath, args, { - env, - cwd: process.cwd(), - encoding: 'utf8' - }) - - _child.on('exit', function (code, signal) { - if (childs.length === 0 && !allStop) { - childs.push(run('restart')) - } - return null - }) - - _child.on('message', (event) => { - const { type, err } = event - if (err) { - emitter.emit('error', err) - return null - } - - if (type === 'ready') { - if (readyEmitted) { - return - } - - readyEmitted = true - } - - emitter.emit(type, err) - }) - - return _child - } - - childs.push(run('start')) - const ignoredArr = ignoreWatch.split(' ').map((item) => item.trim()).filter((item) => item.length) - const ignoredPattern = arrayToRegExp(ignoredArr) - - const watcher = chokidar.watch(process.cwd(), { ignored: ignoredPattern }) - watcher.on('ready', function () { - watcher.on('all', function (event, filepath) { - if (verboseWatch) { - logWatchVerbose(event, filepath) - } - try { - const child = childs.shift() - child.send(GRACEFUL_SHUT) - } catch (err) { - if (childs.length !== 0) { - console.log(chalk.red(err)) - stop(watcher, err) - } - childs.push(run('restart')) - } - }) - }) - - emitter.on('error', (err) => { - stop(watcher, err) - }) - - emitter.on('close', () => { - stop(watcher) - }) - - emitter.stop = stop.bind(null, watcher) - - return emitter -} - -module.exports = watch diff --git a/lib/watch/utils.js b/lib/watch/utils.js deleted file mode 100644 index dae37507..00000000 --- a/lib/watch/utils.js +++ /dev/null @@ -1,26 +0,0 @@ -'use strict' - -const chalk = require('chalk') -const path = require('node:path') - -const arrayToRegExp = (arr) => { - const reg = arr.map((file) => { - if (/^\./.test(file)) { return `\\${file}` } - return file - }).join('|') - return new RegExp(`(${reg})`) -} - -const logWatchVerbose = (event, filepath) => { - const relativeFilepath = path.relative(process.cwd(), filepath) - console.log( - chalk.gray( - `[fastify-cli] watch - '${event}' occurred on '${relativeFilepath}'` - ) - ) -} - -module.exports = { - arrayToRegExp, - logWatchVerbose -} diff --git a/log.js b/log.js deleted file mode 100644 index 84987b9a..00000000 --- a/log.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict' - -const chalk = require('chalk') - -const levels = { - debug: 0, - info: 1, - error: 2 -} - -const colors = [l => l, chalk.green, chalk.red] - -function log (severity, line) { - const level = levels[severity] || 0 - if (level === 1) { - line = '--> ' + line - } - console.log(colors[level](line)) -} - -module.exports = log diff --git a/package.json b/package.json index 8ffd78e7..c9723de1 100644 --- a/package.json +++ b/package.json @@ -1,100 +1,77 @@ { "name": "fastify-cli", - "version": "6.2.1", - "description": "Run a fastify route with one command!", - "main": "cli.js", - "type": "commonjs", + "description": "A new CLI generated with oclif", + "version": "0.0.0", + "author": "KaKa", "bin": { - "fastify": "cli.js" + "fastify": "./bin/run.js", + "fastify-dev": "./bin/dev.js" }, - "scripts": { - "lint": "standard", - "lint:fix": "standard --fix", - "pretest": "xcopy /e /k /i . \"..\\node_modules\\fastify-cli\" || rsync -r --exclude=node_modules ./ node_modules/fastify-cli || echo 'this is fine'", - "test": "npm run unit:suites && c8 --clean npm run test:cli-and-typescript", - "unit:cjs": "node suite-runner.js \"templates/app/test/**/*.test.js\"", - "unit:esm": "node suite-runner.js \"templates/app-esm/test/**/*.test.js\"", - "unit:ts-cjs": "cross-env TS_NODE_PROJECT=./test/configs/ts-cjs.tsconfig.json node -r ts-node/register suite-runner.js \"templates/app-ts/test/**/*.test.ts\"", - "unit:ts-esm": "cross-env TS_NODE_PROJECT=./test/configs/ts-esm.tsconfig.json FASTIFY_AUTOLOAD_TYPESCRIPT=1 node -r ts-node/register --loader ts-node/esm suite-runner.js \"templates/app-ts-esm/test/**/*.test.ts\"", - "unit:suites": "node should-skip-test-suites.js || npm run all-suites", - "all-suites": "npm run unit:cjs && npm run unit:esm && npm run unit:ts-cjs && npm run unit:ts-esm", - "unit:cli-js-esm": "node suite-runner.js \"test/esm/**/*.test.js\"", - "unit:cli-js": "tap \"test/**/*.test.js\" --no-coverage --timeout 400 --jobs 1 --color -R specy", - "unit:cli-ts": "cross-env TS_NODE_PROJECT=./test/configs/ts-cjs.tsconfig.json tap \"test/**/*.test.ts\" --no-coverage --timeout 400 --jobs 1 --color -R specy", - "unit:cli": "npm run unit:cli-js && npm run unit:cli-ts && npm run unit:cli-js-esm", - "test:cli-and-typescript": "npm run unit:cli && npm run test:typescript", - "test:typescript": "tsd templates/plugin -t ./../../index.d.ts && tsc --project templates/app-ts/tsconfig.json --noEmit && tsc --project templates/app-ts-esm/tsconfig.json --noEmit" + "bugs": "https://github.com/fastify/fastify-cli/issues", + "dependencies": { + "@inquirer/prompts": "^5.1.2", + "@oclif/core": "^4", + "@oclif/plugin-help": "^6", + "chokidar": "^3.6.0", + "close-with-grace": "^1.3.0", + "colorette": "^2.0.20", + "ejs": "^3.1.10", + "fastify": "^4.28.1", + "pkg-up": "^5.0.0", + "resolve-from": "^5.0.0", + "semver": "^7.6.2", + "undici": "^6.19.2" }, - "keywords": [ - "fastify", - "cli", - "one command" + "devDependencies": { + "@oclif/prettier-config": "^0.2.1", + "@oclif/test": "^4", + "@types/chai": "^4", + "@types/ejs": "^3.1.5", + "@types/mocha": "^10", + "@types/node": "^18", + "@types/semver": "^7.5.8", + "chai": "^4", + "eslint": "^9", + "mocha": "^10", + "neostandard": "^0.11.0", + "oclif": "^4", + "shx": "^0.3.3", + "ts-node": "^10", + "typescript": "^5" + }, + "engines": { + "node": ">=18.0.0" + }, + "files": [ + "/bin", + "/dist", + "/oclif.manifest.json" ], - "author": "Tomas Della Vedova - @delvedor (http://delved.org)", - "contributors": [ - { - "name": "Matteo Collina", - "email": "hello@matteocollina.com" - } + "homepage": "https://github.com/fastify/fastify-cli", + "keywords": [ + "oclif" ], "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/fastify/fastify-cli.git" + "main": "dist/index.js", + "type": "module", + "oclif": { + "bin": "fastify", + "dirname": "fastify", + "commands": "./dist/commands", + "plugins": [ + "@oclif/plugin-help" + ], + "topicSeparator": " " }, - "bugs": { - "url": "https://github.com/fastify/fastify-cli/issues" - }, - "homepage": "https://github.com/fastify/fastify-cli#readme", - "standard": { - "ignore": [ - "test/data/parsing-error.js", - "test/data/undefinedVariable.js" - ] - }, - "dependencies": { - "@fastify/deepmerge": "^2.0.0", - "chalk": "^4.1.2", - "chokidar": "^3.5.2", - "close-with-grace": "^1.1.0", - "commist": "^3.0.0", - "dotenv": "^16.0.0", - "fastify": "^4.26.1", - "fastify-plugin": "^4.0.0", - "generify": "^4.0.0", - "help-me": "^4.0.1", - "is-docker": "^2.0.0", - "pino-pretty": "^11.2.0", - "pkg-up": "^3.1.0", - "resolve-from": "^5.0.0", - "semver": "^7.3.5", - "yargs-parser": "^21.1.1" - }, - "devDependencies": { - "@fastify/autoload": "^5.0.0", - "@fastify/pre-commit": "^2.0.2", - "@fastify/sensible": "^5.0.0", - "@types/node": "^20.4.4", - "@types/tap": "^15.0.5", - "c8": "^10.1.2", - "concurrently": "^8.2.2", - "cross-env": "^7.0.3", - "fastify-tsconfig": "^2.0.0", - "minimatch": "^9.0.5", - "proxyquire": "^2.1.3", - "rimraf": "^3.0.2", - "simple-get": "^4.0.0", - "sinon": "^18.0.0", - "standard": "^17.0.0", - "strip-ansi": "^6.0.1", - "tap": "^16.1.0", - "ts-node": "^10.4.0", - "ts-standard": "^12.0.1", - "tsd": "^0.31.0", - "typescript": "^5.2.2", - "walker": "^1.0.8" + "repository": "fastify/fastify-cli", + "scripts": { + "build": "shx rm -rf dist && tsc -b", + "lint": "eslint", + "postpack": "shx rm -f oclif.manifest.json", + "posttest": "npm run lint", + "prepack": "oclif manifest && oclif readme", + "test": "test --test \"test/**/*.test.ts\"", + "version": "oclif readme && git add README.md" }, - "tsd": { - "directory": "test" - } + "types": "dist/index.d.ts" } diff --git a/print-plugins.js b/print-plugins.js deleted file mode 100644 index c673130a..00000000 --- a/print-plugins.js +++ /dev/null @@ -1,79 +0,0 @@ -#! /usr/bin/env node - -'use strict' - -const parseArgs = require('./args') -const log = require('./log') -const { - exit, - requireFastifyForModule, - requireServerPluginFromPath, - showHelpForCommand -} = require('./util') - -let Fastify = null - -function loadModules (opts) { - try { - Fastify = requireFastifyForModule(opts._[0]).module - } catch (e) { - module.exports.stop(e) - } -} - -function printPlugins (args) { - const opts = parseArgs(args) - if (opts.help) { - return showHelpForCommand('print-plugins') - } - - if (opts._.length !== 1) { - console.error('Missing the required file parameter\n') - return showHelpForCommand('print-plugins') - } - - loadModules(opts) - - return runFastify(opts) -} - -async function runFastify (opts) { - require('dotenv').config() - - let file = null - - try { - file = await requireServerPluginFromPath(opts._[0]) - } catch (e) { - return module.exports.stop(e) - } - - const fastify = Fastify(opts.options) - - const pluginOptions = {} - if (opts.prefix) { - pluginOptions.prefix = opts.prefix - } - - await fastify.register(file, pluginOptions) - await fastify.ready() - log('debug', fastify.printPlugins()) - - return fastify -} - -function stop (message) { - exit(message) -} - -function cli (args) { - return printPlugins(args).then(fastify => { - if (fastify) return fastify.close() - }) -} - -module.exports = { cli, stop, printPlugins } - -if (require.main === module) { - cli(process.argv.slice(2)) -} diff --git a/print-routes.js b/print-routes.js deleted file mode 100644 index 0e259414..00000000 --- a/print-routes.js +++ /dev/null @@ -1,83 +0,0 @@ -#! /usr/bin/env node - -'use strict' - -const parseArgs = require('./args') -const log = require('./log') -const { - exit, - requireFastifyForModule, - requireServerPluginFromPath, - showHelpForCommand -} = require('./util') - -let Fastify = null - -function loadModules (opts) { - try { - Fastify = requireFastifyForModule(opts._[0]).module - } catch (e) { - module.exports.stop(e) - } -} - -function printRoutes (args) { - const opts = parseArgs(args) - if (opts.help) { - return showHelpForCommand('print-routes') - } - - if (opts._.length !== 1) { - console.error('Missing the required file parameter\n') - return showHelpForCommand('print-routes') - } - - loadModules(opts) - - return runFastify(opts) -} - -async function runFastify (opts) { - require('dotenv').config() - - let file = null - - try { - file = await requireServerPluginFromPath(opts._[0]) - } catch (e) { - return module.exports.stop(e) - } - - const fastify = Fastify(opts.options) - - const pluginOptions = {} - if (opts.prefix) { - pluginOptions.prefix = opts.prefix - } - - await fastify.register(file, pluginOptions) - await fastify.ready() - log('debug', fastify.printRoutes({ - method: opts.method, - commonPrefix: opts.commonPrefix, - includeHooks: opts.includeHooks - })) - - return fastify -} - -function stop (message) { - exit(message) -} - -function cli (args) { - return printRoutes(args).then(fastify => { - if (fastify) return fastify.close() - }) -} - -module.exports = { cli, stop, printRoutes } - -if (require.main === module) { - cli(process.argv.slice(2)) -} diff --git a/should-skip-test-suites.js b/should-skip-test-suites.js deleted file mode 100644 index 8053a82a..00000000 --- a/should-skip-test-suites.js +++ /dev/null @@ -1,7 +0,0 @@ -const nodeMajorVersion = process.versions.node.split('.').map(x => parseInt(x, 10))[0] -const shouldRunSuites = nodeMajorVersion >= 20 -if (!shouldRunSuites) { - console.info(`Skipped templates test suites on node ${nodeMajorVersion}`) - process.exit(0) -} -process.exit(1) diff --git a/src/commands/generate/project.ts b/src/commands/generate/project.ts new file mode 100644 index 00000000..5f12722b --- /dev/null +++ b/src/commands/generate/project.ts @@ -0,0 +1,250 @@ +import { confirm, input, select } from '@inquirer/prompts' +import { Args, Command, Flags } from '@oclif/core' +import { compile } from 'ejs' +import { execSync } from 'node:child_process' +import { access, mkdir, readFile, rm, stat, writeFile } from 'node:fs/promises' +import { basename, dirname, join, resolve } from 'node:path' +import { computePackageJSON } from '../../utils/package-json/project.js' + +export default class GenerateProject extends Command { + static args = { + name: Args.string({ description: 'Name of the project.', required: false }), + } + + static description = 'Generate fastify project' + + static flags = { + force: Flags.boolean({ default: false, description: 'Force to overwrite the project location when it exists.' }), + help: Flags.help(), + language: Flags.string({ description: 'Programming Language to use in project.' }), + lint: Flags.string({ description: 'Lint Tool to use in project.' }), + location: Flags.directory({ description: 'Location to place the project.' }), + repo: Flags.string({ description: 'Git Repository URL of the project.' }), + test: Flags.string({ description: 'Test Framework to use in project.' }), + } + + async run (): Promise { + const { args, flags } = await this.parse(GenerateProject) + + // validate + const answer: Record = {} + answer.language = this.corceLanguage(flags.language) + + answer.name = args.name ?? await input({ message: 'What is your project name?', validate: this.questionNameValidate }) + answer.location = flags.location ?? await input({ default: this.questionLocationDefault(answer.name as string), message: 'Where do you want to place your project?' }) + if (await this.questionOverwriteWhen(answer.location as string)) { + answer.force = await confirm({ default: flags.force ?? false, message: 'The folder already exist. Do you want to overwrite?' }) + + if (!answer.force) { + this.log('Terminated because location cannot be overwrite.') + this.exit(0) + } + } + + answer.repo = flags.repo ?? await input({ message: 'What is the repo url?' }) + answer.language = flags.language ?? await select({ choices: [{ value: 'JavaScript' }, { value: 'TypeScript' }], default: 'JavaScript', message: 'Which language will you use?' }) + answer.lint = flags.lint ?? await select({ choices: this.questionLintChoices(answer.language as string), default: this.questionLintDefault(answer.language as string), message: 'Which linter would you like to use?' }) + answer.test = flags.test ?? await select({ choices: [{ value: 'node:test' }, { value: 'tap' }], default: 'node:test', message: 'Which test framework would you like to use?' }) + + const fileList = await this.computeFileList(answer) + const files = await this.prepareFiles(fileList, answer) + await this.writeFiles(files, answer) + await this.npmInstall(answer) + this.log(`project "${answer.name as string}" initialized in "${answer.location as string}"`) + } + + computeFileList (answer: Record): string[] { + // we do not add .ejs in here + // we should find the file if .ejs exist first and then compile to the destination + const files: string[] = [ + '.vscode/launch.json', + 'README.md', + '__gitignore', + 'eslint.config.js', + ] + + if (answer.language === 'JavaScript') { + files.push( + 'app.js', + 'plugins/README.md', + 'plugins/sensible.js', + 'plugins/support.js', + 'routes/README.md', + 'routes/root.js', + 'routes/example/index.js', + 'test/helper.js', + 'test/plugins/support.test.js', + 'test/routes/root.test.js', + 'test/routes/example.test.js' + ) + } + + if (answer.language === 'TypeScript') { + files.push( + 'tsconfig.json', + 'tsconfig.build.json', + 'src/app.ts', + 'src/plugins/README.md', + 'src/plugins/sensible.ts', + 'src/plugins/support.ts', + 'src/routes/README.md', + 'src/routes/root.ts', + 'src/routes/example/index.ts', + 'test/helper.ts', + 'test/plugins/support.test.ts', + 'test/routes/root.test.ts', + 'test/routes/example.test.ts' + ) + } + + return files + } + + corceLanguage (str?: string): 'JavaScript' | 'TypeScript' { + switch (String(str).trim().toLowerCase()) { + case 'undefined': + case 'js': + case 'javascript': { + return 'JavaScript' + } + + case 'ts': + case 'typescript': { + return 'TypeScript' + } + + default: { + this.error(`Programming Language expected to be "JavaScript" or "TypeScript", but recieved "${str}"`, { exit: 1 }) + } + } + } + + async isFileExist (path: string): Promise { + try { + const stats = await stat(path) + return stats.isFile() + } catch { + return false + } + } + + async npmInstall (answer: Record): Promise { + this.log('run "npm install"') + const result = await confirm({ default: true, message: 'Do you want to run "npm install"?' }) + if (result) { + execSync('npm install', { + cwd: resolve(answer.location as string), + stdio: 'inherit', + }) + } + } + + async prepareFiles (files: string[], answer: Record): Promise<{ [path: string]: string }> { + const o: { [path: string]: string } = {} + o['package.json'] = await computePackageJSON(answer) + for await (let file of files) { + const { content, template } = await this.resolveFile(file) + const dir = dirname(file) + file = `${dir}/${basename(file).replace('__', '.')}` + if (template) { + const render = compile(content, { async: true }) + o[file] = await render(answer) + } else { + o[file] = content + } + } + + return o + } + + questionLintChoices (language: string): { value: string }[] { + switch (this.corceLanguage(language)) { + case 'JavaScript': { + return [{ value: 'eslint' }, { value: 'neostandard' }] + } + + case 'TypeScript': { + return [{ value: 'eslint' }, { value: 'neostandard' }] + } + } + } + + questionLintDefault (language: string): string { + switch (this.corceLanguage(language)) { + case 'JavaScript': { + return 'eslint' + } + + case 'TypeScript': { + return 'eslint' + } + } + } + + questionLocationDefault (name: string): string { + return this.toLocation(name) + } + + questionNameValidate (input?: string): string | true { + if (!input || input.trim() === '') { + return 'Project Name cannot be empty.' + } + + return true + } + + async questionOverwriteWhen (location: string): Promise { + return !(await this.validateProjectLocation(location)) + } + + async resolveFile (file: string): Promise<{ content: string, template: boolean }> { + const o = { content: '', template: false } + const ejsPath = resolve(join('templates', 'project', `${file}.ejs`)) + const isEJSTemplate = await this.isFileExist(ejsPath) + if (isEJSTemplate) { + o.template = true + const data = await readFile(ejsPath) + o.content = data.toString() + return o + } + + const filePath = resolve(join('templates', 'project', file)) + const isFileExist = await this.isFileExist(filePath) + if (isFileExist) { + const data = await readFile(filePath) + o.content = data.toString() + return o + } + + throw new Error(`File ${file} is missing, please check if the module installed properly.`) + } + + toLocation (name: string): string { + return name.trim().toLowerCase().replaceAll(/\s+/g, '-') + } + + async validateProjectLocation (location: string): Promise { + const path = resolve(location) + try { + await access(path) + return false + } catch { + return true + } + } + + async writeFiles (files: { [path: string]: string }, answer: Record): Promise { + if (answer.force === true) { + this.log(`remove folder "${answer.location as string}"`) + await rm(resolve(answer.location as string), { force: true, recursive: true }) + } + + for await (const [path, content] of Object.entries(files)) { + const realpath = join(answer.location as string, path) + const fullpath = resolve(realpath) + await mkdir(dirname(fullpath), { recursive: true }) + await writeFile(fullpath, content) + this.log(`write file "${path}" to "${realpath}"`) + } + } +} diff --git a/src/commands/start.ts b/src/commands/start.ts new file mode 100644 index 00000000..d08a09dd --- /dev/null +++ b/src/commands/start.ts @@ -0,0 +1,56 @@ +import { Args, Command } from '@oclif/core' + +import { Flags } from '@oclif/core' +import { start, StartOption } from '../utils/start.js' +import { watch } from '../utils/watch/watch.js' + +export default class Start extends Command { + static description = 'Start fastify instance' + + static args = { + entry: Args.string({ required: true, description: 'Entry point of fastify instance.' }), + } + + static flags = { + require: Flags.string({ char: 'r', description: 'Preload Modules, for example "-r ts-node/register".', multiple: true }), + import: Flags.string({ char: 'i', description: 'Preload Modules, for example "-i ./script.mjs".', multiple: true }), + port: Flags.integer({ char: 'p', description: '[default: 3000] Port listen on.' }), + address: Flags.string({ char: 'a', description: '[default: localhost] Address listen on. It can be either address or socket.' }), + debug: Flags.boolean({ char: 'd', description: '[default: false] Enable debug mode.' }), + 'debug-port': Flags.integer({ description: '[default: 9320] Inspector port.', dependsOn: ['debug'] }), + 'debug-address': Flags.string({ description: 'Inspector host, by default it will be either "localhost" or "0.0.0.0" in docker.', dependsOn: ['debug'] }), + prefix: Flags.string({ description: '[default: ""] Entry file prefix.' }), + 'pretty-logs': Flags.boolean({ char: 'P', description: '[default: false] Use "pino-pretty" for log display. It require to install the module seperately.' }), + watch: Flags.boolean(), + 'watch-ignore': Flags.string(), + 'watch-verbose': Flags.boolean(), + help: Flags.help(), + } + + async run (): Promise { + const { args, flags } = await this.parse(Start) + + // we normalize the options before start + const options: Partial = { + prefix: flags.prefix, + entry: args.entry, + require: flags.require, + import: flags.import, + port: flags.port, + address: flags.address, + debug: flags.debug, + debugPort: flags['debug-port'], + debugAddress: flags['debug-address'], + pretty: flags['pretty-logs'], + watch: flags.watch, + watchIgnorePattern: flags['watch-ignore'], + watchVerbose: flags['watch-verbose'], + } + + if (options.watch === true) { + await watch(options) + } else { + await start(options) + } + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 00000000..0237950a --- /dev/null +++ b/src/index.ts @@ -0,0 +1 @@ +export { run } from '@oclif/core' diff --git a/src/utils/import.ts b/src/utils/import.ts new file mode 100644 index 00000000..2c5b1ba3 --- /dev/null +++ b/src/utils/import.ts @@ -0,0 +1,12 @@ +// Internal Cache +const moduleCache = new Map() + +type ESMImport = (name: string) => Promise +// eslint-disable-next-line no-new-func +export const _esmImport = new Function('modulePath', 'return import(modulePath)') as ESMImport + +export async function _import (name: string): Promise { + const module = moduleCache.get(name) ?? await _esmImport(name) + moduleCache.set(name, module) + return module +} diff --git a/src/utils/package-json/base.ts b/src/utils/package-json/base.ts new file mode 100644 index 00000000..e49974e8 --- /dev/null +++ b/src/utils/package-json/base.ts @@ -0,0 +1,42 @@ +import { readFile } from 'fs/promises' +import { resolve } from 'path' +import { maxSatisfying } from 'semver' +import { Client } from 'undici' + +const npmRegistry = new Client('https://registry.npmjs.org', {}) +export async function fetchPackageVersion (name: string, constraint: string = 'latest'): Promise { + const response = await npmRegistry.request({ method: 'GET', path: `/${name}` }) + if (response.statusCode !== 200) throw Error(`Cannot find package "${name}" from npm registry.`) + const payload: any = await response.body.json() + if (constraint === 'latest') { + return payload['dist-tags'].latest + } + if (typeof constraint === 'string') { + const versions = Object.keys(payload.versions) + const version = maxSatisfying(versions, constraint) + if (version) return version + } + throw Error(`"${name}" has no version match constraint "${constraint}"`) +} + +export async function findPackageJSON (): Promise<{ + version: string + dependencies: { [key: string]: string } + devDependencies: { [key: string]: string } +}> { + const pkg = JSON.parse(await readFile(resolve('package.json'), { encoding: 'utf8' })) + return { + version: pkg.version, + dependencies: pkg.dependencies, + devDependencies: pkg.devDependencies, + } +} + +export function sort (dependencies: { [key: string]: string }): { [key: string]: string } { + const keys = Object.keys(dependencies).sort() + const obj: { [key: string]: string } = {} + for (const key of keys) { + obj[key] = dependencies[key] + } + return obj +} diff --git a/src/utils/package-json/project.ts b/src/utils/package-json/project.ts new file mode 100644 index 00000000..8f6b2aa7 --- /dev/null +++ b/src/utils/package-json/project.ts @@ -0,0 +1,80 @@ +import { fetchPackageVersion, findPackageJSON, sort } from './base.js' + +export async function computePackageJSON (answer: any): Promise { + const pkg = await findPackageJSON() + const template: any = {} + template.name = answer.name + template.main = answer.language === 'JavaScript' ? 'app.js' : 'dist/app.js' + template.scripts = computeScripts(answer) + template.dependencies = await computeDependencies(answer, pkg) + template.devDependencies = await computeDevDependencies(answer, pkg) + + return JSON.stringify(template, null, 2) +} + +export function computeScripts (answer: any): { [key: string]: string } { + const scripts: any = {} + // Lint Command + if (answer.lint === 'standard') { + scripts.lint = 'standard' + } + if (answer.lint === 'ts-standard') { + scripts.lint = 'ts-standard' + } + if (String(answer.lint).includes('eslint')) { + scripts.lint = 'eslint' + } + + // Other Commands + if (answer.language === 'JavaScript') { + scripts.test = 'node --test "test/**/*.test.js"' + scripts.start = 'fastify start info app.js' + scripts.dev = 'fastify start -w info -P app.js' + } + if (answer.language === 'TypeScript') { + scripts.test = 'node --test -r ts-node/register "test/**/*.test.ts"' + scripts.start = 'fastify start -r ts-node/register app.js' + scripts.dev = 'fastify start -r ts-node/register -w -P app.js' + } + return scripts +} + +export async function computeDependencies (_answer: any, pkg: any): Promise> { + const dependencies: any = {} + dependencies['@fastify/autoload'] = await fetchPackageVersion('@fastify/autoload') + dependencies['@fastify/sensible'] = await fetchPackageVersion('@fastify/sensible') + dependencies.fastify = await fetchPackageVersion('fastify') + dependencies['fastify-cli'] = pkg.version + dependencies['fastify-plugin'] = await fetchPackageVersion('fastify-plugin') + return sort(dependencies) +} + +// ts-standard + +export async function computeDevDependencies (answer: any, pkg: any): Promise> { + const dependencies: any = {} + if (answer.test === 'tap') { + // See https://github.com/tapjs/tapjs/blob/main/src/docs/content/changelog.md#200 + dependencies['tap'] = await fetchPackageVersion('tap', '^20.0.0') + } + if (answer.language === 'TypeScript') { + dependencies['@types/node'] = await fetchPackageVersion('@types/node') + dependencies['ts-node'] = await fetchPackageVersion('ts-node') + dependencies.typescript = await fetchPackageVersion('typescript', '~5.3.0') + } + if (answer.lint === 'eslint') { + // ESLint breaking changes always breaks plugin + // it requires a period of time before supporting + // pinning to the major version helps in this situation + dependencies.eslint = await fetchPackageVersion('eslint', '^9.0.0') + dependencies['@eslint/js'] = await fetchPackageVersion('@eslint/js') + dependencies['globals'] = await fetchPackageVersion('globals') + } + + if (answer.lint === 'neostandard') { + dependencies.eslint = await fetchPackageVersion('eslint', '^9.0.0') + dependencies.neostandard = await fetchPackageVersion('neostandard') + } + + return sort(dependencies) +} diff --git a/src/utils/require.ts b/src/utils/require.ts new file mode 100644 index 00000000..70228f53 --- /dev/null +++ b/src/utils/require.ts @@ -0,0 +1,112 @@ +import type { fastify as _FastifyModule, FastifyServerOptions } from 'fastify' +import { existsSync } from 'node:fs' +import { createRequire } from 'node:module' +import { extname, resolve } from 'node:path' +import { pathToFileURL } from 'node:url' +import resolveFrom from 'resolve-from' +import { _import } from './import.js' + +export const require = createRequire(pathToFileURL(process.cwd())) + +/** + * Resolve the nearest module from `process.cwd` + * @param {string} module package name + * @returns {object} module + */ +export function _require (module: string): T { + if (existsSync(module)) { + // we remove the extension if we require module + const fullPath = resolve(module) + const extension = extname(module) + const path = fullPath.split(extension)[0] + return require(path) + } else { + return require(module) + } +} + +export type FastifyModule = typeof _FastifyModule + +/** + * Resolve the nearest fastify module from `process.cwd` + * @param {string} entry entry file + * @returns {object} fastify module + */ +export function _requireFastify (entry: string): FastifyModule { + try { + const baseDir = resolve(process.cwd(), entry) + + return require(resolveFrom.silent(baseDir, 'fastify') ?? 'fastify') + } catch { + throw Error('unable to load fastify') + } +} +/** + * Resolve the entry file from `process.cwd` + * @param {string} entry entry file + * @returns {function} plugin module + */ +export async function _requireEntryFile (entry: string): Promise<{ plugin: any, options: FastifyServerOptions }> { + const path = resolve(process.cwd(), entry) + if (!existsSync(path)) { + throw Error(`${path} doesn't exist within ${process.cwd()}`) + } + + const defScriptType = await computeDefaultScriptType(path) + const scriptType = computeScriptType(path, defScriptType) + + let plugin + let options + if (scriptType === 'module') { + plugin = await _import(pathToFileURL(path).href) + } else { + plugin = require(path) + } + // we check if custom option provided + if (typeof plugin.options === 'object') options = plugin.options + + // we check if it use default export + if (typeof plugin.default === 'function') plugin = plugin.default + + // we check it again, if custom option provided through default export + if (typeof plugin.options === 'object') options = plugin.options + + if (isInvalidAsyncPlugin(plugin)) { + throw new Error('Async/Await plugin function should contain 2 arguments. Refer to documentation for more information.') + } + + return { plugin, options } +} + +/** + * Validate plugin arguments + * @param {function} plugin plugin function + * @returns {boolean} + */ +export function isInvalidAsyncPlugin (plugin: Function): boolean { + return plugin.length !== 2 && plugin.constructor.name === 'AsyncFunction' +} + +/** + * Compute the file script type + * @param {string} name file name + * @param {string} def default script type + * @returns {string} script type + */ +export function computeScriptType (name: string, def?: 'commonjs' | 'module'): 'commonjs' | 'module' { + const regexpModulePattern = /\.mjs$/i + const regexpCommonJSPattern = /\.cjs$/i + return (regexpModulePattern.test(name) ? 'module' : regexpCommonJSPattern.test(name) ? 'commonjs' : def) ?? 'commonjs' +} + +/** + * Compute the script type from the nearest `package.json` + * @param {string} cwd `process.cwd` + * @returns {string} script type + */ +export async function computeDefaultScriptType (cwd: string): Promise<'commonjs' | 'module' | undefined> { + const { pkgUp } = await _import('pkg-up') + const pkg = await pkgUp({ cwd }) + + if (typeof pkg === 'string') return require(pkg).type +} diff --git a/src/utils/start.ts b/src/utils/start.ts new file mode 100644 index 00000000..779f613e --- /dev/null +++ b/src/utils/start.ts @@ -0,0 +1,232 @@ +import closeWithGrace from 'close-with-grace' +import type { FastifyInstance, fastify as FastifyModule } from 'fastify' +import { constants } from 'node:fs' +import { access } from 'node:fs/promises' +import { resolve } from 'node:path' +import { _import } from './import.js' +import { _require, _requireEntryFile, _requireFastify } from './require.js' + +let Fastify: typeof FastifyModule +/** + * Cache fastify module to global + * It is useful when `watch` is enabled + * @param {string} entry entry file + */ +function cacheFastify (entry: string): void { + try { + Fastify = _requireFastify(entry) + } catch (error: any) { + stop(error) + } +} + +/** + * Start fastify instance + * @param {object} options options + * @returns + */ +export async function start (_o?: Partial): Promise { + const options = await normalizeStartOptions(_o) + + // preload - require + try { + for (const file of options.require) { + if (file) { + /** + * This check ensures we ignore `-r ""`, trailing `-r`, or + * other silly things the user might (inadvertently) be doing. + **/ + await _require(file) + } + } + } catch (error: any) { + stop(error) + } + // preload - import + try { + for (const file of options.import) { + if (file) { + /** + * This check ensures we ignore `-i ""`, trailing `-i`, or + * other silly things the user might (inadvertently) be doing. + **/ + await _import(file) + } + } + } catch (error: any) { + stop(error) + } + + // we update fastify reference + cacheFastify(options.entry) + + let entryPlugin = null + const fastifyOptions: any = { logger: { level: 'fatal' } } + + try { + const plugin = await _requireEntryFile(options.entry) + entryPlugin = plugin.plugin + Object.assign(fastifyOptions, plugin.options) + } catch (error: any) { + stop(error) + } + + if (options.pretty) { + const PinoPretty = _require('pino-pretty') + fastifyOptions.logger.stream = PinoPretty({ + colorize: true, + sync: true, + }) + } + + // debug + if (options.debug) { + const inspector = _require('node:inspector') + inspector.open(options.debugPort, options.debugAddress) + } + + const fastify = Fastify(fastifyOptions) + + await fastify.register(entryPlugin, { prefix: options.prefix }) + + const closeListeners = closeWithGrace({ delay: 500 }, async function ({ signal, err, manual }: any) { + if (err as boolean) { + fastify.log.error(err) + } + await fastify.close() + }) + + fastify.addHook('onClose', function (instance, done) { + closeListeners.uninstall() + done() + }) + + await fastify.listen({ port: options.port, host: options.address }) + + return fastify as any +} +/** + * Stop the process + * @param {object|string} message error or message + * @returns + */ +export function stop (message?: Error | string): void { + if (message instanceof Error) { + console.log(message) + return process.exit(1) + } else if (message !== undefined) { + console.log(`Warn: ${message}`) + return process.exit(1) + } + process.exit() +} + +export interface StartOption { + prefix: string + entry: string + require: string[] + import: string[] + port: number + address: string + debug: boolean + debugPort: number + debugAddress: string + pretty: boolean + watch: boolean + watchIgnorePattern: string + watchVerbose: boolean +} + +export async function normalizeStartOptions (options?: Partial): Promise { + options ??= {} + options.prefix = normalizePrefix(options.prefix) + options.entry = await normalizeEntry(options.entry) + options.require = normalizeRequire(options.require) + options.import = normalizeImport(options.import) + options.port = normalizePort(options.port) + options.address = await normalizeAddress(options.address) + options.debug = normalizeDebug(options.debug) + options.debugPort = normalizeDebugPort(options.debugPort) + options.debugAddress = await normalizeDebugAddress(options.debugAddress) + options.pretty = normalizePretty(options.pretty) + options.watch = normalizeWatch(options.watch) + options.watchIgnorePattern = normalizeWatchIgnorePattern(options.watchIgnorePattern) + options.watchVerbose = normalizeWatchVerbose(options.watchVerbose) + return options as StartOption +} + +export function normalizePrefix (prefix?: string): string { + return prefix ?? '' +} + +export async function normalizeEntry (entry?: string): Promise { + try { + if (typeof entry !== 'string') throw Error(`entry file expected to be a string, but recieved "${typeof entry}"`) + await access(resolve(process.cwd(), entry), constants.F_OK | constants.R_OK) + return entry + } catch { + throw Error(`entry file "${entry as string}" is not exist in ${process.cwd()} or do not have have permission to read.`) + } +} + +export function normalizeImport (imports?: string | string[]): string[] { + const array: string[] = [] + if (imports === undefined) return array + imports = typeof imports === 'string' ? [imports] : imports + + for (const p of imports) { + if (p.trim() !== '') array.push(p.trim()) + } + + return array +} + +export function normalizeRequire (requires?: string | string[]): string[] { + const array: string[] = [] + if (requires === undefined) return array + requires = typeof requires === 'string' ? [requires] : requires + + for (const p of requires) { + if (p.trim() !== '') array.push(p.trim()) + } + + return array +} + +export function normalizePort (port?: number): number { + return process.env.PORT as any ?? port ?? 3000 +} + +export async function normalizeAddress (address?: string): Promise { + const { default: isDocker } = await _import('is-docker') + return address ?? (isDocker() === true ? '0.0.0.0' : 'localhost') +} + +export function normalizeDebug (debug?: boolean): boolean { + return debug ?? false +} + +export function normalizeDebugPort (debugPort?: number): number { + return debugPort ?? 9320 +} + +export async function normalizeDebugAddress (debugAddress?: string): Promise { + const { default: isDocker } = await _import('is-docker') + return debugAddress ?? (isDocker() === true ? '0.0.0.0' : 'localhost') +} + +export function normalizePretty (pretty?: boolean): boolean { + return pretty ?? false +} + +export function normalizeWatch (watch?: boolean): boolean { + return watch ?? false +} + +export function normalizeWatchIgnorePattern (watchIgnorePattern?: string): string { + return watchIgnorePattern ?? 'node_modules .git' +} + +export function normalizeWatchVerbose (watchVerbose?: boolean): boolean { + return watchVerbose ?? false +} diff --git a/src/utils/watch/constants.ts b/src/utils/watch/constants.ts new file mode 100644 index 00000000..2699a98d --- /dev/null +++ b/src/utils/watch/constants.ts @@ -0,0 +1 @@ +export const TIMEOUT = 5000 diff --git a/src/utils/watch/events.ts b/src/utils/watch/events.ts new file mode 100644 index 00000000..0708d0aa --- /dev/null +++ b/src/utils/watch/events.ts @@ -0,0 +1,2 @@ +export const GRACEFUL_SHUTDOWN = 'GRACEFUL SHUTDOWN' +export const READY = 'READY' diff --git a/src/utils/watch/fastify.ts b/src/utils/watch/fastify.ts new file mode 100644 index 00000000..0ec8b1f0 --- /dev/null +++ b/src/utils/watch/fastify.ts @@ -0,0 +1,55 @@ +import { red } from 'colorette' +import { FastifyInstance } from 'fastify' +import { start, stop } from '../start.js' +import { TIMEOUT } from './constants.js' +import { GRACEFUL_SHUTDOWN, READY } from './events.js' + +function exit (this: any): void { + if (this as boolean) { console.log(red('[fastify-cli] process forced end')) } + process.exit(1) +} + +// shared instance +let fastify: null | FastifyInstance = null + +process.on('message', function (event) { + console.log('message', event) + if (event === GRACEFUL_SHUTDOWN) { + const message = red('[fastify-cli] process forced end') + setTimeout(exit.bind({ message }), TIMEOUT).unref() + if (fastify !== null) { + fastify.close(function () { + process.exit(0) + }) + } else { + process.exit(0) + } + } +}) + +process.on('uncaughtException', function (err) { + console.log(red(err as never as string)) + const message = red('[fastify-cli] app crashed - waiting for file changes before starting...') + exit.bind({ message })() +}) + +async function main (): Promise { + const type = process.env.CHILD_EVENT + let options: any = {} + if (typeof process.env.START_OPTIONS === 'string') options = JSON.parse(process.env.START_OPTIONS) + fastify = await start(options) + + process.send?.({ type, err: null }) + + try { + await fastify.ready() + process.send?.({ type: READY }) + } catch (err: any) { + stop(err) + } +} + +main().catch(function (err) { + console.error(err) + process.exit(1) +}) diff --git a/src/utils/watch/options.ts b/src/utils/watch/options.ts new file mode 100644 index 00000000..2aa282cd --- /dev/null +++ b/src/utils/watch/options.ts @@ -0,0 +1,8 @@ +export function normalizeIgnores (ignore: string): RegExp { + const ignores = ignore.split(' ').map((item) => item.trim()).filter((item) => item.length) + const regexp = ignores.map((file) => { + if (/^\./.test(file)) { return `\\${file}` } + return file + }).join('|') + return new RegExp(`(${regexp})`) +} diff --git a/src/utils/watch/watch.ts b/src/utils/watch/watch.ts new file mode 100644 index 00000000..9763a82f --- /dev/null +++ b/src/utils/watch/watch.ts @@ -0,0 +1,113 @@ +import { ChildProcess, fork } from 'child_process' +import { gray, red } from 'colorette' +import EventEmitter from 'events' +import { dirname, join, relative } from 'path' +import { normalizeStartOptions, StartOption } from '../start.js' +import { GRACEFUL_SHUTDOWN } from './events.js' +import { normalizeIgnores } from './options.js' +import { spawnWatcher } from './watcher.js' + +const forkPath = join(dirname(import.meta.url), './fastify.js') + +export async function watch (_o?: Partial): Promise { + // we should spawn a instance that hold node process non-exit + const interval = setInterval(() => {}, 1000000) + const options = await normalizeStartOptions(_o) + const ee: EventEmitter & { stop: Function } = new EventEmitter() as any + let stopAll = false + let childs: ChildProcess[] = [] + + const stop = (watcher?: any, err?: any): void => { + for (const child of childs) { + child.kill() + } + + childs = [] + + if (err !== undefined) { + console.log(red(err)) + } + + if (watcher !== undefined) { + stopAll = true + clearInterval(interval) + return watcher.close() + } + } + + let isReady = false + + const run = (event: string): ChildProcess => { + const env = Object.assign({}, process.env, { CHILD_EVENT: event, START_OPTIONS: JSON.stringify(options) }) + + const child = fork(forkPath, { + env, + cwd: process.cwd(), + }) + + child.on('exit', function (code, signal) { + if (code !== 0) { + stop() + } + if (childs.length === 0 && !stopAll) { + childs.push(run('restart')) + } + return null + }) + + child.on('message', function (event: { type: string, err?: any }) { + const { type, err } = event + if (err !== undefined && err !== null) { + ee.emit('error', err) + return + } + + if (type === 'ready') { + if (isReady) { + return + } + + isReady = true + } + + ee.emit(type, err) + }) + + return child + } + + childs.push(run('start')) + + const watcher = spawnWatcher(process.cwd(), normalizeIgnores(options.watchIgnorePattern)) + watcher.on('ready', function () { + watcher.on('all', function (event, path) { + console.log('watcher', event, path) + if (options.watchVerbose) { + console.log(gray(`[fastify-cli] watch - '${event}' occurred on '${relative(process.cwd(), path)}'`)) + } + + try { + const child = childs.shift() + child?.send(GRACEFUL_SHUTDOWN) + } catch (err: any) { + if (childs.length !== 0) { + console.log(red(err)) + stop(watcher, err) + } + childs.push(run('restart')) + } + }) + }) + + ee.on('error', function (err) { + stop(watcher, err) + }) + + ee.on('close', function () { + stop(watcher) + }) + + ee.stop = stop.bind(null, watcher) + + return ee +} diff --git a/src/utils/watch/watcher.ts b/src/utils/watch/watcher.ts new file mode 100644 index 00000000..12f8cb22 --- /dev/null +++ b/src/utils/watch/watcher.ts @@ -0,0 +1,5 @@ +import chokidar from 'chokidar' + +export function spawnWatcher (directory: string, ignored: RegExp): chokidar.FSWatcher { + return chokidar.watch(directory, { ignored }) +} diff --git a/start.js b/start.js deleted file mode 100755 index 4c4cdf07..00000000 --- a/start.js +++ /dev/null @@ -1,210 +0,0 @@ -#! /usr/bin/env node - -'use strict' - -require('dotenv').config() -const isDocker = require('is-docker') -const closeWithGrace = require('close-with-grace') -const deepmerge = require('@fastify/deepmerge')({ - cloneProtoObject (obj) { return obj } -}) - -const listenAddressDocker = '0.0.0.0' -const watch = require('./lib/watch') -const parseArgs = require('./args') -const { - exit, - requireModule, - requireESModule, - requireFastifyForModule, - requireServerPluginFromPath, - showHelpForCommand, - isKubernetes -} = require('./util') - -let Fastify = null - -function loadModules (opts) { - try { - const { module: fastifyModule } = requireFastifyForModule(opts._[0]) - - Fastify = fastifyModule - } catch (e) { - module.exports.stop(e) - } -} - -async function start (args) { - const opts = parseArgs(args) - if (opts.help) { - return showHelpForCommand('start') - } - - if (opts._.length !== 1) { - console.error('Missing the required file parameter\n') - return showHelpForCommand('start') - } - - loadModules(opts) - - if (opts.watch) { - return watch(args, opts.ignoreWatch, opts.verboseWatch) - } - - return runFastify(args) -} - -function stop (message) { - exit(message) -} - -function preloadCJSModules (opts) { - if (typeof opts.require === 'string') { - opts.require = [opts.require] - } - try { - opts.require.forEach(module => { - if (module) { - /* This check ensures we ignore `-r ""`, trailing `-r`, or - * other silly things the user might (inadvertently) be doing. - */ - requireModule(module) - } - }) - } catch (e) { - module.exports.stop(e) - } -} - -async function preloadESModules (opts) { - if (typeof opts.import === 'string') { - opts.import = [opts.import] - } - opts.import.forEach(async (m) => { - if (m) { - /* This check ensures we ignore `-i ""`, trailing `-i`, or - * other silly things the user might (inadvertently) be doing. - */ - try { - await requireESModule(m) - } catch (e) { - module.exports.stop(e) - } - } - }) -} - -async function runFastify (args, additionalOptions, serverOptions) { - const opts = parseArgs(args) - - if (opts.require) { - preloadCJSModules(opts) - } - if (opts.import) { - await preloadESModules(opts) - } - - opts.port = opts.port || process.env.PORT || 3000 - - loadModules(opts) - - let file = null - - try { - file = await requireServerPluginFromPath(opts._[0]) - } catch (e) { - return module.exports.stop(e) - } - - let logger - if (opts.loggingModule) { - try { - logger = requireModule(opts.loggingModule) - } catch (e) { - module.exports.stop(e) - } - } - - const defaultLogger = { - level: opts.logLevel - } - let options = { - logger: logger || defaultLogger, - - pluginTimeout: opts.pluginTimeout - } - - if (opts.bodyLimit) { - options.bodyLimit = opts.bodyLimit - } - - if (opts.prettyLogs) { - options.logger.transport = { - target: 'pino-pretty' - } - } - - if (opts.debug) { - if (process.version.match(/v[0-6]\..*/g)) { - stop('Fastify debug mode not compatible with Node.js version < 6') - } else { - require('node:inspector').open( - opts.debugPort, - opts.debugHost || isDocker() || isKubernetes() ? listenAddressDocker : undefined - ) - } - } - - if (serverOptions) { - options = deepmerge(options, serverOptions) - } - - if (opts.options && file.options) { - options = deepmerge(options, file.options) - } - - const fastify = Fastify(options) - - if (opts.prefix) { - opts.pluginOptions.prefix = opts.prefix - } - - const appConfig = Object.assign({}, opts.options ? file.options : {}, opts.pluginOptions, additionalOptions) - await fastify.register(file.default || file, appConfig) - - const closeListeners = closeWithGrace({ delay: opts.closeGraceDelay }, async function ({ signal, err, manual }) { - if (err) { - fastify.log.error(err) - } - await fastify.close() - }) - - await fastify.addHook('onClose', (instance, done) => { - closeListeners.uninstall() - done() - }) - - if (additionalOptions && additionalOptions.ready) { - await fastify.ready() - } else if (opts.address) { - await fastify.listen({ port: opts.port, host: opts.address }) - } else if (opts.socket) { - await fastify.listen({ path: opts.socket }) - } else if (isDocker() || isKubernetes()) { - await fastify.listen({ port: opts.port, host: listenAddressDocker }) - } else { - await fastify.listen({ port: opts.port }) - } - - return fastify -} - -function cli (args) { - start(args) -} - -module.exports = { start, stop, runFastify, cli } - -if (require.main === module) { - cli(process.argv.slice(2)) -} diff --git a/suite-runner.js b/suite-runner.js deleted file mode 100644 index 9f8dd876..00000000 --- a/suite-runner.js +++ /dev/null @@ -1,22 +0,0 @@ -const { run } = require('node:test') -const { spec } = require('node:test/reporters') -const path = require('path') -const glob = require('glob') - -const pattern = process.argv[process.argv.length - 1] - -console.info(`Running tests matching ${pattern}`) -const timeout = 10 * 60 * 1000 // 10 minutes -glob(pattern, (err, matches) => { - if (err) { - console.error(err) - process.exit(1) - } - const resolved = matches.map(file => path.resolve(file)) - const testRs = run({ files: resolved, timeout }) - .on('test:fail', () => { - process.exitCode = 1 - }) - .compose(spec) - testRs.pipe(process.stdout) -}) diff --git a/templates/app-esm/README.md b/templates/app-esm/README.md deleted file mode 100644 index 8e117cde..00000000 --- a/templates/app-esm/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# Getting Started with [Fastify-CLI](https://www.npmjs.com/package/fastify-cli) - -This project was bootstrapped with Fastify-CLI. - -## Available Scripts - -In the project directory, you can run: - -### `npm run dev` - -To start the app in dev mode.\ -Open [http://localhost:3000](http://localhost:3000) to view it in the browser. - -### `npm start` - -For production mode - -### `npm run test` - -Run the test cases. - -## Learn More - -To learn Fastify, check out the [Fastify documentation](https://fastify.dev/docs/latest/). diff --git a/templates/app-esm/__gitignore b/templates/app-esm/__gitignore deleted file mode 100644 index 52962c25..00000000 --- a/templates/app-esm/__gitignore +++ /dev/null @@ -1,58 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* - -# Runtime data -pids -*.pid -*.seed - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (http://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules -jspm_packages - -# Optional npm cache directory -.npm - -# Optional REPL history -.node_repl_history - -# 0x -profile-* - -# mac files -.DS_Store - -# vim swap files -*.swp - -# webstorm -.idea - -# vscode -.vscode -*code-workspace - -# clinic -profile* -*clinic* -*flamegraph* diff --git a/templates/app-esm/app.js b/templates/app-esm/app.js deleted file mode 100644 index d5c3cb32..00000000 --- a/templates/app-esm/app.js +++ /dev/null @@ -1,30 +0,0 @@ -import path from 'path' -import AutoLoad from '@fastify/autoload' -import { fileURLToPath } from 'url' - -const __filename = fileURLToPath(import.meta.url) -const __dirname = path.dirname(__filename) - -// Pass --options via CLI arguments in command to enable these options. -export const options = {} - -export default async function (fastify, opts) { - // Place here your custom code! - - // Do not touch the following lines - - // This loads all plugins defined in plugins - // those should be support plugins that are reused - // through your application - fastify.register(AutoLoad, { - dir: path.join(__dirname, 'plugins'), - options: Object.assign({}, opts) - }) - - // This loads all plugins defined in routes - // define your routes in one of these - fastify.register(AutoLoad, { - dir: path.join(__dirname, 'routes'), - options: Object.assign({}, opts) - }) -} diff --git a/templates/app-esm/package.json b/templates/app-esm/package.json deleted file mode 100644 index 47200257..00000000 --- a/templates/app-esm/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "type": "module" -} diff --git a/templates/app-esm/plugins/sensible.js b/templates/app-esm/plugins/sensible.js deleted file mode 100644 index bc239d0c..00000000 --- a/templates/app-esm/plugins/sensible.js +++ /dev/null @@ -1,11 +0,0 @@ -import fp from 'fastify-plugin' -import sensible from '@fastify/sensible' - -/** - * This plugins adds some utilities to handle http errors - * - * @see https://github.com/fastify/fastify-sensible - */ -export default fp(async (fastify) => { - fastify.register(sensible) -}) diff --git a/templates/app-esm/plugins/support.js b/templates/app-esm/plugins/support.js deleted file mode 100644 index a17d8b87..00000000 --- a/templates/app-esm/plugins/support.js +++ /dev/null @@ -1,10 +0,0 @@ -import fp from 'fastify-plugin' - -// the use of fastify-plugin is required to be able -// to export the decorators to the outer scope - -export default fp(async function (fastify, opts) { - fastify.decorate('someSupport', function () { - return 'hugs' - }) -}) diff --git a/templates/app-esm/routes/README.md b/templates/app-esm/routes/README.md deleted file mode 100644 index fd8d0b2a..00000000 --- a/templates/app-esm/routes/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# Routes Folder - -Routes define routes within your application. Fastify provides an -easy path to a microservice architecture, in the future you might want -to independently deploy some of those. - -In this folder you should define all the routes that define the endpoints -of your web application. -Each service is a [Fastify -plugin](https://fastify.dev/docs/latest/Reference/Plugins/), it is -encapsulated (it can have its own independent plugins) and it is -typically stored in a file; be careful to group your routes logically, -e.g. all `/users` routes in a `users.js` file. We have added -a `root.js` file for you with a '/' root added. - -If a single file become too large, create a folder and add a `index.js` file there: -this file must be a Fastify plugin, and it will be loaded automatically -by the application. You can now add as many files as you want inside that folder. -In this way you can create complex routes within a single monolith, -and eventually extract them. - -If you need to share functionality between routes, place that -functionality into the `plugins` folder, and share it via -[decorators](https://fastify.dev/docs/latest/Reference/Decorators/). - -If you're a bit confused about using `async/await` to write routes, you would -better take a look at [Promise resolution](https://fastify.dev/docs/latest/Reference/Routes/#promise-resolution) for more details. diff --git a/templates/app-esm/routes/example/index.js b/templates/app-esm/routes/example/index.js deleted file mode 100644 index fe68183b..00000000 --- a/templates/app-esm/routes/example/index.js +++ /dev/null @@ -1,5 +0,0 @@ -export default async function (fastify, opts) { - fastify.get('/', async function (request, reply) { - return 'this is an example' - }) -} diff --git a/templates/app-esm/routes/root.js b/templates/app-esm/routes/root.js deleted file mode 100644 index b8e4cbce..00000000 --- a/templates/app-esm/routes/root.js +++ /dev/null @@ -1,5 +0,0 @@ -export default async function (fastify, opts) { - fastify.get('/', async function (request, reply) { - return { root: true } - }) -} diff --git a/templates/app-esm/test/helper.js b/templates/app-esm/test/helper.js deleted file mode 100644 index 6fb94119..00000000 --- a/templates/app-esm/test/helper.js +++ /dev/null @@ -1,37 +0,0 @@ -// This file contains code that we reuse -// between our tests. - -import helper from 'fastify-cli/helper.js' -import path from 'path' -import { fileURLToPath } from 'url' - -const __filename = fileURLToPath(import.meta.url) -const __dirname = path.dirname(__filename) -const AppPath = path.join(__dirname, '..', 'app.js') - -// Fill in this config with all the configurations -// needed for testing the application -function config () { - return {} -} - -// automatically build and tear down our instance -async function build (t) { - // you can set all the options supported by the fastify CLI command - const argv = [AppPath] - - // fastify-plugin ensures that all decorators - // are exposed for testing purposes, this is - // different from the production setup - const app = await helper.build(argv, config()) - - // tear down our app after we are done - t.after(() => app.close()) - - return app -} - -export { - config, - build -} diff --git a/templates/app-esm/test/plugins/support.test.js b/templates/app-esm/test/plugins/support.test.js deleted file mode 100644 index c4297e84..00000000 --- a/templates/app-esm/test/plugins/support.test.js +++ /dev/null @@ -1,25 +0,0 @@ -import { test } from 'node:test' -import * as assert from 'node:assert' -import Fastify from 'fastify' -import Support from '../../plugins/support.js' - -test('support works standalone', async (t) => { - const fastify = Fastify() - fastify.register(Support) - - await fastify.ready() - assert.equal(fastify.someSupport(), 'hugs') -}) - -// You can also use plugin with opts in fastify v2 -// -// test('support works standalone', (t) => { -// t.plan(2) -// const fastify = Fastify() -// fastify.register(Support) -// -// fastify.ready((err) => { -// t.error(err) -// assert.equal(fastify.someSupport(), 'hugs') -// }) -// }) diff --git a/templates/app-esm/test/routes/example.test.js b/templates/app-esm/test/routes/example.test.js deleted file mode 100644 index 890e13e4..00000000 --- a/templates/app-esm/test/routes/example.test.js +++ /dev/null @@ -1,12 +0,0 @@ -import { test } from 'node:test' -import * as assert from 'node:assert' -import { build } from '../helper.js' - -test('example is loaded', async (t) => { - const app = await build(t) - - const res = await app.inject({ - url: '/example' - }) - assert.equal(res.payload, 'this is an example') -}) diff --git a/templates/app-esm/test/routes/root.test.js b/templates/app-esm/test/routes/root.test.js deleted file mode 100644 index 743b610d..00000000 --- a/templates/app-esm/test/routes/root.test.js +++ /dev/null @@ -1,12 +0,0 @@ -import { test } from 'node:test' -import * as assert from 'node:assert' -import { build } from '../helper.js' - -test('default root route', async (t) => { - const app = await build(t) - - const res = await app.inject({ - url: '/' - }) - assert.deepStrictEqual(JSON.parse(res.payload), { root: true }) -}) diff --git a/templates/app-ts-esm/__gitignore b/templates/app-ts-esm/__gitignore deleted file mode 100644 index f4cefe89..00000000 --- a/templates/app-ts-esm/__gitignore +++ /dev/null @@ -1,65 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* - -# Runtime data -pids -*.pid -*.seed - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (http://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules -jspm_packages - -# Optional npm cache directory -.npm - -# Optional REPL history -.node_repl_history - -# 0x -profile-* - -# mac files -.DS_Store - -# vim swap files -*.swp - -# webstorm -.idea - -# vscode -.vscode -*code-workspace - -# clinic -profile* -*clinic* -*flamegraph* - -# generated code -examples/typescript-server.js -test/types/index.js - -# compiled app -dist diff --git a/templates/app-ts-esm/__taprc b/templates/app-ts-esm/__taprc deleted file mode 100644 index 59c35c53..00000000 --- a/templates/app-ts-esm/__taprc +++ /dev/null @@ -1,5 +0,0 @@ -test-env: [ - TS_NODE_FILES=true, - TS_NODE_PROJECT=./test/tsconfig.json -] -timeout: 120 diff --git a/templates/app-ts-esm/package.json b/templates/app-ts-esm/package.json deleted file mode 100644 index 47200257..00000000 --- a/templates/app-ts-esm/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "type": "module" -} diff --git a/templates/app-ts-esm/src/app.ts b/templates/app-ts-esm/src/app.ts deleted file mode 100644 index 1aa38221..00000000 --- a/templates/app-ts-esm/src/app.ts +++ /dev/null @@ -1,46 +0,0 @@ -import * as path from 'path'; -import AutoLoad, {AutoloadPluginOptions} from '@fastify/autoload'; -import { FastifyPluginAsync } from 'fastify'; -import { fileURLToPath } from 'url' - -const __filename = fileURLToPath(import.meta.url) -const __dirname = path.dirname(__filename) - -export type AppOptions = { - // Place your custom options for app below here. -} & Partial; - - -// Pass --options via CLI arguments in command to enable these options. -const options: AppOptions = { -} - -const app: FastifyPluginAsync = async ( - fastify, - opts -): Promise => { - // Place here your custom code! - - // Do not touch the following lines - - // This loads all plugins defined in plugins - // those should be support plugins that are reused - // through your application - void fastify.register(AutoLoad, { - dir: path.join(__dirname, 'plugins'), - options: opts, - forceESM: true - }) - - // This loads all plugins defined in routes - // define your routes in one of these - void fastify.register(AutoLoad, { - dir: path.join(__dirname, 'routes'), - options: opts, - forceESM: true - }) - -}; - -export default app; -export { app, options } diff --git a/templates/app-ts-esm/test/helper.ts b/templates/app-ts-esm/test/helper.ts deleted file mode 100644 index 253bcf92..00000000 --- a/templates/app-ts-esm/test/helper.ts +++ /dev/null @@ -1,40 +0,0 @@ -// This file contains code that we reuse between our tests. -import helper from 'fastify-cli/helper.js' -import * as test from 'node:test' -import * as path from 'path' -import { fileURLToPath } from 'url' - -export type TestContext = { - after: typeof test.after -}; - -const __filename = fileURLToPath(import.meta.url) -const __dirname = path.dirname(__filename) -const AppPath = path.join(__dirname, '..', 'src', 'app.ts') - -// Fill in this config with all the configurations -// needed for testing the application -async function config () { - return {} -} - -// Automatically build and tear down our instance -async function build (t: TestContext) { - // you can set all the options supported by the fastify CLI command - const argv = [AppPath] - - // fastify-plugin ensures that all decorators - // are exposed for testing purposes, this is - // different from the production setup - const app = await helper.build(argv, await config()) - - // Tear down our app after we are done - t.after(() => void app.close()) - - return app -} - -export { - config, - build -} diff --git a/templates/app-ts-esm/test/plugins/support.test.ts b/templates/app-ts-esm/test/plugins/support.test.ts deleted file mode 100644 index c10957ba..00000000 --- a/templates/app-ts-esm/test/plugins/support.test.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { test } from 'node:test' -import * as assert from 'node:assert' -import Fastify from 'fastify' -import Support from '../../src/plugins/support.js' - -test('support works standalone', async (t) => { - const fastify = Fastify() - void fastify.register(Support) - await fastify.ready() - - assert.equal(fastify.someSupport(), 'hugs') -}) diff --git a/templates/app-ts-esm/test/routes/example.test.ts b/templates/app-ts-esm/test/routes/example.test.ts deleted file mode 100644 index 87f26ab7..00000000 --- a/templates/app-ts-esm/test/routes/example.test.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { test } from 'node:test' -import * as assert from 'node:assert' -import { build } from '../helper.js' - -test('example is loaded', async (t) => { - const app = await build(t) - - const res = await app.inject({ - url: '/example' - }) - - assert.equal(res.payload, 'this is an example') -}) diff --git a/templates/app-ts-esm/test/routes/root.test.ts b/templates/app-ts-esm/test/routes/root.test.ts deleted file mode 100644 index 743b610d..00000000 --- a/templates/app-ts-esm/test/routes/root.test.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { test } from 'node:test' -import * as assert from 'node:assert' -import { build } from '../helper.js' - -test('default root route', async (t) => { - const app = await build(t) - - const res = await app.inject({ - url: '/' - }) - assert.deepStrictEqual(JSON.parse(res.payload), { root: true }) -}) diff --git a/templates/app-ts-esm/test/tsconfig.json b/templates/app-ts-esm/test/tsconfig.json deleted file mode 100644 index 3b443e0d..00000000 --- a/templates/app-ts-esm/test/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "baseUrl": ".", - "noEmit": false - }, - "include": ["../src/**/*.ts", "**/*.ts"] -} diff --git a/templates/app-ts-esm/tsconfig.json b/templates/app-ts-esm/tsconfig.json deleted file mode 100644 index cd4a3e52..00000000 --- a/templates/app-ts-esm/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "fastify-tsconfig", - "compilerOptions": { - "outDir": "dist", - "sourceMap": true, - "moduleResolution": "NodeNext", - "module": "NodeNext", - "target": "ES2022", - "esModuleInterop": true - }, - "include": ["src/**/*.ts"] -} diff --git a/templates/app-ts/.vscode/launch.json b/templates/app-ts/.vscode/launch.json deleted file mode 100644 index 8c3b38e5..00000000 --- a/templates/app-ts/.vscode/launch.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "type": "node", - "name": "Launch Fastify", - "request": "launch", - "runtimeArgs": [ - "run", - "dev:start" - ], - "runtimeExecutable": "npm", - "skipFiles": [ - "/**" - ], - "console": "integratedTerminal", - "preLaunchTask": "npm: watch:ts" - } - ] -} diff --git a/templates/app-ts/.vscode/tasks.json b/templates/app-ts/.vscode/tasks.json deleted file mode 100644 index 6d0c2bd6..00000000 --- a/templates/app-ts/.vscode/tasks.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "type": "npm", - "label": "npm: watch:ts", - "detail": "tsc -w", - "group": { - "type": "build", - "isDefault": true - }, - "script": "watch:ts", - "isBackground": true, - "problemMatcher": "$tsc-watch", - "presentation": { - "reveal": "never", - "group": "watchers" - } - } - ] -} diff --git a/templates/app-ts/README.md b/templates/app-ts/README.md deleted file mode 100644 index 2d5e417c..00000000 --- a/templates/app-ts/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# Getting Started with [Fastify-CLI](https://www.npmjs.com/package/fastify-cli) -This project was bootstrapped with Fastify-CLI. - -## Available Scripts - -In the project directory, you can run: - -### `npm run dev` - -To start the app in dev mode.\ -Open [http://localhost:3000](http://localhost:3000) to view it in the browser. - -### `npm start` - -For production mode - -### `npm run test` - -Run the test cases. - -## Learn More - -To learn Fastify, check out the [Fastify documentation](https://fastify.dev/docs/latest/). diff --git a/templates/app-ts/__gitignore b/templates/app-ts/__gitignore deleted file mode 100644 index f4cefe89..00000000 --- a/templates/app-ts/__gitignore +++ /dev/null @@ -1,65 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* - -# Runtime data -pids -*.pid -*.seed - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (http://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules -jspm_packages - -# Optional npm cache directory -.npm - -# Optional REPL history -.node_repl_history - -# 0x -profile-* - -# mac files -.DS_Store - -# vim swap files -*.swp - -# webstorm -.idea - -# vscode -.vscode -*code-workspace - -# clinic -profile* -*clinic* -*flamegraph* - -# generated code -examples/typescript-server.js -test/types/index.js - -# compiled app -dist diff --git a/templates/app-ts/__taprc b/templates/app-ts/__taprc deleted file mode 100644 index 59c35c53..00000000 --- a/templates/app-ts/__taprc +++ /dev/null @@ -1,5 +0,0 @@ -test-env: [ - TS_NODE_FILES=true, - TS_NODE_PROJECT=./test/tsconfig.json -] -timeout: 120 diff --git a/templates/app-ts/src/plugins/README.md b/templates/app-ts/src/plugins/README.md deleted file mode 100644 index 1e61ee5b..00000000 --- a/templates/app-ts/src/plugins/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# Plugins Folder - -Plugins define behavior that is common to all the routes in your -application. Authentication, caching, templates, and all the other cross -cutting concerns should be handled by plugins placed in this folder. - -Files in this folder are typically defined through the -[`fastify-plugin`](https://github.com/fastify/fastify-plugin) module, -making them non-encapsulated. They can define decorators and set hooks -that will then be used in the rest of your application. - -Check out: - -* [The hitchhiker's guide to plugins](https://fastify.dev/docs/latest/Guides/Plugins-Guide/) -* [Fastify decorators](https://fastify.dev/docs/latest/Reference/Decorators/). -* [Fastify lifecycle](https://fastify.dev/docs/latest/Reference/Lifecycle/). diff --git a/templates/app-ts/src/plugins/sensible.ts b/templates/app-ts/src/plugins/sensible.ts deleted file mode 100644 index b1230219..00000000 --- a/templates/app-ts/src/plugins/sensible.ts +++ /dev/null @@ -1,11 +0,0 @@ -import fp from 'fastify-plugin' -import sensible, { SensibleOptions } from '@fastify/sensible' - -/** - * This plugins adds some utilities to handle http errors - * - * @see https://github.com/fastify/fastify-sensible - */ -export default fp(async (fastify) => { - fastify.register(sensible) -}) diff --git a/templates/app-ts/src/plugins/support.ts b/templates/app-ts/src/plugins/support.ts deleted file mode 100644 index 94bae4f5..00000000 --- a/templates/app-ts/src/plugins/support.ts +++ /dev/null @@ -1,20 +0,0 @@ -import fp from 'fastify-plugin' - -export interface SupportPluginOptions { - // Specify Support plugin options here -} - -// The use of fastify-plugin is required to be able -// to export the decorators to the outer scope -export default fp(async (fastify, opts) => { - fastify.decorate('someSupport', function () { - return 'hugs' - }) -}) - -// When using .decorate you have to specify added properties for Typescript -declare module 'fastify' { - export interface FastifyInstance { - someSupport(): string; - } -} diff --git a/templates/app-ts/src/routes/README.md b/templates/app-ts/src/routes/README.md deleted file mode 100644 index 9d209026..00000000 --- a/templates/app-ts/src/routes/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# Routes Folder - -Routes define endpoints within your application. Fastify provides an -easy path to a microservice architecture, in the future you might want -to independently deploy some of those. - -In this folder you should define all the routes that define the endpoints -of your web application. -Each service is a [Fastify -plugin](https://fastify.dev/docs/latest/Reference/Plugins/), it is -encapsulated (it can have its own independent plugins) and it is -typically stored in a file; be careful to group your routes logically, -e.g. all `/users` routes in a `users.js` file. We have added -a `root.js` file for you with a '/' root added. - -If a single file become too large, create a folder and add a `index.js` file there: -this file must be a Fastify plugin, and it will be loaded automatically -by the application. You can now add as many files as you want inside that folder. -In this way you can create complex routes within a single monolith, -and eventually extract them. - -If you need to share functionality between routes, place that -functionality into the `plugins` folder, and share it via -[decorators](https://fastify.dev/docs/latest/Reference/Decorators/). diff --git a/templates/app-ts/src/routes/example/index.ts b/templates/app-ts/src/routes/example/index.ts deleted file mode 100644 index 819c5e77..00000000 --- a/templates/app-ts/src/routes/example/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { FastifyPluginAsync } from "fastify" - -const example: FastifyPluginAsync = async (fastify, opts): Promise => { - fastify.get('/', async function (request, reply) { - return 'this is an example' - }) -} - -export default example; diff --git a/templates/app-ts/src/routes/root.ts b/templates/app-ts/src/routes/root.ts deleted file mode 100644 index 2a1b3342..00000000 --- a/templates/app-ts/src/routes/root.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { FastifyPluginAsync } from 'fastify' - -const root: FastifyPluginAsync = async (fastify, opts): Promise => { - fastify.get('/', async function (request, reply) { - return { root: true } - }) -} - -export default root; diff --git a/templates/app-ts/test/helper.ts b/templates/app-ts/test/helper.ts deleted file mode 100644 index 70451775..00000000 --- a/templates/app-ts/test/helper.ts +++ /dev/null @@ -1,37 +0,0 @@ -// This file contains code that we reuse between our tests. -const helper = require('fastify-cli/helper.js') -import * as path from 'path' -import * as test from 'node:test' - -export type TestContext = { - after: typeof test.after -}; - -const AppPath = path.join(__dirname, '..', 'src', 'app.ts') - -// Fill in this config with all the configurations -// needed for testing the application -async function config () { - return {} -} - -// Automatically build and tear down our instance -async function build (t: TestContext) { - // you can set all the options supported by the fastify CLI command - const argv = [AppPath] - - // fastify-plugin ensures that all decorators - // are exposed for testing purposes, this is - // different from the production setup - const app = await helper.build(argv, await config()) - - // Tear down our app after we are done - t.after(() => void app.close()) - - return app -} - -export { - config, - build -} diff --git a/templates/app-ts/test/tsconfig.json b/templates/app-ts/test/tsconfig.json deleted file mode 100644 index 384d1712..00000000 --- a/templates/app-ts/test/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "baseUrl": ".", - "noEmit": true - }, - "include": ["../src/**/*.ts", "**/*.ts"] -} diff --git a/templates/app-ts/tsconfig.json b/templates/app-ts/tsconfig.json deleted file mode 100644 index 50dd0995..00000000 --- a/templates/app-ts/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "fastify-tsconfig", - "compilerOptions": { - "outDir": "dist", - "sourceMap": true - }, - "include": ["src/**/*.ts"] -} diff --git a/templates/app/README.md b/templates/app/README.md deleted file mode 100644 index 2d5e417c..00000000 --- a/templates/app/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# Getting Started with [Fastify-CLI](https://www.npmjs.com/package/fastify-cli) -This project was bootstrapped with Fastify-CLI. - -## Available Scripts - -In the project directory, you can run: - -### `npm run dev` - -To start the app in dev mode.\ -Open [http://localhost:3000](http://localhost:3000) to view it in the browser. - -### `npm start` - -For production mode - -### `npm run test` - -Run the test cases. - -## Learn More - -To learn Fastify, check out the [Fastify documentation](https://fastify.dev/docs/latest/). diff --git a/templates/app/__gitignore b/templates/app/__gitignore deleted file mode 100644 index 52962c25..00000000 --- a/templates/app/__gitignore +++ /dev/null @@ -1,58 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* - -# Runtime data -pids -*.pid -*.seed - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (http://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules -jspm_packages - -# Optional npm cache directory -.npm - -# Optional REPL history -.node_repl_history - -# 0x -profile-* - -# mac files -.DS_Store - -# vim swap files -*.swp - -# webstorm -.idea - -# vscode -.vscode -*code-workspace - -# clinic -profile* -*clinic* -*flamegraph* diff --git a/templates/app/plugins/README.md b/templates/app/plugins/README.md deleted file mode 100644 index 1e61ee5b..00000000 --- a/templates/app/plugins/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# Plugins Folder - -Plugins define behavior that is common to all the routes in your -application. Authentication, caching, templates, and all the other cross -cutting concerns should be handled by plugins placed in this folder. - -Files in this folder are typically defined through the -[`fastify-plugin`](https://github.com/fastify/fastify-plugin) module, -making them non-encapsulated. They can define decorators and set hooks -that will then be used in the rest of your application. - -Check out: - -* [The hitchhiker's guide to plugins](https://fastify.dev/docs/latest/Guides/Plugins-Guide/) -* [Fastify decorators](https://fastify.dev/docs/latest/Reference/Decorators/). -* [Fastify lifecycle](https://fastify.dev/docs/latest/Reference/Lifecycle/). diff --git a/templates/app/test/helper.js b/templates/app/test/helper.js deleted file mode 100644 index a5237509..00000000 --- a/templates/app/test/helper.js +++ /dev/null @@ -1,35 +0,0 @@ -'use strict' - -// This file contains code that we reuse -// between our tests. - -const { build: buildApplication } = require('fastify-cli/helper') -const path = require('node:path') -const AppPath = path.join(__dirname, '..', 'app.js') - -// Fill in this config with all the configurations -// needed for testing the application -function config () { - return {} -} - -// automatically build and tear down our instance -async function build (t) { - // you can set all the options supported by the fastify CLI command - const argv = [AppPath] - - // fastify-plugin ensures that all decorators - // are exposed for testing purposes, this is - // different from the production setup - const app = await buildApplication(argv, config()) - - // close the app after we are done - t.after(() => app.close()) - - return app -} - -module.exports = { - config, - build -} diff --git a/templates/eject-esm/server.js b/templates/eject-esm/server.js deleted file mode 100644 index 3001fa62..00000000 --- a/templates/eject-esm/server.js +++ /dev/null @@ -1,38 +0,0 @@ -// Read the .env file. -import * as dotenv from 'dotenv' - -// Require the framework -import Fastify from 'fastify' - -// Require library to exit fastify process, gracefully (if possible) -import closeWithGrace from 'close-with-grace' - -// Import your application -import appService from './app.js' - -// Dotenv config -dotenv.config() - -// Instantiate Fastify with some config -const app = Fastify({ - logger: true -}) - -// Register your application as a normal plugin. -app.register(appService) - -// delay is the number of milliseconds for the graceful close to finish -closeWithGrace({ delay: process.env.FASTIFY_CLOSE_GRACE_DELAY || 500 }, async function ({ signal, err, manual }) { - if (err) { - app.log.error(err) - } - await app.close() -}) - -// Start listening. -app.listen({ port: process.env.PORT || 3000 }, (err) => { - if (err) { - app.log.error(err) - process.exit(1) - } -}) diff --git a/templates/eject-ts/server.ts b/templates/eject-ts/server.ts deleted file mode 100644 index d2ea5071..00000000 --- a/templates/eject-ts/server.ts +++ /dev/null @@ -1,33 +0,0 @@ -// Read the .env file. -import * as dotenv from "dotenv"; -dotenv.config(); - -// Require the framework -import Fastify from "fastify"; - -// Require library to exit fastify process, gracefully (if possible) -import closeWithGrace from "close-with-grace"; - -// Instantiate Fastify with some config -const app = Fastify({ - logger: true, -}); - -// Register your application as a normal plugin. -app.register(import("./app")); - -// delay is the number of milliseconds for the graceful close to finish -closeWithGrace({ delay: parseInt(process.env.FASTIFY_CLOSE_GRACE_DELAY) || 500 }, async function ({ signal, err, manual }) { - if (err) { - app.log.error(err) - } - await app.close() -} as closeWithGrace.CloseWithGraceAsyncCallback) - -// Start listening. -app.listen({ port: parseInt(process.env.PORT) || 3000 }, (err: any) => { - if (err) { - app.log.error(err); - process.exit(1); - } -}); diff --git a/templates/eject/server.js b/templates/eject/server.js deleted file mode 100644 index eb96a0e0..00000000 --- a/templates/eject/server.js +++ /dev/null @@ -1,35 +0,0 @@ -'use strict' - -// Read the .env file. -require('dotenv').config() - -// Require the framework -const Fastify = require('fastify') - -// Require library to exit fastify process, gracefully (if possible) -const closeWithGrace = require('close-with-grace') - -// Instantiate Fastify with some config -const app = Fastify({ - logger: true -}) - -// Register your application as a normal plugin. -const appService = require('./app.js') -app.register(appService) - -// delay is the number of milliseconds for the graceful close to finish -closeWithGrace({ delay: process.env.FASTIFY_CLOSE_GRACE_DELAY || 500 }, async function ({ signal, err, manual }) { - if (err) { - app.log.error(err) - } - await app.close() -}) - -// Start listening. -app.listen({ port: process.env.PORT || 3000 }, (err) => { - if (err) { - app.log.error(err) - process.exit(1) - } -}) diff --git a/templates/plugin/.github/workflows/ci.yml b/templates/plugin/.github/workflows/ci.yml deleted file mode 100644 index c8d01a63..00000000 --- a/templates/plugin/.github/workflows/ci.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: CI workflow -on: [push, pull_request] -jobs: - test: - runs-on: ${{ matrix.os }} - strategy: - matrix: - node-version: [10.x, 12.x, 14.x] - os: [ubuntu-latest, windows-latest] - steps: - - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node-version }} - - name: Install Dependencies - run: npm install --ignore-scripts - - name: Test - run: npm run test diff --git a/templates/plugin/.gitignore b/templates/plugin/.gitignore deleted file mode 100644 index 6214c438..00000000 --- a/templates/plugin/.gitignore +++ /dev/null @@ -1,80 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# TypeScript v1 declaration files -typings/ - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file -.env -.env.test - -# parcel-bundler cache (https://parceljs.org/) -.cache - -# next.js build output -.next - -# nuxt.js build output -.nuxt - -# vuepress build output -.vuepress/dist - -# Serverless directories -.serverless/ - -# FuseBox cache -.fusebox/ - -# DynamoDB Local files -.dynamodb/ diff --git a/templates/plugin/.taprc b/templates/plugin/.taprc deleted file mode 100644 index 31e63a78..00000000 --- a/templates/plugin/.taprc +++ /dev/null @@ -1,3 +0,0 @@ -ts: false -jsx: false -coverage: false diff --git a/templates/plugin/README.md b/templates/plugin/README.md deleted file mode 100644 index 00907c97..00000000 --- a/templates/plugin/README.md +++ /dev/null @@ -1,29 +0,0 @@ -# __MY_PLUGIN__ - -[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](http://standardjs.com/) ![CI workflow](__MY_PLUGIN_URL__ -/workflows/CI%20workflow/badge.svg) - -Supports Fastify versions `4.x` - -## Install -``` -npm i __MY_PLUGIN__ -``` - -## Usage -Require `__MY_PLUGIN__` and register. -```js -const fastify = require('fastify')() - -fastify.register(require('__MY_PLUGIN__'), { - // put your options here -}) - -fastify.listen({ port: 3000 }) -``` - -## Acknowledgements - -## License - -Licensed under [MIT](./LICENSE).
diff --git a/templates/plugin/index.d.ts b/templates/plugin/index.d.ts deleted file mode 100644 index 0063dc62..00000000 --- a/templates/plugin/index.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { FastifyPluginCallback } from 'fastify' - -declare module 'fastify' { - export interface FastifyInstance { - // This is an example decorator type added to fastify - exampleDecorator: () => string - } -} - -declare const example: FastifyPluginCallback<() => string> - -export { example } -export default example diff --git a/templates/plugin/index.js b/templates/plugin/index.js deleted file mode 100644 index 9fa66d95..00000000 --- a/templates/plugin/index.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict' - -const fp = require('fastify-plugin') - -module.exports = fp(async function (fastify, opts) { - fastify.decorate('exampleDecorator', () => { - return 'decorated' - }) -}, { fastify: '^4.x' }) diff --git a/templates/plugin/test/index.test-d.ts b/templates/plugin/test/index.test-d.ts deleted file mode 100644 index 72f3d6be..00000000 --- a/templates/plugin/test/index.test-d.ts +++ /dev/null @@ -1,13 +0,0 @@ -import fastify from 'fastify' -import example from '..' -import { expectType } from 'tsd' - -let app -try { - app = fastify() - void app.ready() - void app.register(example) - expectType<() => string>(app.exampleDecorator) -} catch (err) { - console.error(err) -} diff --git a/templates/plugin/test/index.test.js b/templates/plugin/test/index.test.js deleted file mode 100644 index f66166b5..00000000 --- a/templates/plugin/test/index.test.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict' - -const { test } = require('node:test') -const assert = require('node:assert') - -test('should register the correct decorator', async t => { - const app = require('fastify')() - - app.register(require('..')) - - await app.ready() - - assert.equal(app.exampleDecorator(), 'decorated') -}) diff --git a/templates/plugin/tsconfig.json b/templates/plugin/tsconfig.json deleted file mode 100644 index fb5c357a..00000000 --- a/templates/plugin/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "fastify-tsconfig", - "compilerOptions": { - "noEmit": true - }, - "include": [ - "**/*.ts" - ] -} diff --git a/templates/app/.vscode/launch.json b/templates/project/.vscode/launch.json similarity index 100% rename from templates/app/.vscode/launch.json rename to templates/project/.vscode/launch.json diff --git a/templates/app-ts-esm/README.md b/templates/project/README.md similarity index 100% rename from templates/app-ts-esm/README.md rename to templates/project/README.md diff --git a/templates/project/__gitignore b/templates/project/__gitignore new file mode 100644 index 00000000..f8fb0c79 --- /dev/null +++ b/templates/project/__gitignore @@ -0,0 +1,160 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# Vim swap files +*.swp + +# macOS files +.DS_Store + +# lock files +bun.lockb +package-lock.json +pnpm-lock.yaml +yarn.lock + +# editor files +.vscode +.idea + +# tap files +.tap/ + +# Optional compressed files (npm generated package, zip, etc) +/*.zip + +# 0x +profile-* + +# clinic +profile* +*clinic* +*flamegraph* diff --git a/templates/app/app.js b/templates/project/app.js similarity index 90% rename from templates/app/app.js rename to templates/project/app.js index d666bdce..13d69faf 100644 --- a/templates/app/app.js +++ b/templates/project/app.js @@ -16,14 +16,14 @@ module.exports = async function (fastify, opts) { // through your application fastify.register(AutoLoad, { dir: path.join(__dirname, 'plugins'), - options: Object.assign({}, opts) + options: Object.assign({}, opts), }) // This loads all plugins defined in routes // define your routes in one of these fastify.register(AutoLoad, { dir: path.join(__dirname, 'routes'), - options: Object.assign({}, opts) + options: Object.assign({}, opts), }) } diff --git a/templates/project/eslint.config.js.ejs b/templates/project/eslint.config.js.ejs new file mode 100644 index 00000000..a8610375 --- /dev/null +++ b/templates/project/eslint.config.js.ejs @@ -0,0 +1,26 @@ +<% if (lint === 'eslint') { %> +const js = require('@eslint/js') +const globals = require('globals') + +module.exports = [ + { + files: ["**/*.js"<%= language === 'TypeScript' ? ", '**/*.ts'" : "" %>], + languageOptions: { + globals: { + ...globals.node + } + }, + rules: js.configs.recommended.rules + } +] +<% } %> +<% if (lint === 'neostandard') { %> +const neostandard = require('neostandard') + +module.exports = [ + { + files: ['**/*.js'<%= language === 'TypeScript' ? ", '**/*.ts'" : "" %>], + }, + ...neostandard(<%= language === 'TypeScript' ? " ts: true " : "" %>), +] +<% } %> \ No newline at end of file diff --git a/templates/app-esm/plugins/README.md b/templates/project/plugins/README.md similarity index 100% rename from templates/app-esm/plugins/README.md rename to templates/project/plugins/README.md diff --git a/templates/app/plugins/sensible.js b/templates/project/plugins/sensible.js similarity index 91% rename from templates/app/plugins/sensible.js rename to templates/project/plugins/sensible.js index a8d912e2..7fd1f77c 100644 --- a/templates/app/plugins/sensible.js +++ b/templates/project/plugins/sensible.js @@ -9,6 +9,6 @@ const fp = require('fastify-plugin') */ module.exports = fp(async function (fastify, opts) { fastify.register(require('@fastify/sensible'), { - errorHandler: false + errorHandler: false, }) }) diff --git a/templates/app/plugins/support.js b/templates/project/plugins/support.js similarity index 100% rename from templates/app/plugins/support.js rename to templates/project/plugins/support.js diff --git a/templates/app/routes/README.md b/templates/project/routes/README.md similarity index 100% rename from templates/app/routes/README.md rename to templates/project/routes/README.md diff --git a/templates/app/routes/example/index.js b/templates/project/routes/example/index.js similarity index 100% rename from templates/app/routes/example/index.js rename to templates/project/routes/example/index.js diff --git a/templates/app/routes/root.js b/templates/project/routes/root.js similarity index 100% rename from templates/app/routes/root.js rename to templates/project/routes/root.js diff --git a/templates/app-ts/src/app.ts b/templates/project/src/app.ts similarity index 100% rename from templates/app-ts/src/app.ts rename to templates/project/src/app.ts diff --git a/templates/app-ts-esm/src/plugins/README.md b/templates/project/src/plugins/README.md similarity index 100% rename from templates/app-ts-esm/src/plugins/README.md rename to templates/project/src/plugins/README.md diff --git a/templates/app-ts-esm/src/plugins/sensible.ts b/templates/project/src/plugins/sensible.ts similarity index 100% rename from templates/app-ts-esm/src/plugins/sensible.ts rename to templates/project/src/plugins/sensible.ts diff --git a/templates/app-ts-esm/src/plugins/support.ts b/templates/project/src/plugins/support.ts similarity index 100% rename from templates/app-ts-esm/src/plugins/support.ts rename to templates/project/src/plugins/support.ts diff --git a/templates/app-ts-esm/src/routes/README.md b/templates/project/src/routes/README.md similarity index 100% rename from templates/app-ts-esm/src/routes/README.md rename to templates/project/src/routes/README.md diff --git a/templates/app-ts-esm/src/routes/example/index.ts b/templates/project/src/routes/example/index.ts similarity index 100% rename from templates/app-ts-esm/src/routes/example/index.ts rename to templates/project/src/routes/example/index.ts diff --git a/templates/app-ts-esm/src/routes/root.ts b/templates/project/src/routes/root.ts similarity index 100% rename from templates/app-ts-esm/src/routes/root.ts rename to templates/project/src/routes/root.ts diff --git a/templates/project/test/helper.js b/templates/project/test/helper.js new file mode 100644 index 00000000..d4a5b7f1 --- /dev/null +++ b/templates/project/test/helper.js @@ -0,0 +1,30 @@ +'use strict' + +// This file contains code that we reuse +// between our tests. +const path = require('node:path') +const Fastify = require('fastify') + +const AppPath = path.join(__dirname, '..', 'app.js') + +// automatically build and tear down our instance +async function build (t) { + // fastify options + const options = {} + + const app = Fastify(options) + const entry = require(AppPath) + + app.register(entry, entry.options) + + await app.ready() + + // close the app after we are done + t.after(() => app.close()) + + return app +} + +module.exports = { + build, +} diff --git a/templates/project/test/helper.ts b/templates/project/test/helper.ts new file mode 100644 index 00000000..fb2adfef --- /dev/null +++ b/templates/project/test/helper.ts @@ -0,0 +1,32 @@ +// This file contains code that we reuse between our tests. +import Fastify from 'fastify' +import type * as test from 'node:test' +import * as path from 'path' + +export type TestContext = { + after: typeof test.after +} + +const AppPath = path.join(__dirname, '..', 'src', 'app.ts') + +// Automatically build and tear down our instance +async function build (t: TestContext) { + // fastify options + const options = {} + + const app = Fastify(options) + const entry = await import(AppPath) + + app.register(entry, entry.options) + + await app.ready() + + // Tear down our app after we are done + t.after(() => app.close()) + + return app +} + +export { + build, +} diff --git a/templates/app/test/plugins/support.test.js b/templates/project/test/plugins/support.test.js similarity index 100% rename from templates/app/test/plugins/support.test.js rename to templates/project/test/plugins/support.test.js diff --git a/templates/app-ts/test/plugins/support.test.ts b/templates/project/test/plugins/support.test.ts similarity index 100% rename from templates/app-ts/test/plugins/support.test.ts rename to templates/project/test/plugins/support.test.ts diff --git a/templates/app/test/routes/example.test.js b/templates/project/test/routes/example.test.js similarity index 96% rename from templates/app/test/routes/example.test.js rename to templates/project/test/routes/example.test.js index 4909a3a2..db1a7446 100644 --- a/templates/app/test/routes/example.test.js +++ b/templates/project/test/routes/example.test.js @@ -8,7 +8,7 @@ test('example is loaded', async (t) => { const app = await build(t) const res = await app.inject({ - url: '/example' + url: '/example', }) assert.equal(res.payload, 'this is an example') }) diff --git a/templates/app-ts/test/routes/example.test.ts b/templates/project/test/routes/example.test.ts similarity index 92% rename from templates/app-ts/test/routes/example.test.ts rename to templates/project/test/routes/example.test.ts index 0c35dfef..2aa7ea26 100644 --- a/templates/app-ts/test/routes/example.test.ts +++ b/templates/project/test/routes/example.test.ts @@ -1,12 +1,12 @@ -import { test } from 'node:test' import * as assert from 'node:assert' +import { test } from 'node:test' import { build } from '../helper' test('example is loaded', async (t) => { const app = await build(t) const res = await app.inject({ - url: '/example' + url: '/example', }) assert.equal(res.payload, 'this is an example') diff --git a/templates/app/test/routes/root.test.js b/templates/project/test/routes/root.test.js similarity index 97% rename from templates/app/test/routes/root.test.js rename to templates/project/test/routes/root.test.js index 6fac9fc1..10f51e32 100644 --- a/templates/app/test/routes/root.test.js +++ b/templates/project/test/routes/root.test.js @@ -8,7 +8,7 @@ test('default root route', async (t) => { const app = await build(t) const res = await app.inject({ - url: '/' + url: '/', }) assert.deepStrictEqual(JSON.parse(res.payload), { root: true }) }) diff --git a/templates/app-ts/test/routes/root.test.ts b/templates/project/test/routes/root.test.ts similarity index 95% rename from templates/app-ts/test/routes/root.test.ts rename to templates/project/test/routes/root.test.ts index 17554ecc..57f10d2a 100644 --- a/templates/app-ts/test/routes/root.test.ts +++ b/templates/project/test/routes/root.test.ts @@ -1,12 +1,12 @@ -import { test } from 'node:test' import * as assert from 'node:assert' +import { test } from 'node:test' import { build } from '../helper' test('default root route', async (t) => { const app = await build(t) const res = await app.inject({ - url: '/' + url: '/', }) assert.deepStrictEqual(JSON.parse(res.payload), { root: true }) }) diff --git a/templates/project/tsconfig.build.json b/templates/project/tsconfig.build.json new file mode 100644 index 00000000..86cebd9a --- /dev/null +++ b/templates/project/tsconfig.build.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "lib": ["ESNext"], + "module": "CommonJS", + "target": "ES2018", + "moduleResolution": "Node", + "resolveJsonModule": true, + "removeComments": true, + "preserveConstEnums": true, + "sourceMap": true, + "declaration": true, + "strict": true, + "noImplicitAny": true, + "noImplicitThis": true, + "skipLibCheck": true, + "esModuleInterop": true, + "rootDir": "src", + "outDir": "dist" + }, + "include": ["src/**/*"], + "exclude": ["node_modules"] +} diff --git a/templates/project/tsconfig.json b/templates/project/tsconfig.json new file mode 100644 index 00000000..59d08e0d --- /dev/null +++ b/templates/project/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "lib": ["ESNext"], + "module": "CommonJS", + "target": "ES2018", + "moduleResolution": "Node", + "resolveJsonModule": true, + "removeComments": true, + "preserveConstEnums": true, + "sourceMap": true, + "declaration": true, + "strict": true, + "noImplicitAny": true, + "noImplicitThis": true, + "skipLibCheck": true, + "esModuleInterop": true + }, + "include": ["src/**/*", "test/**/*"], + "exclude": ["node_modules"] +} diff --git a/templates/readme/README.md b/templates/readme/README.md deleted file mode 100644 index f15c7b73..00000000 --- a/templates/readme/README.md +++ /dev/null @@ -1,52 +0,0 @@ -# __packageName__ - -Short description of the purpose of your plugin. - -## Install - -``` -npm i __packageName__ -yarn add __packageName__ -``` - -## Example - -```js -const fastify = require('fastify')() -fastify.register(require('__packageName__')) -fastify.listen({ port: 3000 }) -``` - -You can also start any Fastify plugin with the [Fastify-cli](https://github.com/fastify/fastify-cli): - -``` -fastify start __pluginFileName__ -``` - -## Plugin - -### Accessibility - - -__accessibilityTemplate__ - -### Decorators - - -#### Fastify - - -__fastifyDecorators__ - -#### Reply - - -__replyDecorators__ - -## Dependencies - -__pluginDeps__ - -## Compatible Fastify version - -__minFastify__ diff --git a/test/args.test.js b/test/args.test.js deleted file mode 100644 index 5d024a83..00000000 --- a/test/args.test.js +++ /dev/null @@ -1,371 +0,0 @@ -'use strict' - -const t = require('tap') -const test = t.test -const parseArgs = require('../args') - -test('should parse args correctly', t => { - t.plan(1) - - const argv = [ - '--port', '7777', - '--address', 'fastify.dev:9999', - '--socket', 'fastify.dev.socket:9999', - '--require', './require-module.js', - '--import', './import-module.js', - '--log-level', 'info', - '--pretty-logs', 'true', - '--watch', 'true', - '--ignore-watch', 'ignoreme.js', - '--verbose-watch', 'true', - '--options', 'true', - '--prefix', 'FASTIFY_', - '--plugin-timeout', '500', - '--close-grace-delay', '30000', - '--body-limit', '5242880', - '--debug', 'true', - '--debug-port', 1111, - '--debug-host', '1.1.1.1', - '--logging-module', './custom-logger.js', - 'app.js' - ] - const parsedArgs = parseArgs(argv) - - t.strictSame(parsedArgs, { - _: ['app.js'], - '--': [], - prettyLogs: true, - options: true, - watch: true, - ignoreWatch: 'node_modules build dist .git bower_components logs .swp .nyc_output ignoreme.js', - verboseWatch: true, - port: 7777, - address: 'fastify.dev:9999', - socket: 'fastify.dev.socket:9999', - require: './require-module.js', - import: './import-module.js', - logLevel: 'info', - prefix: 'FASTIFY_', - pluginTimeout: 500, - closeGraceDelay: 30000, - pluginOptions: {}, - bodyLimit: 5242880, - debug: true, - debugPort: 1111, - debugHost: '1.1.1.1', - loggingModule: './custom-logger.js', - lang: 'js', - method: undefined, - commonPrefix: false, - includeHooks: undefined - }) -}) - -test('should parse args with = assignment correctly', t => { - t.plan(1) - - const argv = [ - '--port=7777', - '--address=fastify.dev:9999', - '--socket=fastify.dev.socket:9999', - '--require', './require-module.js', - '--import', './import-module.js', - '--log-level=info', - '--pretty-logs=true', - '--watch=true', - '--ignore-watch=ignoreme.js', - '--verbose-watch=true', - '--options=true', - '--prefix=FASTIFY_', - '--plugin-timeout=500', - '--close-grace-delay=30000', - '--body-limit=5242880', - '--debug=true', - '--debug-port', 1111, - '--debug-host', '1.1.1.1', - '--logging-module', './custom-logger.js', - 'app.js' - ] - const parsedArgs = parseArgs(argv) - - t.strictSame(parsedArgs, { - _: ['app.js'], - '--': [], - prettyLogs: true, - options: true, - watch: true, - ignoreWatch: 'node_modules build dist .git bower_components logs .swp .nyc_output ignoreme.js', - verboseWatch: true, - port: 7777, - address: 'fastify.dev:9999', - socket: 'fastify.dev.socket:9999', - require: './require-module.js', - import: './import-module.js', - logLevel: 'info', - prefix: 'FASTIFY_', - pluginTimeout: 500, - closeGraceDelay: 30000, - pluginOptions: {}, - bodyLimit: 5242880, - debug: true, - debugPort: 1111, - debugHost: '1.1.1.1', - loggingModule: './custom-logger.js', - lang: 'js', - method: undefined, - commonPrefix: false, - includeHooks: undefined - }) -}) - -test('should parse env vars correctly', t => { - t.plan(1) - - process.env.FASTIFY_PORT = '7777' - process.env.FASTIFY_ADDRESS = 'fastify.dev:9999' - process.env.FASTIFY_SOCKET = 'fastify.dev.socket:9999' - process.env.FASTIFY_REQUIRE = './require-module.js' - process.env.FASTIFY_IMPORT = './import-module.js' - process.env.FASTIFY_LOG_LEVEL = 'info' - process.env.FASTIFY_PRETTY_LOGS = 'true' - process.env.FASTIFY_WATCH = 'true' - process.env.FASTIFY_IGNORE_WATCH = 'ignoreme.js' - process.env.FASTIFY_VERBOSE_WATCH = 'true' - process.env.FASTIFY_OPTIONS = 'true' - process.env.FASTIFY_PREFIX = 'FASTIFY_' - process.env.FASTIFY_BODY_LIMIT = '5242880' - process.env.FASTIFY_PLUGIN_TIMEOUT = '500' - process.env.FASTIFY_CLOSE_GRACE_DELAY = '30000' - process.env.FASTIFY_DEBUG = 'true' - process.env.FASTIFY_DEBUG_PORT = '1111' - process.env.FASTIFY_DEBUG_HOST = '1.1.1.1' - process.env.FASTIFY_LOGGING_MODULE = './custom-logger.js' - - t.teardown(function () { - delete process.env.FASTIFY_PORT - delete process.env.FASTIFY_ADDRESS - delete process.env.FASTIFY_SOCKET - delete process.env.FASTIFY_REQUIRE - delete process.env.FASTIFY_IMPORT - delete process.env.FASTIFY_LOG_LEVEL - delete process.env.FASTIFY_PRETTY_LOGS - delete process.env.FASTIFY_WATCH - delete process.env.FASTIFY_IGNORE_WATCH - delete process.env.FASTIFY_VERBOSE_WATCH - delete process.env.FASTIFY_OPTIONS - delete process.env.FASTIFY_PREFIX - delete process.env.FASTIFY_BODY_LIMIT - delete process.env.FASTIFY_PLUGIN_TIMEOUT - delete process.env.FASTIFY_CLOSE_GRACE_DELAY - delete process.env.FASTIFY_DEBUG - delete process.env.FASTIFY_DEBUG_PORT - delete process.env.FASTIFY_LOGGING_MODULE - }) - - const parsedArgs = parseArgs([]) - - t.strictSame(parsedArgs, { - _: [], - '--': [], - prettyLogs: true, - options: true, - watch: true, - ignoreWatch: 'node_modules build dist .git bower_components logs .swp .nyc_output ignoreme.js', - verboseWatch: true, - address: 'fastify.dev:9999', - bodyLimit: 5242880, - logLevel: 'info', - port: 7777, - prefix: 'FASTIFY_', - socket: 'fastify.dev.socket:9999', - require: './require-module.js', - import: './import-module.js', - pluginTimeout: 500, - closeGraceDelay: 30000, - pluginOptions: {}, - debug: true, - debugPort: 1111, - debugHost: '1.1.1.1', - loggingModule: './custom-logger.js', - lang: 'js', - method: undefined, - commonPrefix: false, - includeHooks: undefined - }) -}) - -test('should respect default values', t => { - t.plan(14) - - const argv = [ - 'app.js' - ] - - const parsedArgs = parseArgs(argv) - - t.equal(parsedArgs._[0], 'app.js') - t.equal(parsedArgs.options, false) - t.equal(parsedArgs.prettyLogs, false) - t.equal(parsedArgs.watch, false) - t.equal(parsedArgs.ignoreWatch, 'node_modules build dist .git bower_components logs .swp .nyc_output') - t.equal(parsedArgs.verboseWatch, false) - t.equal(parsedArgs.logLevel, 'fatal') - t.equal(parsedArgs.pluginTimeout, 10000) - t.equal(parsedArgs.closeGraceDelay, 500) - t.equal(parsedArgs.debug, false) - t.equal(parsedArgs.debugPort, 9320) - t.equal(parsedArgs.loggingModule, undefined) - t.equal(parsedArgs.require, undefined) - t.equal(parsedArgs.import, undefined) -}) - -test('should parse custom plugin options', t => { - t.plan(1) - - const argv = [ - '--port', '7777', - '--address', 'fastify.dev:9999', - '--socket', 'fastify.dev.socket:9999', - '--require', './require-module.js', - '--import', './import-module.js', - '--log-level', 'info', - '--pretty-logs', 'true', - '--watch', 'true', - '--ignore-watch', 'ignoreme.js', - '--verbose-watch', 'true', - '--options', 'true', - '--prefix', 'FASTIFY_', - '--plugin-timeout', '500', - '--close-grace-delay', '30000', - '--body-limit', '5242880', - '--debug', 'true', - '--debug-port', 1111, - '--debug-host', '1.1.1.1', - '--logging-module', './custom-logger.js', - 'app.js', - '--', - '-abc', - '--hello', 'world' - ] - const parsedArgs = parseArgs(argv) - - t.strictSame(parsedArgs, { - _: ['app.js'], - '--': [ - '-abc', - '--hello', - 'world' - ], - prettyLogs: true, - options: true, - watch: true, - ignoreWatch: 'node_modules build dist .git bower_components logs .swp .nyc_output ignoreme.js', - verboseWatch: true, - port: 7777, - address: 'fastify.dev:9999', - socket: 'fastify.dev.socket:9999', - require: './require-module.js', - import: './import-module.js', - logLevel: 'info', - prefix: 'FASTIFY_', - pluginTimeout: 500, - closeGraceDelay: 30000, - pluginOptions: { - a: true, - b: true, - c: true, - hello: 'world' - }, - bodyLimit: 5242880, - debug: true, - debugPort: 1111, - debugHost: '1.1.1.1', - loggingModule: './custom-logger.js', - lang: 'js', - method: undefined, - commonPrefix: false, - includeHooks: undefined - }) -}) - -test('should parse config file correctly and prefer config values over default ones', t => { - t.plan(1) - - const argv = [ - '--config', './test/data/custom-config.js', - 'app.js' - ] - const parsedArgs = parseArgs(argv) - - t.strictSame(parsedArgs, { - _: ['app.js'], - '--': [], - port: 5000, - bodyLimit: undefined, - pluginTimeout: 9000, - closeGraceDelay: 1000, - pluginOptions: {}, - prettyLogs: true, - options: false, - watch: true, - debug: false, - debugPort: 4000, - debugHost: '1.1.1.1', - ignoreWatch: 'node_modules build dist .git bower_components logs .swp .nyc_output', - verboseWatch: false, - logLevel: 'fatal', - address: 'fastify.dev:9999', - socket: undefined, - require: undefined, - import: undefined, - prefix: 'FASTIFY_', - loggingModule: undefined, - lang: 'js', - method: undefined, - commonPrefix: false, - includeHooks: undefined - }) -}) - -test('should prefer command line args over config file options', t => { - t.plan(1) - - const argv = [ - '--config', './test/data/custom-config.js', - '--port', '4000', - '--debugPort', '9320', - '--plugin-timeout', '10000', - '--close-grace-delay', '30000', - 'app.js' - ] - const parsedArgs = parseArgs(argv) - - t.strictSame(parsedArgs, { - _: ['app.js'], - '--': [], - port: 4000, - bodyLimit: undefined, - pluginTimeout: 10000, - closeGraceDelay: 30000, - pluginOptions: {}, - prettyLogs: true, - options: false, - watch: true, - debug: false, - debugPort: 9320, - debugHost: '1.1.1.1', - ignoreWatch: 'node_modules build dist .git bower_components logs .swp .nyc_output', - verboseWatch: false, - logLevel: 'fatal', - address: 'fastify.dev:9999', - socket: undefined, - require: undefined, - import: undefined, - prefix: 'FASTIFY_', - loggingModule: undefined, - lang: 'js', - method: undefined, - commonPrefix: false, - includeHooks: undefined - }) -}) diff --git a/test/cli.test.js b/test/cli.test.js deleted file mode 100644 index d13ccc7a..00000000 --- a/test/cli.test.js +++ /dev/null @@ -1,38 +0,0 @@ -'use strict' - -const t = require('tap') -const { execSync } = require('node:child_process') -const { mkdirSync, readFileSync } = require('node:fs') -const path = require('node:path') -const rimraf = require('rimraf') - -t.test('generate', async (t) => { - const workdir = path.join(__dirname, 'workdir') - const target = path.join(workdir, 'cli.test') - - rimraf.sync(workdir) - mkdirSync(workdir, { recursive: true }) - - execSync(`node cli.js generate ${target}`) -}) - -t.test('help', async t => { - t.equal( - execSync('node cli.js', { encoding: 'utf-8' }), - readFileSync(path.join(__dirname, '../help/help.txt'), 'utf-8') - ) -}) - -t.test('--help', async t => { - t.equal( - execSync('node cli.js --help', { encoding: 'utf-8' }), - readFileSync(path.join(__dirname, '../help/help.txt'), 'utf-8') - ) -}) - -t.test('generate --help', async t => { - t.equal( - execSync('node cli.js generate --help', { encoding: 'utf-8' }), - readFileSync(path.join(__dirname, '../help/generate.txt'), 'utf-8') - ) -}) diff --git a/test/configs/ts-cjs.tsconfig.json b/test/configs/ts-cjs.tsconfig.json deleted file mode 100644 index 5e0e7973..00000000 --- a/test/configs/ts-cjs.tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../node_modules/fastify-tsconfig/tsconfig.json", - "compilerOptions": { - "outDir": "dist", - "sourceMap": true - }, - "include": ["src/**/*.ts"] -} diff --git a/test/configs/ts-esm.tsconfig.json b/test/configs/ts-esm.tsconfig.json deleted file mode 100644 index b52b3ee8..00000000 --- a/test/configs/ts-esm.tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "../../node_modules/fastify-tsconfig/tsconfig.json", - "compilerOptions": { - "outDir": "dist", - "sourceMap": true, - "moduleResolution": "NodeNext", - "module": "NodeNext", - "target": "ES2022", - "esModuleInterop": true - }, - "include": ["src/**/*.ts"] -} diff --git a/test/data/async-plugin-with-one-argument.js b/test/data/async-plugin-with-one-argument.js deleted file mode 100644 index 57a95ce1..00000000 --- a/test/data/async-plugin-with-one-argument.js +++ /dev/null @@ -1,6 +0,0 @@ -'use strict' - -// This is a counter example. WILL NOT WORK! -// we are creating an async plugin with only one argument -module.exports = async function (fastify) { -} diff --git a/test/data/custom-config.js b/test/data/custom-config.js deleted file mode 100644 index 744e471c..00000000 --- a/test/data/custom-config.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict' - -module.exports = { - port: 5000, - address: 'fastify.dev:9999', - prefix: 'FASTIFY_', - watch: true, - prettyLogs: true, - debugPort: 4000, - pluginTimeout: 9 * 1000, - closeGraceDelay: 1000 -} diff --git a/test/data/custom-import.mjs b/test/data/custom-import.mjs deleted file mode 100644 index 9dd35c4c..00000000 --- a/test/data/custom-import.mjs +++ /dev/null @@ -1,3 +0,0 @@ -/* global GLOBAL_MODULE_3 */ -globalThis.GLOBAL_MODULE_3 = true // eslint-disable-line -console.log('this is module to be preloaded that sets GLOBAL_MODULE_3=', GLOBAL_MODULE_3) diff --git a/test/data/custom-import2.mjs b/test/data/custom-import2.mjs deleted file mode 100644 index e3e2a2dd..00000000 --- a/test/data/custom-import2.mjs +++ /dev/null @@ -1,3 +0,0 @@ -/* global GLOBAL_MODULE_4 */ -globalThis.GLOBAL_MODULE_4 = true // eslint-disable-line -console.log('this is module to be preloaded that sets GLOBAL_MODULE_4=', GLOBAL_MODULE_4) diff --git a/test/data/custom-logger.js b/test/data/custom-logger.js deleted file mode 100644 index cfd70093..00000000 --- a/test/data/custom-logger.js +++ /dev/null @@ -1,8 +0,0 @@ -'use strict' - -module.exports = { - name: 'Custom Logger', - customLevels: { - test: 99 - } -} diff --git a/test/data/custom-require.js b/test/data/custom-require.js deleted file mode 100644 index 3fa1a938..00000000 --- a/test/data/custom-require.js +++ /dev/null @@ -1,3 +0,0 @@ -/* global GLOBAL_MODULE_1 */ -GLOBAL_MODULE_1 = true // eslint-disable-line -console.log('this is module to be preloaded that sets GLOBAL_MODULE_1=', GLOBAL_MODULE_1) diff --git a/test/data/custom-require2.js b/test/data/custom-require2.js deleted file mode 100644 index e92e5f6f..00000000 --- a/test/data/custom-require2.js +++ /dev/null @@ -1,3 +0,0 @@ -/* global GLOBAL_MODULE_2 */ -GLOBAL_MODULE_2 = true // eslint-disable-line -console.log('this is module to be preloaded that sets GLOBAL_MODULE_2=', GLOBAL_MODULE_2) diff --git a/test/data/object.js b/test/data/object.js deleted file mode 100644 index 545992eb..00000000 --- a/test/data/object.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict' - -module.exports = {} diff --git a/test/data/package-not-found.js b/test/data/package-not-found.js deleted file mode 100644 index 6563c58b..00000000 --- a/test/data/package-not-found.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict' - -require('unknown-package') diff --git a/test/data/parsing-error.js b/test/data/parsing-error.js deleted file mode 100644 index eef843b5..00000000 --- a/test/data/parsing-error.js +++ /dev/null @@ -1 +0,0 @@ -' diff --git a/test/data/rejection.js b/test/data/rejection.js deleted file mode 100644 index 8c6e4a24..00000000 --- a/test/data/rejection.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict' - -// This is a counter example. WILL NOT WORK! -// we are creating an unhandledRejection on purpose -module.exports = function (fastify, opts, next) { - Promise.reject(new Error('there is no catch')) -} diff --git a/test/data/timeout-plugin.js b/test/data/timeout-plugin.js deleted file mode 100644 index fd15a61a..00000000 --- a/test/data/timeout-plugin.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict' - -// This is a counter example. WILL NOT WORK! -// the next function is not called -module.exports = function (fastify, opts, next) { - fastify.get('/', function (req, reply) { - reply.send({ wont: 'work' }) - }) - // next() not called on purpose -} diff --git a/test/data/undefinedVariable.js b/test/data/undefinedVariable.js deleted file mode 100644 index 1e2e4575..00000000 --- a/test/data/undefinedVariable.js +++ /dev/null @@ -1 +0,0 @@ -undefinedVariable.pippo.pluto.paperino = 5 diff --git a/test/eject-esm.test.js b/test/eject-esm.test.js deleted file mode 100644 index 2f8f1161..00000000 --- a/test/eject-esm.test.js +++ /dev/null @@ -1,107 +0,0 @@ -'use strict' - -// bailout if a test is broken -// so that the folder can be inspected -process.env.TAP_BAIL = true - -const t = require('tap') -const { - mkdirSync, - readFileSync, - readFile -} = require('node:fs') -const path = require('node:path') -const rimraf = require('rimraf') -const walker = require('walker') -const workdir = path.join(__dirname, 'workdir') -const appTemplateDir = path.join(__dirname, '..', 'templates', 'eject-esm') -const { eject, cli } = require('../eject') -const expected = {}; - -(function (cb) { - const files = [] - walker(appTemplateDir) - .on('file', function (file) { - files.push(file) - }) - .on('end', function () { - let count = 0 - files.forEach(function (file) { - readFile(file, function (err, data) { - if (err) { - return cb(err) - } - - expected[ - file.replace(appTemplateDir, '').replace(/__/, '.') - ] = data.toString() - - count++ - if (count === files.length) { - cb(null) - } - }) - }) - }) - .on('error', cb) -})(function (err) { - t.error(err) - define(t) -}) - -function define (t) { - const { beforeEach, test } = t - - beforeEach(() => { - rimraf.sync(workdir) - mkdirSync(workdir, { recursive: true }) - }) - - test('should finish succesfully with template', async (t) => { - try { - const template = 'eject-esm' - await eject(workdir, template) - await verifyCopy(t, expected) - } catch (err) { - t.error(err) - } - }) - - test('should finish successfully with cli', async (t) => { - try { - process.chdir(workdir) - await cli(['--esm']) - await verifyCopy(t, expected) - } catch (err) { - t.error(err) - } - }) - - function verifyCopy (t, expected) { - return new Promise((resolve, reject) => { - let count = 0 - walker(workdir) - .on('file', function (file) { - count++ - try { - const data = readFileSync(file) - file = file.replace(workdir, '') - t.same( - data.toString().replace(/\r\n/g, '\n'), - expected[file], - file + ' matching' - ) - } catch (err) { - reject(err) - } - }) - .on('end', function () { - t.equal(Object.keys(expected).length, count) - resolve() - }) - .on('error', function (err, entry, stat) { - reject(err) - }) - }) - } -} diff --git a/test/eject-ts.test.js b/test/eject-ts.test.js deleted file mode 100644 index 5d3a7a70..00000000 --- a/test/eject-ts.test.js +++ /dev/null @@ -1,103 +0,0 @@ -'use strict' - -// bailout if a test is broken -// so that the folder can be inspected -process.env.TAP_BAIL = true - -const t = require('tap') -const { mkdirSync, readFileSync, readFile } = require('node:fs') -const path = require('node:path') -const rimraf = require('rimraf') -const walker = require('walker') -const workdir = path.join(__dirname, 'workdir') -const appTemplateDir = path.join(__dirname, '..', 'templates', 'eject-ts') -const { eject, cli } = require('../eject') -const expected = {}; - -(function (cb) { - const files = [] - walker(appTemplateDir) - .on('file', function (file) { - files.push(file) - }) - .on('end', function () { - let count = 0 - files.forEach(function (file) { - readFile(file, function (err, data) { - if (err) { - return cb(err) - } - - expected[ - file.replace(appTemplateDir, '').replace(/__/, '.') - ] = data.toString() - - count++ - if (count === files.length) { - cb(null) - } - }) - }) - }) - .on('error', cb) -})(function (err) { - t.error(err) - define(t) -}) - -function define (t) { - const { beforeEach, test } = t - - beforeEach(() => { - rimraf.sync(workdir) - mkdirSync(workdir, { recursive: true }) - }) - - test('should finish succesfully with template', async (t) => { - try { - const template = 'eject-ts' - await eject(workdir, template) - await verifyCopy(t, expected) - } catch (err) { - t.error(err) - } - }) - - test('should finish successfully with cli', async (t) => { - try { - process.chdir(workdir) - await cli(['--lang=typescript']) - await verifyCopy(t, expected) - } catch (err) { - t.error(err) - } - }) - - function verifyCopy (t, expected) { - return new Promise((resolve, reject) => { - let count = 0 - walker(workdir) - .on('file', function (file) { - count++ - try { - const data = readFileSync(file) - file = file.replace(workdir, '') - t.same( - data.toString().replace(/\r\n/g, '\n'), - expected[file], - file + ' matching' - ) - } catch (err) { - reject(err) - } - }) - .on('end', function () { - t.equal(Object.keys(expected).length, count) - resolve() - }) - .on('error', function (err, entry, stat) { - reject(err) - }) - }) - } -} diff --git a/test/eject.test.js b/test/eject.test.js deleted file mode 100644 index eee32f72..00000000 --- a/test/eject.test.js +++ /dev/null @@ -1,101 +0,0 @@ -'use strict' - -// bailout if a test is broken -// so that the folder can be inspected -process.env.TAP_BAIL = true - -const t = require('tap') -const { - mkdirSync, - readFileSync, - readFile -} = require('node:fs') -const path = require('node:path') -const rimraf = require('rimraf') -const walker = require('walker') -const workdir = path.join(__dirname, 'workdir') -const appTemplateDir = path.join(__dirname, '..', 'templates', 'eject') -const { eject, cli } = require('../eject') -const expected = {} - -;(function (cb) { - const files = [] - walker(appTemplateDir) - .on('file', function (file) { - files.push(file) - }) - .on('end', function () { - let count = 0 - files.forEach(function (file) { - readFile(file, function (err, data) { - if (err) { - return cb(err) - } - - expected[file.replace(appTemplateDir, '').replace(/__/, '.')] = data.toString() - - count++ - if (count === files.length) { - cb(null) - } - }) - }) - }) - .on('error', cb) -})(function (err) { - t.error(err) - define(t) -}) - -function define (t) { - const { beforeEach, test } = t - - beforeEach(() => { - rimraf.sync(workdir) - mkdirSync(workdir, { recursive: true }) - }) - - test('should finish succesfully with template', async (t) => { - try { - const template = 'eject' - await eject(workdir, template) - await verifyCopy(t, expected) - } catch (err) { - t.error(err) - } - }) - - test('should finish successfully with cli', async (t) => { - try { - process.chdir(workdir) - await cli([]) - await verifyCopy(t, expected) - } catch (err) { - t.error(err) - } - }) - - function verifyCopy (t, expected) { - return new Promise((resolve, reject) => { - let count = 0 - walker(workdir) - .on('file', function (file) { - count++ - try { - const data = readFileSync(file) - file = file.replace(workdir, '') - t.same(data.toString().replace(/\r\n/g, '\n'), expected[file], file + ' matching') - } catch (err) { - reject(err) - } - }) - .on('end', function () { - t.equal(Object.keys(expected).length, count) - resolve() - }) - .on('error', function (err, entry, stat) { - reject(err) - }) - }) - } -} diff --git a/test/esm/data/custom-logger.cjs b/test/esm/data/custom-logger.cjs deleted file mode 100644 index cfd70093..00000000 --- a/test/esm/data/custom-logger.cjs +++ /dev/null @@ -1,8 +0,0 @@ -'use strict' - -module.exports = { - name: 'Custom Logger', - customLevels: { - test: 99 - } -} diff --git a/test/esm/package.json b/test/esm/package.json deleted file mode 100644 index 47200257..00000000 --- a/test/esm/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "type": "module" -} diff --git a/test/esm/util.test.js b/test/esm/util.test.js deleted file mode 100644 index 33472c9b..00000000 --- a/test/esm/util.test.js +++ /dev/null @@ -1,16 +0,0 @@ -import { requireModule } from '../../util.js' -import { resolve, join } from 'node:path' -import t from 'tap' -import * as url from 'url' - -const test = t.test - -const __dirname = url.fileURLToPath(new URL('.', import.meta.url)) - -test('requiring a commonjs module works in an esm project', (t) => { - t.plan(1) - const module = requireModule( - resolve(join(__dirname, './data/custom-logger.cjs')) - ) - t.strictSame(module, { name: 'Custom Logger', customLevels: { test: 99 } }) -}) diff --git a/test/fixtures/.keep b/test/fixtures/.keep deleted file mode 100644 index e69de29b..00000000 diff --git a/test/generate-esm.test.js b/test/generate-esm.test.js deleted file mode 100644 index 221d3ea8..00000000 --- a/test/generate-esm.test.js +++ /dev/null @@ -1,210 +0,0 @@ -'use strict' - -// bailout if a test is broken -// so that the folder can be inspected -process.env.TAP_BAIL = true - -const t = require('tap') -const { - mkdirSync, - readFileSync, - readFile, - promises: fsPromises, - unlink -} = require('node:fs') -const path = require('node:path') -const { promisify } = require('node:util') -const rimraf = require('rimraf') -const walker = require('walker') -const { generate, javascriptTemplate } = require('../generate') -const workdir = path.join(__dirname, 'workdir') -const appTemplateDir = path.join(__dirname, '..', 'templates', 'app-esm') -const cliPkg = require('../package') -const { exec, execSync } = require('node:child_process') -const pExec = promisify(exec) -const pUnlink = promisify(unlink) -const minimatch = require('minimatch') -const strip = require('strip-ansi') -const expected = {} -const initVersion = execSync('npm get init-version').toString().trim() - -javascriptTemplate.dir = 'app-esm' -javascriptTemplate.type = 'module' -javascriptTemplate.devDependencies.c8 = cliPkg.devDependencies.c8 -javascriptTemplate.scripts.test = 'node --test test/**/*.test.js' - -;(function (cb) { - const files = [] - walker(appTemplateDir) - .on('file', function (file) { - files.push(file) - }) - .on('end', function () { - let count = 0 - files.forEach(function (file) { - readFile(file, function (err, data) { - if (err) { - return cb(err) - } - - expected[file.replace(appTemplateDir, '').replace(/__/, '.')] = data.toString() - - count++ - if (count === files.length) { - cb(null) - } - }) - }) - }) - .on('error', cb) -})(function (err) { - t.error(err) - define(t) -}) - -function define (t) { - const { beforeEach, test } = t - - beforeEach(() => { - rimraf.sync(workdir) - mkdirSync(workdir, { recursive: true }) - }) - - test('errors if directory exists', (t) => { - t.plan(2) - exec('node generate.js ./test/workdir --esm', (err, stdout) => { - t.equal('directory ./test/workdir already exists', strip(stdout.toString().trim())) - t.equal(1, err.code) - }) - }) - - test('errors if generate doesn\'t have arguments', (t) => { - t.plan(2) - exec('node generate.js --esm', (err, stdout) => { - t.equal('must specify a directory to \'fastify generate\'', strip(stdout.toString().trim())) - t.equal(1, err.code) - }) - }) - - test('errors if package.json exists when use generate . and integrate flag is not set', (t) => { - t.plan(2) - exec('node generate.js . --esm', (err, stdout) => { - t.equal('a package.json file already exists in target directory. retry with the --integrate flag to proceed', strip(stdout.toString().trim())) - t.equal(1, err.code) - }) - }) - - test('errors if package.json exists when use generate ./ and integrate flag is not set', (t) => { - t.plan(2) - exec('node generate.js ./ --esm', (err, stdout) => { - t.equal('a package.json file already exists in target directory. retry with the --integrate flag to proceed', strip(stdout.toString().trim())) - t.equal(1, err.code) - }) - }) - - test('errors if folder exists', (t) => { - t.plan(2) - exec('node generate.js test --esm', (err, stdout) => { - t.equal('directory test already exists', strip(stdout.toString().trim())) - t.equal(1, err.code) - }) - }) - - test('should finish successfully with ESM javascript template', async (t) => { - t.plan(13 + Object.keys(expected).length) - try { - await generate(workdir, javascriptTemplate) - await verifyPkg(t) - await verifyCopy(t, expected) - } catch (err) { - t.error(err) - } - }) - - test('--integrate option will enhance preexisting package.json and overwrite preexisting files', async (t) => { - t.plan(13 + Object.keys(expected).length) - try { - await generate(workdir, javascriptTemplate) - await pUnlink(path.join(workdir, 'package.json')) - await pExec('npm init -y', { cwd: workdir }) - await pExec('node ../../generate . --integrate --esm', { cwd: workdir }) - await verifyPkg(t) - await verifyCopy(t, expected) - } catch (err) { - t.error(err) - } - }) - - test('--standardlint option will add standard lint dependencies and scripts to javascript template', async (t) => { - const dir = path.join(__dirname, 'workdir-with-lint') - const cwd = path.join(dir, '..') - const bin = path.join('..', 'generate') - rimraf.sync(dir) - await pExec(`node ${bin} ${dir} --standardlint --esm`, { cwd }) - - await verifyPkg(t, dir, 'workdir-with-lint') - - const data = await fsPromises.readFile(path.join(dir, 'package.json')) - const pkg = JSON.parse(data) - t.equal(pkg.scripts.pretest, 'standard') - t.equal(pkg.scripts.lint, 'standard --fix') - t.equal(pkg.devDependencies.standard, cliPkg.devDependencies.standard) - }) - - function verifyPkg (t, dir = workdir, pkgName = 'workdir') { - return new Promise((resolve, reject) => { - const pkgFile = path.join(dir, 'package.json') - - readFile(pkgFile, function (err, data) { - err && reject(err) - const pkg = JSON.parse(data) - t.equal(pkg.name, pkgName) - // we are not checking author because it depends on global npm configs - t.equal(pkg.version, initVersion) - t.equal(pkg.description, 'This project was bootstrapped with Fastify-CLI.') - // by default this will be ISC but since we have a MIT licensed pkg file in upper dir, npm will set the license to MIT in this case - // so for local tests we need to accept MIT as well - t.ok(pkg.license === 'ISC' || pkg.license === 'MIT') - t.equal(pkg.scripts.test, 'node --test test/**/*.test.js') - t.equal(pkg.scripts.start, 'fastify start -l info app.js') - t.equal(pkg.scripts.dev, 'fastify start -w -l info -P app.js') - t.equal(pkg.dependencies['fastify-cli'], '^' + cliPkg.version) - t.equal(pkg.dependencies.fastify, cliPkg.dependencies.fastify) - t.equal(pkg.dependencies['fastify-plugin'], cliPkg.devDependencies['fastify-plugin'] || cliPkg.dependencies['fastify-plugin']) - t.equal(pkg.dependencies['@fastify/autoload'], cliPkg.devDependencies['@fastify/autoload']) - t.equal(pkg.dependencies['@fastify/sensible'], cliPkg.devDependencies['@fastify/sensible']) - // Test for "type:module" - t.equal(pkg.type, 'module') - - const testGlob = pkg.scripts.test.split(' ', 3)[2].replace(/"/g, '') - t.equal(minimatch.match(['test/services/plugins/more/test/here/ok.test.js'], testGlob).length, 1) - resolve() - }) - }) - } - - function verifyCopy (t, expected) { - const pkgFile = path.join(workdir, 'package.json') - return new Promise((resolve, reject) => { - walker(workdir) - .on('file', function (file) { - if (file === pkgFile) { - return - } - try { - const data = readFileSync(file) - file = file.replace(workdir, '') - t.same(data.toString().replace(/\r\n/g, '\n'), expected[file], file + ' matching') - } catch (err) { - reject(err) - } - }) - .on('end', function () { - resolve() - }) - .on('error', function (err, entry, stat) { - reject(err) - }) - }) - } -} diff --git a/test/generate-plugin.test.js b/test/generate-plugin.test.js deleted file mode 100644 index 5b521131..00000000 --- a/test/generate-plugin.test.js +++ /dev/null @@ -1,170 +0,0 @@ -'use strict' - -// bailout if a test is broken -// so that the folder can be inspected -process.env.TAP_BAIL = true - -const t = require('tap') -const { - mkdirSync, - readFileSync, - readFile -} = require('node:fs') -const path = require('node:path') -const rimraf = require('rimraf') -const walker = require('walker') -const { generate, pluginTemplate } = require('../generate-plugin') -const workdir = path.join(__dirname, 'workdir') -const templateDir = path.join(__dirname, '..', 'templates', 'plugin') -const cliPkg = require('../package') -const { exec, execSync } = require('node:child_process') -const strip = require('strip-ansi') -const expected = {} -const initVersion = execSync('npm get init-version').toString().trim() - -;(function (cb) { - const files = [] - walker(templateDir) - .on('file', function (file) { - files.push(file) - }) - .on('end', function () { - let count = 0 - files.forEach(function (file) { - readFile(file, function (err, data) { - if (err) { - return cb(err) - } - - expected[file.replace(templateDir, '').replace(/__/, '.')] = data.toString() - - count++ - if (count === files.length) { - cb(null) - } - }) - }) - }) - .on('error', cb) -})(function (err) { - t.error(err) - define(t) -}) - -function define (t) { - const { beforeEach, test } = t - - beforeEach(() => { - rimraf.sync(workdir) - mkdirSync(workdir, { recursive: true }) - }) - - test('errors if directory exists', (t) => { - t.plan(2) - exec('node generate-plugin.js ./test/workdir', (err, stdout) => { - t.equal('directory ./test/workdir already exists', strip(stdout.toString().trim())) - t.equal(1, err.code) - }) - }) - - test('errors if generate doesn\'t have arguments', (t) => { - t.plan(2) - exec('node generate-plugin.js', (err, stdout) => { - t.equal('must specify a directory to \'fastify generate\'', strip(stdout.toString().trim())) - t.equal(1, err.code) - }) - }) - - test('errors if package.json exists when use generate .', (t) => { - t.plan(2) - exec('node generate-plugin.js .', (err, stdout) => { - t.equal('a package.json file already exists in target directory. retry with the --integrate flag to proceed', strip(stdout.toString().trim())) - t.equal(1, err.code) - }) - }) - - test('errors if package.json exists when use generate ./', (t) => { - t.plan(2) - exec('node generate-plugin.js ./', (err, stdout) => { - t.equal('a package.json file already exists in target directory. retry with the --integrate flag to proceed', strip(stdout.toString().trim())) - t.equal(1, err.code) - }) - }) - - test('errors if folder exists', (t) => { - t.plan(2) - exec('node generate-plugin.js test', (err, stdout) => { - t.equal('directory test already exists', strip(stdout.toString().trim())) - t.equal(1, err.code) - }) - }) - - test('should finish succesfully', async (t) => { - t.plan(17 + Object.keys(expected).length) - try { - await generate(workdir, pluginTemplate) - await verifyPkg(t) - await verifyCopy(t, expected) - } catch (err) { - t.error(err) - } - }) - - function verifyPkg (t) { - return new Promise((resolve, reject) => { - const pkgFile = path.join(workdir, 'package.json') - - readFile(pkgFile, function (err, data) { - err && reject(err) - const pkg = JSON.parse(data) - t.equal(pkg.name, 'workdir') - t.equal(pkg.main, 'index.js') - t.equal(pkg.types, 'index.d.ts') - // we are not checking author because it depends on global npm configs - t.equal(pkg.version, initVersion) - t.equal(pkg.description, '') - t.ok(pkg.license === 'MIT') - t.equal(pkg.scripts.lint, 'standard && npm run lint:typescript') - t.equal(pkg.scripts['lint:typescript'], 'ts-standard') - t.equal(pkg.scripts.test, 'npm run lint && npm run unit && npm run test:typescript') - t.equal(pkg.scripts['test:typescript'], 'tsd') - t.equal(pkg.scripts.unit, 'node --test') - t.equal(pkg.dependencies['fastify-plugin'], cliPkg.dependencies['fastify-plugin']) - t.equal(pkg.devDependencies['@types/node'], cliPkg.devDependencies['@types/node']) - t.equal(pkg.devDependencies.fastify, cliPkg.devDependencies.fastify) - t.equal(pkg.devDependencies.standard, cliPkg.devDependencies.standard) - t.equal(pkg.devDependencies.tsd, cliPkg.devDependencies.tsd) - t.equal(pkg.devDependencies.typescript, cliPkg.devDependencies.typescript) - t.same(pkg.tsd, pluginTemplate.tsd) - - resolve() - }) - }) - } - - function verifyCopy (t, expected) { - const pkgFile = path.join(workdir, 'package.json') - const githubDir = path.join(workdir, '.github', 'workflows', 'ci.yml') - return new Promise((resolve, reject) => { - walker(workdir) - .on('file', function (file) { - if (file === pkgFile || file === githubDir) { - return - } - try { - const data = readFileSync(file) - file = file.replace(workdir, '') - t.same(data.toString().replace(/\r\n/g, '\n'), expected[file], file + ' matching') - } catch (err) { - reject(err) - } - }) - .on('end', function () { - resolve() - }) - .on('error', function (err, entry, stat) { - reject(err) - }) - }) - } -} diff --git a/test/generate-readme.test.js b/test/generate-readme.test.js deleted file mode 100644 index 66d92bd8..00000000 --- a/test/generate-readme.test.js +++ /dev/null @@ -1,26 +0,0 @@ -'use strict' - -const path = require('node:path') -const fs = require('node:fs') -const t = require('tap') -const rimraf = require('rimraf') -const { generate } = require('../generate-readme') - -const plugindir = path.join(__dirname, 'plugindir') -const plugin = require(plugindir) -const { test } = t - -test('should create readme', async (t) => { - t.plan(1) - const pluginMeta = plugin[Symbol.for('plugin-meta')] - const encapsulated = !plugin[Symbol.for('skip-override')] - const pluginFileName = path.basename(plugindir) - try { - await generate(plugindir, { pluginMeta, encapsulated, pluginFileName }) - const readme = path.join(plugindir, 'README.md') - t.ok(fs.existsSync(readme)) - rimraf(readme, () => t.end()) - } catch (err) { - t.error(err) - } -}) diff --git a/test/generate-swagger.test.js b/test/generate-swagger.test.js deleted file mode 100644 index f0e42ea5..00000000 --- a/test/generate-swagger.test.js +++ /dev/null @@ -1,31 +0,0 @@ -'use strict' - -const path = require('node:path') -const t = require('tap') -const { test } = t -const { generateSwagger } = require('../generate-swagger') - -const swaggerplugindir = path.join(__dirname, 'swaggerplugindir') -const swaggerplugin = path.join(swaggerplugindir, 'plugin.js') - -test('should generate swagger', async (t) => { - t.plan(1) - - try { - const swagger = JSON.parse(await generateSwagger([swaggerplugin])) - t.equal(swagger.openapi, '3.0.3') - } catch (err) { - t.error(err) - } -}) - -test('should generate swagger in yaml format', async (t) => { - t.plan(1) - - try { - const swagger = await generateSwagger(['--yaml=true', swaggerplugin]) - t.ok(swagger.startsWith('openapi: 3.0.3')) - } catch (err) { - t.error(err) - } -}) diff --git a/test/generate-typescript-esm.test.js b/test/generate-typescript-esm.test.js deleted file mode 100644 index de11de1a..00000000 --- a/test/generate-typescript-esm.test.js +++ /dev/null @@ -1,194 +0,0 @@ -'use strict' - -// bailout if a test is broken -// so that the folder can be inspected -process.env.TAP_BAIL = true - -const t = require('tap') -const { - mkdirSync, - readFileSync, - readFile -} = require('node:fs') -const path = require('node:path') -const rimraf = require('rimraf') -const walker = require('walker') -const { generate, typescriptTemplate } = require('../generate') -const workdir = path.join(__dirname, 'workdir') -const appTemplateDir = path.join(__dirname, '..', 'templates', 'app-ts') -const cliPkg = require('../package') -const { exec, execSync } = require('node:child_process') -const minimatch = require('minimatch') -const strip = require('strip-ansi') -const expected = {} -const initVersion = execSync('npm get init-version').toString().trim() - -typescriptTemplate.type = 'module' -typescriptTemplate.scripts.test = 'npm run build:ts && tsc -p test/tsconfig.json && FASTIFY_AUTOLOAD_TYPESCRIPT=1 node --test --experimental-test-coverage --loader ts-node/esm test/**/*.ts' - -;(function (cb) { - const files = [] - walker(appTemplateDir) - .on('file', function (file) { - files.push(file) - }) - .on('end', function () { - let count = 0 - files.forEach(function (file) { - readFile(file, function (err, data) { - if (err) { - return cb(err) - } - - expected[file.replace(appTemplateDir, '').replace(/__/, '.')] = data.toString() - - count++ - if (count === files.length) { - cb(null) - } - }) - }) - }) - .on('error', cb) -})(function (err) { - t.error(err) - define(t) -}) - -function define (t) { - const { beforeEach, test } = t - - beforeEach(() => { - rimraf.sync(workdir) - mkdirSync(workdir, { recursive: true }) - }) - - test('errors if directory exists', (t) => { - t.plan(2) - exec('node generate.js --lang=ts ./test/workdir --esm', (err, stdout) => { - t.equal('directory ./test/workdir already exists', strip(stdout.toString().trim())) - t.equal(1, err.code) - }) - }) - - test('errors if generate doesn\'t have arguments', (t) => { - t.plan(2) - exec('node generate.js --lang=ts', (err, stdout) => { - t.equal('must specify a directory to \'fastify generate\'', strip(stdout.toString().trim())) - t.equal(1, err.code) - }) - }) - - test('errors if package.json exists when use generate .', (t) => { - t.plan(2) - exec('node generate.js --lang=ts .', (err, stdout) => { - t.equal('a package.json file already exists in target directory. retry with the --integrate flag to proceed', strip(stdout.toString().trim())) - t.equal(1, err.code) - }) - }) - - test('errors if package.json exists when use generate ./', (t) => { - t.plan(2) - exec('node generate.js --lang=ts ./', (err, stdout) => { - t.equal('a package.json file already exists in target directory. retry with the --integrate flag to proceed', strip(stdout.toString().trim())) - t.equal(1, err.code) - }) - }) - - test('errors if folder exists', (t) => { - t.plan(2) - exec('node generate.js --lang=ts test', (err, stdout) => { - t.equal('directory test already exists', strip(stdout.toString().trim())) - t.equal(1, err.code) - }) - }) - - test('should finish successfully with typescript template', async (t) => { - t.plan(24 + Object.keys(expected).length) - try { - await generate(workdir, typescriptTemplate) - await verifyPkg(t) - await verifyTSConfig(t) - await verifyCopy(t, expected) - } catch (err) { - t.error(err) - } - }) - - function verifyPkg (t) { - return new Promise((resolve, reject) => { - const pkgFile = path.join(workdir, 'package.json') - - readFile(pkgFile, function (err, data) { - t.error(err) - const pkg = JSON.parse(data) - t.equal(pkg.name, 'workdir') - // we are not checking author because it depends on global npm configs - t.equal(pkg.version, initVersion) - t.equal(pkg.description, 'This project was bootstrapped with Fastify-CLI.') - // by default this will be ISC but since we have a MIT licensed pkg file in upper dir, npm will set the license to MIT in this case - // so for local tests we need to accept MIT as well - t.ok(pkg.license === 'ISC' || pkg.license === 'MIT') - t.equal(pkg.scripts.test, 'npm run build:ts && tsc -p test/tsconfig.json && FASTIFY_AUTOLOAD_TYPESCRIPT=1 node --test --experimental-test-coverage --loader ts-node/esm test/**/*.ts') - t.equal(pkg.scripts.start, 'npm run build:ts && fastify start -l info dist/app.js') - t.equal(pkg.scripts['build:ts'], 'tsc') - t.equal(pkg.scripts['watch:ts'], 'tsc -w') - t.equal(pkg.scripts.dev, 'npm run build:ts && concurrently -k -p "[{name}]" -n "TypeScript,App" -c "yellow.bold,cyan.bold" "npm:watch:ts" "npm:dev:start"') - t.equal(pkg.scripts['dev:start'], 'fastify start --ignore-watch=.ts$ -w -l info -P dist/app.js') - t.equal(pkg.dependencies['fastify-cli'], '^' + cliPkg.version) - t.equal(pkg.dependencies.fastify, cliPkg.dependencies.fastify) - t.equal(pkg.dependencies['fastify-plugin'], cliPkg.devDependencies['fastify-plugin'] || cliPkg.dependencies['fastify-plugin']) - t.equal(pkg.dependencies['@fastify/autoload'], cliPkg.devDependencies['@fastify/autoload']) - t.equal(pkg.dependencies['@fastify/sensible'], cliPkg.devDependencies['@fastify/sensible']) - t.equal(pkg.devDependencies['@types/node'], cliPkg.devDependencies['@types/node']) - t.equal(pkg.devDependencies['ts-node'], cliPkg.devDependencies['ts-node']) - t.equal(pkg.devDependencies.concurrently, cliPkg.devDependencies.concurrently) - t.equal(pkg.devDependencies.typescript, cliPkg.devDependencies.typescript) - - const testGlob = pkg.scripts.test.split(' ', 15)[14] - - t.equal(minimatch.match(['test/routes/plugins/more/test/here/ok.test.ts'], testGlob).length, 1) - resolve() - }) - }) - } - - function verifyTSConfig (t) { - const tsConfigFile = path.join(workdir, 'tsconfig.json') - - readFile(tsConfigFile, function (err, data) { - t.error(err) - const tsConfig = JSON.parse(data) - - t.equal(tsConfig.extends, 'fastify-tsconfig') - t.equal(tsConfig.compilerOptions.outDir, 'dist') - t.same(tsConfig.include, ['src/**/*.ts']) - }) - } - - function verifyCopy (t, expected) { - const pkgFile = path.join(workdir, 'package.json') - const tsConfigFile = path.join(workdir, 'tsconfig.json') - return new Promise((resolve, reject) => { - walker(workdir) - .on('file', function (file) { - if (file === pkgFile || file === tsConfigFile) { - return - } - try { - const data = readFileSync(file) - file = file.replace(workdir, '') - t.same(data.toString().replace(/\r\n/g, '\n'), expected[file], file + ' matching') - } catch (err) { - reject(err) - } - }) - .on('end', function () { - resolve() - }) - .on('error', function (err, entry, stat) { - reject(err) - }) - }) - } -} diff --git a/test/generate-typescript.test.js b/test/generate-typescript.test.js deleted file mode 100644 index d38ebed0..00000000 --- a/test/generate-typescript.test.js +++ /dev/null @@ -1,192 +0,0 @@ -'use strict' - -// bailout if a test is broken -// so that the folder can be inspected -process.env.TAP_BAIL = true - -const t = require('tap') -const { - mkdirSync, - readFileSync, - readFile -} = require('node:fs') -const path = require('node:path') -const rimraf = require('rimraf') -const walker = require('walker') -const { generate, typescriptTemplate } = require('../generate') -const workdir = path.join(__dirname, 'workdir') -const appTemplateDir = path.join(__dirname, '..', 'templates', 'app-ts') -const cliPkg = require('../package') -const { exec, execSync } = require('node:child_process') -const minimatch = require('minimatch') -const strip = require('strip-ansi') -const expected = {} -const initVersion = execSync('npm get init-version').toString().trim() - -;(function (cb) { - const files = [] - walker(appTemplateDir) - .on('file', function (file) { - files.push(file) - }) - .on('end', function () { - let count = 0 - files.forEach(function (file) { - readFile(file, function (err, data) { - if (err) { - return cb(err) - } - - expected[file.replace(appTemplateDir, '').replace(/__/, '.')] = data.toString() - - count++ - if (count === files.length) { - cb(null) - } - }) - }) - }) - .on('error', cb) -})(function (err) { - t.error(err) - define(t) -}) - -function define (t) { - const { beforeEach, test } = t - - beforeEach(() => { - rimraf.sync(workdir) - mkdirSync(workdir, { recursive: true }) - }) - - test('errors if directory exists', (t) => { - t.plan(2) - exec('node generate.js --lang=ts ./test/workdir', (err, stdout) => { - t.equal('directory ./test/workdir already exists', strip(stdout.toString().trim())) - t.equal(1, err.code) - }) - }) - - test('errors if generate doesn\'t have arguments', (t) => { - t.plan(2) - exec('node generate.js --lang=ts', (err, stdout) => { - t.equal('must specify a directory to \'fastify generate\'', strip(stdout.toString().trim())) - t.equal(1, err.code) - }) - }) - - test('errors if package.json exists when use generate .', (t) => { - t.plan(2) - exec('node generate.js --lang=ts .', (err, stdout) => { - t.equal('a package.json file already exists in target directory. retry with the --integrate flag to proceed', strip(stdout.toString().trim())) - t.equal(1, err.code) - }) - }) - - test('errors if package.json exists when use generate ./', (t) => { - t.plan(2) - exec('node generate.js --lang=ts ./', (err, stdout) => { - t.equal('a package.json file already exists in target directory. retry with the --integrate flag to proceed', strip(stdout.toString().trim())) - t.equal(1, err.code) - }) - }) - - test('errors if folder exists', (t) => { - t.plan(2) - exec('node generate.js --lang=ts test', (err, stdout) => { - t.equal('directory test already exists', strip(stdout.toString().trim())) - t.equal(1, err.code) - }) - }) - - test('should finish successfully with typescript template', async (t) => { - t.plan(25 + Object.keys(expected).length) - try { - await generate(workdir, typescriptTemplate) - await verifyPkg(t) - await verifyTSConfig(t) - await verifyCopy(t, expected) - } catch (err) { - t.error(err) - } - }) - - function verifyPkg (t) { - return new Promise((resolve, reject) => { - const pkgFile = path.join(workdir, 'package.json') - - readFile(pkgFile, function (err, data) { - t.error(err) - const pkg = JSON.parse(data) - t.equal(pkg.name, 'workdir') - // we are not checking author because it depends on global npm configs - t.equal(pkg.version, initVersion) - t.equal(pkg.description, 'This project was bootstrapped with Fastify-CLI.') - // by default this will be ISC but since we have a MIT licensed pkg file in upper dir, npm will set the license to MIT in this case - // so for local tests we need to accept MIT as well - t.ok(pkg.license === 'ISC' || pkg.license === 'MIT') - t.equal(pkg.scripts.test, 'npm run build:ts && tsc -p test/tsconfig.json && c8 node --test -r ts-node/register "test/**/*.ts"') - t.equal(pkg.scripts.start, 'npm run build:ts && fastify start -l info dist/app.js') - t.equal(pkg.scripts['build:ts'], 'tsc') - t.equal(pkg.scripts['watch:ts'], 'tsc -w') - t.equal(pkg.scripts.dev, 'npm run build:ts && concurrently -k -p "[{name}]" -n "TypeScript,App" -c "yellow.bold,cyan.bold" "npm:watch:ts" "npm:dev:start"') - t.equal(pkg.scripts['dev:start'], 'fastify start --ignore-watch=.ts$ -w -l info -P dist/app.js') - t.equal(pkg.dependencies['fastify-cli'], '^' + cliPkg.version) - t.equal(pkg.dependencies.fastify, cliPkg.dependencies.fastify) - t.equal(pkg.dependencies['fastify-plugin'], cliPkg.devDependencies['fastify-plugin'] || cliPkg.dependencies['fastify-plugin']) - t.equal(pkg.dependencies['@fastify/autoload'], cliPkg.devDependencies['@fastify/autoload']) - t.equal(pkg.dependencies['@fastify/sensible'], cliPkg.devDependencies['@fastify/sensible']) - t.equal(pkg.devDependencies['@types/node'], cliPkg.devDependencies['@types/node']) - t.equal(pkg.devDependencies.c8, cliPkg.devDependencies.c8) - t.equal(pkg.devDependencies['ts-node'], cliPkg.devDependencies['ts-node']) - t.equal(pkg.devDependencies.concurrently, cliPkg.devDependencies.concurrently) - t.equal(pkg.devDependencies.typescript, cliPkg.devDependencies.typescript) - - const testGlob = pkg.scripts.test.split(' ', 14)[13].replaceAll('"', '') - - t.equal(minimatch.match(['test/routes/plugins/more/test/here/ok.test.ts'], testGlob).length, 1, 'should match glob') - resolve() - }) - }) - } - - function verifyTSConfig (t) { - const tsConfigFile = path.join(workdir, 'tsconfig.json') - - readFile(tsConfigFile, function (err, data) { - t.error(err) - const tsConfig = JSON.parse(data) - - t.equal(tsConfig.extends, 'fastify-tsconfig') - t.equal(tsConfig.compilerOptions.outDir, 'dist') - t.same(tsConfig.include, ['src/**/*.ts']) - }) - } - - function verifyCopy (t, expected) { - const pkgFile = path.join(workdir, 'package.json') - const tsConfigFile = path.join(workdir, 'tsconfig.json') - return new Promise((resolve, reject) => { - walker(workdir) - .on('file', function (file) { - if (file === pkgFile || file === tsConfigFile) { - return - } - try { - const data = readFileSync(file) - file = file.replace(workdir, '') - t.same(data.toString().replace(/\r\n/g, '\n'), expected[file], file + ' matching') - } catch (err) { - reject(err) - } - }) - .on('end', function () { - resolve() - }) - .on('error', function (err, entry, stat) { - reject(err) - }) - }) - } -} diff --git a/test/generate.test.js b/test/generate.test.js deleted file mode 100644 index 84d590e9..00000000 --- a/test/generate.test.js +++ /dev/null @@ -1,203 +0,0 @@ -'use strict' - -// bailout if a test is broken -// so that the folder can be inspected -process.env.TAP_BAIL = true - -const t = require('tap') -const { - mkdirSync, - readFileSync, - readFile, - promises: fsPromises, - unlink -} = require('node:fs') -const path = require('node:path') -const { promisify } = require('node:util') -const rimraf = require('rimraf') -const walker = require('walker') -const { generate, javascriptTemplate } = require('../generate') -const workdir = path.join(__dirname, 'workdir') -const appTemplateDir = path.join(__dirname, '..', 'templates', 'app') -const cliPkg = require('../package') -const { exec, execSync } = require('node:child_process') -const pExec = promisify(exec) -const pUnlink = promisify(unlink) -const minimatch = require('minimatch') -const strip = require('strip-ansi') -const expected = {} -const initVersion = execSync('npm get init-version').toString().trim() - -;(function (cb) { - const files = [] - walker(appTemplateDir) - .on('file', function (file) { - files.push(file) - }) - .on('end', function () { - let count = 0 - files.forEach(function (file) { - readFile(file, function (err, data) { - if (err) { - return cb(err) - } - - expected[file.replace(appTemplateDir, '').replace(/__/, '.')] = data.toString() - - count++ - if (count === files.length) { - cb(null) - } - }) - }) - }) - .on('error', cb) -})(function (err) { - t.error(err) - define(t) -}) - -function define (t) { - const { beforeEach, test } = t - - beforeEach(() => { - rimraf.sync(workdir) - mkdirSync(workdir, { recursive: true }) - }) - - test('errors if directory exists', (t) => { - t.plan(2) - exec('node generate.js ./test/workdir', (err, stdout) => { - t.equal('directory ./test/workdir already exists', strip(stdout.toString().trim())) - t.equal(1, err.code) - }) - }) - - test('errors if generate doesn\'t have arguments', (t) => { - t.plan(2) - exec('node generate.js', (err, stdout) => { - t.equal('must specify a directory to \'fastify generate\'', strip(stdout.toString().trim())) - t.equal(1, err.code) - }) - }) - - test('errors if package.json exists when use generate . and integrate flag is not set', (t) => { - t.plan(2) - exec('node generate.js .', (err, stdout) => { - t.equal('a package.json file already exists in target directory. retry with the --integrate flag to proceed', strip(stdout.toString().trim())) - t.equal(1, err.code) - }) - }) - - test('errors if package.json exists when use generate ./ and integrate flag is not set', (t) => { - t.plan(2) - exec('node generate.js ./', (err, stdout) => { - t.equal('a package.json file already exists in target directory. retry with the --integrate flag to proceed', strip(stdout.toString().trim())) - t.equal(1, err.code) - }) - }) - - test('errors if folder exists', (t) => { - t.plan(2) - exec('node generate.js test', (err, stdout) => { - t.equal('directory test already exists', strip(stdout.toString().trim())) - t.equal(1, err.code) - }) - }) - - test('should finish succesfully with javascript template', async (t) => { - t.plan(13 + Object.keys(expected).length) - try { - await generate(workdir, javascriptTemplate) - await verifyPkg(t) - await verifyCopy(t, expected) - } catch (err) { - t.error(err) - } - }) - - test('--integrate option will enhance preexisting package.json and overwrite preexisting files', async (t) => { - t.plan(13 + Object.keys(expected).length) - try { - await generate(workdir, javascriptTemplate) - await pUnlink(path.join(workdir, 'package.json')) - await pExec('npm init -y', { cwd: workdir }) - await pExec('node ../../generate . --integrate', { cwd: workdir }) - await verifyPkg(t) - await verifyCopy(t, expected) - } catch (err) { - t.error(err) - } - }) - - test('--standardlint option will add standard lint dependencies and scripts to javascript template', async (t) => { - const dir = path.join(__dirname, 'workdir-with-lint') - const cwd = path.join(dir, '..') - const bin = path.join('..', 'generate') - rimraf.sync(dir) - await pExec(`node ${bin} ${dir} --standardlint`, { cwd }) - - await verifyPkg(t, dir, 'workdir-with-lint') - - const data = await fsPromises.readFile(path.join(dir, 'package.json')) - const pkg = JSON.parse(data) - t.equal(pkg.scripts.pretest, 'standard') - t.equal(pkg.scripts.lint, 'standard --fix') - t.equal(pkg.devDependencies.standard, cliPkg.devDependencies.standard) - }) - - function verifyPkg (t, dir = workdir, pkgName = 'workdir') { - return new Promise((resolve, reject) => { - const pkgFile = path.join(dir, 'package.json') - - readFile(pkgFile, function (err, data) { - err && reject(err) - const pkg = JSON.parse(data) - t.equal(pkg.name, pkgName) - // we are not checking author because it depends on global npm configs - t.equal(pkg.version, initVersion) - t.equal(pkg.description, 'This project was bootstrapped with Fastify-CLI.') - // by default this will be ISC but since we have a MIT licensed pkg file in upper dir, npm will set the license to MIT in this case - // so for local tests we need to accept MIT as well - t.ok(pkg.license === 'ISC' || pkg.license === 'MIT') - t.equal(pkg.scripts.test, 'node --test test/**/*.test.js') - t.equal(pkg.scripts.start, 'fastify start -l info app.js') - t.equal(pkg.scripts.dev, 'fastify start -w -l info -P app.js') - t.equal(pkg.dependencies['fastify-cli'], '^' + cliPkg.version) - t.equal(pkg.dependencies.fastify, cliPkg.dependencies.fastify) - t.equal(pkg.dependencies['fastify-plugin'], cliPkg.devDependencies['fastify-plugin'] || cliPkg.dependencies['fastify-plugin']) - t.equal(pkg.dependencies['@fastify/autoload'], cliPkg.devDependencies['@fastify/autoload']) - t.equal(pkg.dependencies['@fastify/sensible'], cliPkg.devDependencies['@fastify/sensible']) - - const testGlob = pkg.scripts.test.split(' ', 3)[2] - t.equal(minimatch.match(['test/services/plugins/more/test/here/ok.test.js'], testGlob).length, 1) - resolve() - }) - }) - } - - function verifyCopy (t, expected) { - const pkgFile = path.join(workdir, 'package.json') - return new Promise((resolve, reject) => { - walker(workdir) - .on('file', function (file) { - if (file === pkgFile) { - return - } - try { - const data = readFileSync(file) - file = file.replace(workdir, '') - t.same(data.toString().replace(/\r\n/g, '\n'), expected[file], file + ' matching') - } catch (err) { - reject(err) - } - }) - .on('end', function () { - resolve() - }) - .on('error', function (err, entry, stat) { - reject(err) - }) - }) - } -} diff --git a/test/graceful-shutdown.test.js b/test/graceful-shutdown.test.js deleted file mode 100644 index 4b9196e0..00000000 --- a/test/graceful-shutdown.test.js +++ /dev/null @@ -1,54 +0,0 @@ -'use strict' - -const t = require('tap') -// Tests skip on win32 platforms due SIGINT signal is not supported across all windows platforms -const test = (process.platform === 'win32') ? t.skip : t.test -const sinon = require('sinon') -const start = require('../start') - -let _port = 3001 - -function getPort () { - return '' + _port++ -} - -let spy = null -let fastify = null -let signalCounter = null -const sandbox = sinon.createSandbox() - -t.beforeEach(async () => { - signalCounter = process.listenerCount('SIGINT') - - const argv = ['-p', getPort(), './examples/plugin.js'] - fastify = await start.start(argv) - spy = sinon.spy(fastify, 'close') -}) - -t.afterEach(async () => { - sandbox.restore() -}) - -test('should add and remove SIGINT listener as expected ', async t => { - t.plan(2) - - t.equal(process.listenerCount('SIGINT'), signalCounter + 1) - - await fastify.close() - - t.equal(process.listenerCount('SIGINT'), signalCounter) - - t.end() -}) - -test('should have called fastify.close() when receives a SIGINT signal', async t => { - process.once('SIGINT', () => { - sinon.assert.called(spy) - - t.end() - - process.exit() - }) - - process.kill(process.pid, 'SIGINT') -}) diff --git a/test/helper.test.js b/test/helper.test.js deleted file mode 100644 index 20607d9f..00000000 --- a/test/helper.test.js +++ /dev/null @@ -1,152 +0,0 @@ -'use strict' - -const util = require('node:util') -const fs = require('node:fs') -const path = require('node:path') -const { test } = require('tap') -const stream = require('node:stream') - -const helper = require('../helper') - -const writeFile = util.promisify(fs.writeFile) -const readFile = util.promisify(fs.readFile) - -test('should return the fastify instance', async t => { - const argv = ['./examples/plugin.js'] - const app = await helper.build(argv, {}) - t.teardown(() => app.close()) - t.notOk(app.server.listening) -}) - -test('should reload the env at each build', async t => { - const testdir = t.testdir({ - '.env': 'GREETING=world', - 'plugin.js': await readFile(path.join(__dirname, '../examples/plugin-with-env.js')) - }) - - const argv = [path.join(testdir, 'plugin.js')] - const cwd = process.cwd() - - process.chdir(testdir) - t.teardown(() => { process.chdir(cwd) }) - - { - await writeFile(path.join(testdir, '.env'), 'GREETING=one') - const app = await helper.build(argv) - t.teardown(() => app.close()) - const res = await app.inject('/') - t.same(res.json(), { hello: 'one' }) - } - - { - delete process.env.GREETING // dotenv will not overwrite the env if set - await writeFile(path.join(testdir, '.env'), 'GREETING=two') - const app = await helper.build(argv) - t.teardown(() => app.close()) - const res = await app.inject('/') - t.same(res.json(), { hello: 'two' }) - } -}) - -test('setting plugin options', async t => { - const argv = [ - './examples/plugin-with-custom-options.js', - '--', - '-abc', - '--hello', - 'world' - ] - const app = await helper.build(argv, { from: 'build' }) - t.teardown(() => app.close()) - const res = await app.inject('/') - t.same(res.json(), { - a: true, - b: true, - c: true, - hello: 'world', - from: 'build' - }) -}) - -test('setting plugin options, extra has priority', async t => { - const argv = [ - './examples/plugin-with-custom-options.js', - '--', - '--hello', - 'world' - ] - const app = await helper.build(argv, { hello: 'planet' }) - t.teardown(() => app.close()) - const res = await app.inject('/') - t.same(res.json(), { - hello: 'planet' - }) -}) - -test('setting plugin options, extra has priority', async t => { - const args = './examples/plugin-with-custom-options.js -- --hello world --from args' - const app = await helper.build(args, { hello: 'planet' }) - t.teardown(() => app.close()) - const res = await app.inject('/') - t.same(res.json(), { - hello: 'planet', - from: 'args' - }) -}) - -test('should start fastify', async t => { - const argv = ['./examples/plugin.js'] - const app = await helper.listen(argv, {}) - t.teardown(() => app.close()) - t.ok(app.server.listening) -}) - -test('should start fastify with custom logger configuration', async t => { - const argv = ['./examples/plugin.js'] - const lines = [] - const dest = new stream.Writable({ - write: function (chunk, enc, cb) { - lines.push(JSON.parse(chunk)) - cb() - } - }) - - const app = await helper.listen(argv, {}, { - logger: { - level: 'warn', - stream: dest - } - }) - t.teardown(() => app.close()) - app.log.info('test') - t.same(lines.length, 0) - app.log.warn('test') - t.same(lines.length, 1) - t.same(app.log.level, 'warn') -}) - -test('should merge the CLI and FILE configs', async t => { - const argv = ['./examples/plugin-with-logger.js', '--options'] - - const lines = [] - const dest = new stream.Writable({ - write: function (chunk, enc, cb) { - lines.push(JSON.parse(chunk)) - cb() - } - }) - - const app = await helper.listen(argv, {}, { - logger: { - level: 'warn', - stream: dest - } - }) - t.teardown(() => app.close()) - app.log.info('test') - t.same(lines.length, 0) - app.log.warn({ foo: 'test' }) - t.same(app.log.level, 'warn') - t.same(lines.length, 1) - t.same(lines[0].foo, '***') -}) diff --git a/test/plugindir/package.json b/test/plugindir/package.json deleted file mode 100644 index f680eaa3..00000000 --- a/test/plugindir/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "plugindir", - "version": "1.0.0", - "description": "test for generator-readme", - "main": "plugin.js", - "scripts": { - "test": "" - }, - "dependencies": { - "fastify-plugin": "^1.4.0" - }, - "author": "", - "license": "ISC" -} diff --git a/test/plugindir/plugin.js b/test/plugindir/plugin.js deleted file mode 100644 index c2232ab5..00000000 --- a/test/plugindir/plugin.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict' - -const fp = require('fastify-plugin') - -module.exports = fp(function (fastify, opts, next) { - fastify.decorate('someSupport', function () { - return 'hugs' - }) - next() -}) diff --git a/test/print-plugins.test.js b/test/print-plugins.test.js deleted file mode 100644 index da1450ef..00000000 --- a/test/print-plugins.test.js +++ /dev/null @@ -1,119 +0,0 @@ -'use strict' - -const proxyquire = require('proxyquire') -const tap = require('tap') -const sinon = require('sinon') -const util = require('node:util') -const exec = util.promisify(require('node:child_process').exec) - -const printPlugins = require('../print-plugins') - -const test = tap.test - -const { NYC_PROCESS_ID, NODE_V8_COVERAGE } = process.env -const SHOULD_SKIP = NYC_PROCESS_ID || NODE_V8_COVERAGE - -// This test should be skipped when coverage reporting is used since outputs won't match -test('should print plugins', { skip: SHOULD_SKIP }, async t => { - t.plan(3) - - const spy = sinon.spy() - const command = proxyquire('../print-plugins', { - './log': spy - }) - const fastify = await command.printPlugins(['./examples/plugin.js']) - - await fastify.close() - t.ok(spy.called) - t.same(spy.args[0][0], 'debug') - t.match(spy.args[0][1], /bound root \d+ ms\n├── bound _after \d+ ms\n├─┬ function \(fastify, options, next\) { -- fastify\.decorate\('test', true\) \d+ ms\n│ ├── bound _after \d+ ms\n│ ├── bound _after \d+ ms\n│ └── bound _after \d+ ms\n└── bound _after \d+ ms\n/) -}) - -// This test should be skipped when coverage reporting is used since outputs won't match -test('should plugins routes via cli', { skip: SHOULD_SKIP }, async t => { - t.plan(1) - const { stdout } = await exec('node cli.js print-plugins ./examples/plugin.js', { encoding: 'utf-8', timeout: 10000 }) - t.match( - stdout, - /bound root \d+ ms\n├── bound _after \d+ ms\n├─┬ function \(fastify, options, next\) { -- fastify\.decorate\('test', true\) \d+ ms\n│ ├── bound _after \d+ ms\n│ ├── bound _after \d+ ms\n│ └── bound _after \d+ ms\n└── bound _after \d+ ms\n\n/ - ) -}) - -test('should warn on file not found', t => { - t.plan(1) - - const oldStop = printPlugins.stop - t.teardown(() => { printPlugins.stop = oldStop }) - printPlugins.stop = function (message) { - t.ok(/not-found.js doesn't exist within/.test(message), message) - } - - const argv = ['./data/not-found.js'] - printPlugins.printPlugins(argv) -}) - -test('should throw on package not found', t => { - t.plan(1) - - const oldStop = printPlugins.stop - t.teardown(() => { printPlugins.stop = oldStop }) - printPlugins.stop = function (err) { - t.ok(/Cannot find module 'unknown-package'/.test(err.message), err.message) - } - - const argv = ['./test/data/package-not-found.js'] - printPlugins.printPlugins(argv) -}) - -test('should throw on parsing error', t => { - t.plan(1) - - const oldStop = printPlugins.stop - t.teardown(() => { printPlugins.stop = oldStop }) - printPlugins.stop = function (err) { - t.equal(err.constructor, SyntaxError) - } - - const argv = ['./test/data/parsing-error.js'] - printPlugins.printPlugins(argv) -}) - -test('should exit without error on help', t => { - const exit = process.exit - process.exit = sinon.spy() - - t.teardown(() => { - process.exit = exit - }) - - const argv = ['-h', 'true'] - printPlugins.printPlugins(argv) - - t.ok(process.exit.called) - t.equal(process.exit.lastCall.args[0], undefined) - - t.end() -}) - -// This test should be skipped when coverage reporting is used since outputs won't match -test('should print plugins of server with an async/await plugin', { skip: SHOULD_SKIP }, async t => { - const nodeMajorVersion = process.versions.node.split('.').map(x => parseInt(x, 10))[0] - if (nodeMajorVersion < 7) { - t.pass('Skip because Node version < 7') - return t.end() - } - - t.plan(3) - - const spy = sinon.spy() - const command = proxyquire('../print-plugins', { - './log': spy - }) - const argv = ['./examples/async-await-plugin.js'] - const fastify = await command.printPlugins(argv) - - await fastify.close() - t.ok(spy.called) - t.same(spy.args[0][0], 'debug') - t.match(spy.args[0][1], /bound root \d+ ms\n├── bound _after \d+ ms\n├─┬ async function \(fastify, options\) { -- fastify\.get\('\/', async function \(req, reply\) { \d+ ms\n│ ├── bound _after \d+ ms\n│ └── bound _after \d+ ms\n└── bound _after \d+ ms\n/) -}) diff --git a/test/print-routes.test.js b/test/print-routes.test.js deleted file mode 100644 index 67bd8867..00000000 --- a/test/print-routes.test.js +++ /dev/null @@ -1,153 +0,0 @@ -'use strict' - -const proxyquire = require('proxyquire') -const tap = require('tap') -const sinon = require('sinon') -const util = require('node:util') -const exec = util.promisify(require('node:child_process').exec) - -const printRoutes = require('../print-routes') - -const test = tap.test -const { NYC_PROCESS_ID, NODE_V8_COVERAGE } = process.env -const SHOULD_SKIP = NYC_PROCESS_ID || NODE_V8_COVERAGE - -test('should print routes', async t => { - t.plan(2) - - const spy = sinon.spy() - const command = proxyquire('../print-routes', { - './log': spy - }) - const fastify = await command.printRoutes(['./examples/plugin.js']) - - await fastify.close() - t.ok(spy.called) - t.same(spy.args, [['debug', '└── / (GET, HEAD, POST)\n']]) -}) - -// This never exits in CI for some reason -test('should print routes via cli', { skip: SHOULD_SKIP }, async t => { - t.plan(1) - const { stdout } = await exec('node cli.js print-routes ./examples/plugin.js', { encoding: 'utf-8', timeout: 10000 }) - t.same( - stdout, - '└── / (GET, HEAD, POST)\n\n' - ) -}) - -test('should warn on file not found', t => { - t.plan(1) - - const oldStop = printRoutes.stop - t.teardown(() => { printRoutes.stop = oldStop }) - printRoutes.stop = function (message) { - t.ok(/not-found.js doesn't exist within/.test(message), message) - } - - const argv = ['./data/not-found.js'] - printRoutes.printRoutes(argv) -}) - -test('should throw on package not found', t => { - t.plan(1) - - const oldStop = printRoutes.stop - t.teardown(() => { printRoutes.stop = oldStop }) - printRoutes.stop = function (err) { - t.ok(/Cannot find module 'unknown-package'/.test(err.message), err.message) - } - - const argv = ['./test/data/package-not-found.js'] - printRoutes.printRoutes(argv) -}) - -test('should throw on parsing error', t => { - t.plan(1) - - const oldStop = printRoutes.stop - t.teardown(() => { printRoutes.stop = oldStop }) - printRoutes.stop = function (err) { - t.equal(err.constructor, SyntaxError) - } - - const argv = ['./test/data/parsing-error.js'] - printRoutes.printRoutes(argv) -}) - -test('should exit without error on help', t => { - const exit = process.exit - process.exit = sinon.spy() - - t.teardown(() => { - process.exit = exit - }) - - const argv = ['-h', 'true'] - printRoutes.printRoutes(argv) - - t.ok(process.exit.called) - t.equal(process.exit.lastCall.args[0], undefined) - - t.end() -}) - -test('should print routes of server with an async/await plugin', async t => { - const nodeMajorVersion = process.versions.node.split('.').map(x => parseInt(x, 10))[0] - if (nodeMajorVersion < 7) { - t.pass('Skip because Node version < 7') - return t.end() - } - - t.plan(2) - - const spy = sinon.spy() - const command = proxyquire('../print-routes', { - './log': spy - }) - const argv = ['./examples/async-await-plugin.js'] - const fastify = await command.printRoutes(argv) - - await fastify.close() - t.ok(spy.called) - t.same(spy.args, [['debug', '└── / (GET, HEAD)\n']]) -}) - -test('should print uncimpressed routes with --common-refix flag', async t => { - t.plan(2) - - const spy = sinon.spy() - const command = proxyquire('../print-routes', { - './log': spy - }) - await command.cli(['./examples/plugin-common-prefix.js', '--commonPrefix']) - - t.ok(spy.called) - t.same(spy.args, [['debug', '└── /\n └── hel\n ├── lo-world (GET, HEAD)\n └── p (POST)\n']]) -}) - -test('should print debug safe GET routes with --method GET flag', async t => { - t.plan(2) - - const spy = sinon.spy() - const command = proxyquire('../print-routes', { - './log': spy - }) - await command.cli(['./examples/plugin.js', '--method', 'GET']) - - t.ok(spy.called) - t.same(spy.args, [['debug', '└── / (GET)\n']]) -}) - -test('should print routes with hooks with --include-hooks flag', async t => { - t.plan(2) - - const spy = sinon.spy() - const command = proxyquire('../print-routes', { - './log': spy - }) - await command.cli(['./examples/plugin.js', '--include-hooks']) - - t.ok(spy.called) - t.same(spy.args, [['debug', '└── / (GET, POST)\n / (HEAD)\n • (onSend) ["headRouteOnSendHandler()"]\n']]) -}) diff --git a/test/start.test.js b/test/start.test.js deleted file mode 100644 index c9e8dee5..00000000 --- a/test/start.test.js +++ /dev/null @@ -1,1179 +0,0 @@ -/* global GLOBAL_MODULE_1, GLOBAL_MODULE_2, GLOBAL_MODULE_3, GLOBAL_MODULE_4 */ -'use strict' - -const util = require('node:util') -const { once } = require('node:events') -const fs = require('node:fs') -const path = require('node:path') -const crypto = require('node:crypto') -const semver = require('semver') -const baseFilename = path.join(__dirname, 'fixtures', `test_${crypto.randomBytes(16).toString('hex')}`) -const { fork } = require('node:child_process') -const moduleSupport = semver.satisfies(process.version, '>= 14 || >= 12.17.0 < 13.0.0') - -const t = require('tap') -const test = t.test -const sgetOriginal = require('simple-get').concat -const sget = (opts, cb) => { - return new Promise((resolve, reject) => { - sgetOriginal(opts, (err, response, body) => { - if (err) return reject(err) - return resolve({ response, body }) - }) - }) -} -const sinon = require('sinon') -const proxyquire = require('proxyquire').noPreserveCache() -const start = require('../start') - -const writeFile = util.promisify(fs.writeFile) -const readFile = util.promisify(fs.readFile) - -function requireUncached (module) { - delete require.cache[require.resolve(module)] - return require(module) -} - -let _port = 3001 - -function getPort () { - return '' + _port++ -} - -// FIXME -// paths are relative to the root of the project -// this can be run only from there - -test('should start the server', async t => { - t.plan(4) - - const argv = ['-p', getPort(), './examples/plugin.js'] - const fastify = await start.start(argv) - - const { response, body } = await sget({ - method: 'GET', - url: `http://localhost:${fastify.server.address().port}` - }) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) - - await fastify.close() - t.pass('server closed') -}) - -test('should start the server with a typescript compiled module', async t => { - t.plan(4) - - const argv = ['-p', getPort(), './examples/ts-plugin.js'] - const fastify = await start.start(argv) - - const { response, body } = await sget({ - method: 'GET', - url: `http://localhost:${fastify.server.address().port}` - }) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) - - await fastify.close() - t.pass('server closed') -}) - -test('should start the server with pretty output', async t => { - t.plan(4) - - const argv = ['-p', getPort(), '-P', './examples/plugin.js'] - const fastify = await start.start(argv) - - const { response, body } = await sget({ - method: 'GET', - url: `http://localhost:${fastify.server.address().port}` - }) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) - - await fastify.close() - t.pass('server closed') -}) - -test('should start fastify with custom options', async t => { - t.plan(1) - // here the test should fail because of the wrong certificate - // or because the server is booted without the custom options - try { - const argv = ['-p', getPort(), '-o', 'true', './examples/plugin-with-options.js'] - const fastify = await start.start(argv) - await fastify.close() - t.pass('server closed') - } catch (e) { - t.pass('Custom options') - } -}) - -test('should start fastify with custom plugin options', async t => { - t.plan(4) - - const argv = [ - '-p', - getPort(), - './examples/plugin-with-custom-options.js', - '--', - '-abc', - '--hello', - 'world' - ] - const fastify = await start.start(argv) - - const { response, body } = await sget({ - method: 'GET', - url: `http://localhost:${fastify.server.address().port}` - }) - - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { - a: true, - b: true, - c: true, - hello: 'world' - }) - - await fastify.close() - t.pass('server closed') - t.end() -}) - -test('should start fastify with default custom plugin options', async t => { - t.plan(4) - - const argv = [ - '-o', - '-p', - getPort(), - './examples/plugin-with-custom-options.js' - ] - const fastify = await start.start(argv) - - const { response, body } = await sget({ - method: 'GET', - url: `http://localhost:${fastify.server.address().port}` - }) - - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { - hello: 'test' - }) - - await fastify.close() - t.pass('server closed') - t.end() -}) - -test('should start fastify with custom options with a typescript compiled plugin', async t => { - t.plan(1) - // here the test should fail because of the wrong certificate - // or because the server is booted without the custom options - try { - const argv = ['-p', getPort(), '-o', 'true', './examples/ts-plugin-with-options.js'] - await start.start(argv) - t.fail('Custom options') - } catch (e) { - t.pass('Custom options') - } -}) - -test('should start fastify with custom plugin options with a typescript compiled plugin', async t => { - t.plan(4) - - const argv = [ - '-p', - getPort(), - './examples/ts-plugin-with-custom-options.js', - '--', - '-abc', - '--hello', - 'world' - ] - const fastify = await start.start(argv) - - const { response, body } = await sget({ - method: 'GET', - url: `http://localhost:${fastify.server.address().port}` - }) - - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { - a: true, - b: true, - c: true, - hello: 'world' - }) - - await fastify.close() - t.pass('server closed') -}) - -test('should start fastify with custom plugin default exported options with a typescript compiled plugin', async t => { - t.plan(4) - - const argv = [ - '-o', - '-p', - getPort(), - './examples/ts-plugin-with-custom-options.js' - ] - const fastify = await start.start(argv) - - const { response, body } = await sget({ - method: 'GET', - url: `http://localhost:${fastify.server.address().port}` - }) - - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { - hello: 'test' - }) - - await fastify.close() - t.pass('server closed') - t.end() -}) - -test('should start the server at the given prefix', async t => { - t.plan(4) - - const argv = ['-p', getPort(), '-x', '/api/hello', './examples/plugin.js'] - const fastify = await start.start(argv) - - const { response, body } = await sget({ - method: 'GET', - url: `http://localhost:${fastify.server.address().port}/api/hello` - }) - - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) - - await fastify.close() - t.pass('server closed') -}) - -test('should start fastify at given socket path', { skip: process.platform === 'win32' }, async t => { - t.plan(1) - - const sockFile = path.resolve('test.sock') - t.teardown(() => { - try { - fs.unlinkSync(sockFile) - } catch (e) { } - }) - const argv = ['-s', sockFile, '-o', 'true', './examples/plugin.js'] - - try { - fs.unlinkSync(sockFile) - } catch (e) { } - - const fastify = await start.start(argv) - - await new Promise((resolve, reject) => { - const request = require('node:http').request({ - method: 'GET', - path: '/', - socketPath: sockFile - }, function (response) { - t.same(response.statusCode, 200) - return resolve() - }) - request.end() - }) - - t.teardown(fastify.close.bind(fastify)) -}) - -test('should error with a good timeout value', async t => { - t.plan(1) - - const start = proxyquire('../start', { - assert: { - ifError (err) { - t.equal(err.code, 'AVV_ERR_PLUGIN_EXEC_TIMEOUT') - } - } - }) - - const port = getPort() - - try { - const argv = ['-p', port, '-T', '100', './test/data/timeout-plugin.js'] - await start.start(argv) - } catch (err) { - t.equal(err.code, 'AVV_ERR_PLUGIN_EXEC_TIMEOUT') - } -}) - -test('should warn on file not found', t => { - t.plan(1) - - const oldStop = start.stop - t.teardown(() => { start.stop = oldStop }) - start.stop = function (message) { - t.ok(/not-found.js doesn't exist within/.test(message), message) - } - - const argv = ['-p', getPort(), './data/not-found.js'] - start.start(argv) -}) - -test('should throw on package not found', t => { - t.plan(1) - - const oldStop = start.stop - t.teardown(() => { start.stop = oldStop }) - start.stop = function (err) { - t.ok(/Cannot find module 'unknown-package'/.test(err.message), err.message) - } - - const argv = ['-p', getPort(), './test/data/package-not-found.js'] - start.start(argv) -}) - -test('should throw on parsing error', t => { - t.plan(1) - - const oldStop = start.stop - t.teardown(() => { start.stop = oldStop }) - start.stop = function (err) { - t.equal(err.constructor, SyntaxError) - } - - const argv = ['-p', getPort(), './test/data/parsing-error.js'] - start.start(argv) -}) - -test('should start the server with an async/await plugin', async t => { - if (Number(process.versions.node[0]) < 7) { - t.pass('Skip because Node version < 7') - return t.end() - } - - t.plan(4) - - const argv = ['-p', getPort(), './examples/async-await-plugin.js'] - const fastify = await start.start(argv) - - const { response, body } = await sget({ - method: 'GET', - url: `http://localhost:${fastify.server.address().port}` - }) - - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) - - await fastify.close() - t.pass('server closed') -}) - -test('should exit without error on help', t => { - const exit = process.exit - process.exit = sinon.spy() - - t.teardown(() => { - process.exit = exit - }) - - const argv = ['-p', getPort(), '-h', 'true'] - start.start(argv) - - t.ok(process.exit.called) - t.equal(process.exit.lastCall.args[0], undefined) - - t.end() -}) - -test('should throw the right error on require file', t => { - t.plan(1) - - const oldStop = start.stop - t.teardown(() => { start.stop = oldStop }) - start.stop = function (err) { - t.ok(/undefinedVariable is not defined/.test(err.message), err.message) - } - - const argv = ['-p', getPort(), './test/data/undefinedVariable.js'] - start.start(argv) -}) - -test('should respond 413 - Payload too large', async t => { - t.plan(3) - - const bodyTooLarge = '{1: 11}' - const bodySmaller = '{1: 1}' - - const bodyLimitValue = '' + (bodyTooLarge.length + 2 - 1) - const argv = ['-p', getPort(), '--body-limit', bodyLimitValue, './examples/plugin.js'] - const fastify = await start.start(argv) - - const { response: responseFail } = await sget({ - method: 'POST', - url: `http://localhost:${fastify.server.address().port}`, - body: bodyTooLarge, - json: true - }) - - t.equal(responseFail.statusCode, 413) - - const { response: responseOk } = await sget({ - method: 'POST', - url: `http://localhost:${fastify.server.address().port}`, - body: bodySmaller, - json: true - }) - t.equal(responseOk.statusCode, 200) - - await fastify.close() - t.pass('server closed') -}) - -test('should start the server (using env var)', async t => { - t.plan(4) - - process.env.FASTIFY_PORT = getPort() - const argv = ['./examples/plugin.js'] - const fastify = await start.start(argv) - - const { response, body } = await sget({ - method: 'GET', - url: `http://localhost:${process.env.FASTIFY_PORT}` - }) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) - - delete process.env.FASTIFY_PORT - - await fastify.close() - t.pass('server closed') -}) - -test('should start the server (using PORT-env var)', async t => { - t.plan(4) - - process.env.PORT = getPort() - const argv = ['./examples/plugin.js'] - const fastify = await start.start(argv) - - const { response, body } = await sget({ - method: 'GET', - url: `http://localhost:${process.env.PORT}` - }) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) - - delete process.env.PORT - - await fastify.close() - t.pass('server closed') -}) - -test('should start the server (using FASTIFY_PORT-env preceding PORT-env var)', async t => { - t.plan(4) - - process.env.FASTIFY_PORT = getPort() - process.env.PORT = getPort() - const argv = ['./examples/plugin.js'] - const fastify = await start.start(argv) - - const { response, body } = await sget({ - method: 'GET', - url: `http://localhost:${process.env.FASTIFY_PORT}` - }) - - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) - - delete process.env.FASTIFY_PORT - delete process.env.PORT - - await fastify.close() - t.pass('server closed') -}) - -test('should start the server (using -p preceding FASTIFY_PORT-env var)', async t => { - t.plan(4) - - const port = getPort() - process.env.FASTIFY_PORT = getPort() - const argv = ['-p', port, './examples/plugin.js'] - const fastify = await start.start(argv) - - const { response, body } = await sget({ - method: 'GET', - url: `http://localhost:${port}` - }) - - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) - - delete process.env.FASTIFY_PORT - - await fastify.close() - t.pass('server closed') -}) - -test('should start the server at the given prefix (using env var)', async t => { - t.plan(4) - - process.env.FASTIFY_PORT = getPort() - process.env.FASTIFY_PREFIX = '/api/hello' - const argv = ['./examples/plugin.js'] - const fastify = await start.start(argv) - - const { response, body } = await sget({ - method: 'GET', - url: `http://localhost:${process.env.FASTIFY_PORT}/api/hello` - }) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hello: 'world' }) - - delete process.env.FASTIFY_PORT - delete process.env.FASTIFY_PREFIX - - await fastify.close() - t.pass('server closed') -}) - -test('should start the server at the given prefix (using env var read from dotenv)', async t => { - t.plan(3) - - const start = proxyquire('../start', { - dotenv: { - config () { - t.pass('config called') - process.env.FASTIFY_PORT = 8080 - } - } - }) - const argv = ['./examples/plugin.js'] - const fastify = await start.start(argv) - t.equal(fastify.server.address().port, 8080) - delete process.env.FASTIFY_PORT - - await fastify.close() - t.pass('server closed') -}) - -test('should start the server listening on 0.0.0.0 when running in docker', async t => { - t.plan(2) - const isDocker = sinon.stub() - isDocker.returns(true) - - const start = proxyquire('../start', { - 'is-docker': isDocker - }) - - const argv = ['-p', getPort(), './examples/plugin.js'] - const fastify = await start.start(argv) - - t.equal(fastify.server.address().address, '0.0.0.0') - - await fastify.close() - t.pass('server closed') -}) - -test('should start the server listening on 0.0.0.0 when running in kubernetes', async t => { - t.plan(2) - const isKubernetes = sinon.stub() - isKubernetes.returns(true) - - const start = proxyquire('../start', { - './util': { - ...require('../util'), - isKubernetes - } - }) - - const argv = ['-p', getPort(), './examples/plugin.js'] - const fastify = await start.start(argv) - - t.equal(fastify.server.address().address, '0.0.0.0') - - await fastify.close() - t.pass('server closed') -}) - -test('should start the server with watch options that the child process restart when directory changed', { skip: ['win32', 'darwin'].includes(process.platform) }, async (t) => { - t.plan(3) - const tmpjs = path.resolve(baseFilename + '.js') - - const port = getPort() - - await writeFile(tmpjs, 'hello world') - const argv = ['-p', port, '-w', './examples/plugin.js'] - const fastifyEmitter = await start.start(argv) - - t.teardown(async () => { - if (fs.existsSync(tmpjs)) { - fs.unlinkSync(tmpjs) - } - await fastifyEmitter.stop() - }) - - await once(fastifyEmitter, 'ready') - t.pass('should receive ready event') - - const restartPromise = once(fastifyEmitter, 'restart') - await writeFile(tmpjs, 'hello fastify', { flag: 'a+' }) // chokidar watch can't catch change event in CI, but local test is all ok. you can remove annotation in local environment. - t.pass('change tmpjs') - - // this might happen more than once but does not matter in this context - await restartPromise - t.pass('should receive restart event') -}) - -test('should start the server with watch and verbose-watch options that the child process restart when directory changed with console message about changes ', { skip: ['win32', 'darwin'].includes(process.platform) }, async (t) => { - t.plan(4) - - const spy = sinon.spy() - const watch = proxyquire('../lib/watch', { - './utils': { - logWatchVerbose: spy - } - }) - - const start = proxyquire('../start', { - './lib/watch': watch - }) - - const tmpjs = path.resolve(baseFilename + '.js') - - const port = getPort() - - await writeFile(tmpjs, 'hello world') - const argv = ['-p', port, '-w', '--verbose-watch', './examples/plugin.js'] - const fastifyEmitter = await start.start(argv) - - t.teardown(async () => { - if (fs.existsSync(tmpjs)) { - fs.unlinkSync(tmpjs) - } - await fastifyEmitter.stop() - }) - - await once(fastifyEmitter, 'ready') - t.pass('should receive ready event') - - const restartPromise = once(fastifyEmitter, 'restart') - await writeFile(tmpjs, 'hello fastify', { flag: 'a+' }) // chokidar watch can't catch change event in CI, but local test is all ok. you can remove annotation in local environment. - t.pass('change tmpjs') - - // this might happen more than once but does not matter in this context - await restartPromise - t.pass('should receive restart event') - t.ok(spy.args.length > 0, 'should print a console message on file update') -}) - -test('should reload the env on restart when watching', { skip: process.platform === 'win32' }, async (t) => { - const testdir = t.testdir({ - '.env': 'GREETING=world', - 'plugin.js': await readFile(path.join(__dirname, '../examples/plugin-with-env.js')) - }) - - const cwd = process.cwd() - - process.chdir(testdir) - - const port = getPort() - const argv = ['-p', port, '-w', path.join(testdir, 'plugin.js')] - const fastifyEmitter = await requireUncached('../start').start(argv) - - t.teardown(() => { - process.chdir(cwd) - }) - - await once(fastifyEmitter, 'ready') - - const r1 = await sget({ - method: 'GET', - url: `http://localhost:${port}` - }) - - t.equal(r1.response.statusCode, 200) - t.same(JSON.parse(r1.body), { hello: 'world' }) - - await writeFile(path.join(testdir, '.env'), 'GREETING=planet') - - await once(fastifyEmitter, 'restart') - - const r2 = await sget({ - method: 'GET', - url: `http://localhost:${port}` - }) - - t.equal(r2.response.statusCode, 200) - t.same(JSON.parse(r2.body), { hello: 'world' }) /* world because when making a restart the server still passes the arguments that change the environment variable */ - - await fastifyEmitter.stop() -}) - -test('should read env variables from .env file', async (t) => { - const port = getPort() - - const testdir = t.testdir({ - '.env': `FASTIFY_PORT=${port}`, - 'plugin.js': await readFile(path.join(__dirname, '../examples/plugin.js')) - }) - - const cwd = process.cwd() - - process.chdir(testdir) - - t.teardown(() => { - process.chdir(cwd) - }) - - const fastify = await requireUncached('../start').start([path.join(testdir, 'plugin.js')]) - t.equal(fastify.server.address().port, +port) - - const res = await sget({ - method: 'GET', - url: `http://localhost:${port}` - }) - - t.equal(res.response.statusCode, 200) - t.same(JSON.parse(res.body), { hello: 'world' }) - - await fastify.close() -}) - -test('crash on unhandled rejection', t => { - t.plan(1) - - const argv = ['-p', getPort(), './test/data/rejection.js'] - const child = fork(path.join(__dirname, '..', 'start.js'), argv, { silent: true }) - child.on('close', function (code) { - t.equal(code, 1) - }) -}) - -test('should start the server with inspect options and the defalut port is 9320', async t => { - t.plan(3) - - const start = proxyquire('../start', { - 'node:inspector': { - open (p) { - t.equal(p, 9320) - t.pass('inspect open called') - } - } - }) - const argv = ['--d', './examples/plugin.js'] - const fastify = await start.start(argv) - - await fastify.close() - t.pass('server closed') -}) - -test('should start the server with inspect options and use the exactly port', async t => { - t.plan(3) - - const port = getPort() - const start = proxyquire('../start', { - 'node:inspector': { - open (p) { - t.equal(p, Number(port)) - t.pass('inspect open called') - } - } - }) - const argv = ['--d', '--debug-port', port, './examples/plugin.js'] - const fastify = await start.start(argv) - - await fastify.close() - t.pass('server closed') -}) - -test('boolean env are not overridden if no arguments are passed', async t => { - t.plan(1) - - process.env.FASTIFY_OPTIONS = 'true' - - // here the test should fail because of the wrong certificate - // or because the server is booted without the custom options - try { - const argv = ['./examples/plugin-with-options.js'] - await start.start(argv) - t.fail('Custom options') - } catch (e) { - t.pass('Custom options') - } -}) - -test('should support preloading custom module', async t => { - t.plan(2) - - const argv = ['-r', './test/data/custom-require.js', './examples/plugin.js'] - const fastify = await start.start(argv) - t.ok(GLOBAL_MODULE_1) - - await fastify.close() - t.pass('server closed') -}) - -test('should support preloading custom ES module', async t => { - t.plan(2) - - const argv = ['-i', './test/data/custom-import.mjs', './examples/plugin.js'] - const fastify = await start.start(argv) - t.ok(globalThis.GLOBAL_MODULE_3) - - await fastify.close() - t.pass('server closed') -}) - -test('should support preloading multiple custom modules', async t => { - t.plan(3) - - const argv = ['-r', './test/data/custom-require.js', '-r', './test/data/custom-require2.js', './examples/plugin.js'] - const fastify = await start.start(argv) - t.ok(GLOBAL_MODULE_1) - t.ok(GLOBAL_MODULE_2) - - await fastify.close() - t.pass('server closed') -}) - -test('should support preloading multiple custom ES modules', async t => { - t.plan(3) - - const argv = ['-i', './test/data/custom-import.mjs', '-i', './test/data/custom-import2.mjs', './examples/plugin.js'] - const fastify = await start.start(argv) - t.ok(GLOBAL_MODULE_3) - t.ok(GLOBAL_MODULE_4) - await fastify.close() - t.pass('server closed') -}) - -test('preloading custom module with empty and trailing require flags should not throw', async t => { - t.plan(2) - - const argv = ['-r', './test/data/custom-require.js', '-r', '', './examples/plugin.js', '-r'] - const fastify = await start.start(argv) - t.ok(GLOBAL_MODULE_1) - - await fastify.close() - t.pass('server closed') -}) - -test('preloading custom ES module with empty and trailing import flags should not throw', async t => { - t.plan(2) - - const argv = ['-i', './test/data/custom-import.mjs', '-i', '', './examples/plugin.js', '-i'] - const fastify = await start.start(argv) - t.ok(GLOBAL_MODULE_3) - - await fastify.close() - t.pass('server closed') -}) - -test('preloading custom module that is not found should throw', async t => { - t.plan(2) - - const oldStop = start.stop - t.teardown(() => { start.stop = oldStop }) - start.stop = function (err) { - t.ok(/Cannot find module/.test(err.message), err.message) - } - - const argv = ['-r', './test/data/require-missing.js', './examples/plugin.js'] - const fastify = await start.start(argv) - - await fastify.close() - t.pass('server closed') -}) - -test('preloading custom ES module that is not found should throw', async t => { - t.plan(2) - - const oldStop = start.stop - t.teardown(() => { start.stop = oldStop }) - start.stop = function (err) { - t.ok(/Cannot find module/.test(err.message), err.message) - } - - const argv = ['-i', './test/data/import-missing.mjs', './examples/plugin.js'] - const fastify = await start.start(argv) - - await fastify.close() - t.pass('server closed') -}) - -test('preloading custom module should be done before starting server', async t => { - t.plan(4) - - const argv = ['./examples/plugin-with-preloaded.js'] - const fastify = await start.start(argv) - - const { response, body } = await sget({ - method: 'GET', - url: `http://localhost:${fastify.server.address().port}` - }) - - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { hasPreloaded: true }) - - await fastify.close() - t.pass('server closed') -}) - -test('should support custom logger configuration', async t => { - t.plan(2) - - const argv = ['-L', './test/data/custom-logger.js', './examples/plugin.js'] - const fastify = await start.start(argv) - t.ok(fastify.log.test) - - await fastify.close() - t.pass('server closed') -}) - -test('preloading a built-in module works', async t => { - t.plan(1) - - const argv = ['-r', 'path', './examples/plugin.js'] - const fastify = await start.start(argv) - await fastify.close() - t.pass('server closed') -}) - -test('preloading a module in node_modules works', async t => { - t.plan(1) - - const argv = ['-r', 'tap', './examples/plugin.js'] - const fastify = await start.start(argv) - await fastify.close() - t.pass('server closed') -}) - -test('should throw on logger configuration module not found', async t => { - t.plan(2) - - const oldStop = start.stop - t.teardown(() => { start.stop = oldStop }) - start.stop = function (err) { - t.ok(/Cannot find module/.test(err.message), err.message) - } - - const argv = ['-L', './test/data/missing.js', './examples/plugin.js'] - const fastify = await start.start(argv) - - await fastify.close() - t.pass('server closed') -}) - -test('should throw on async plugin with one argument', async t => { - t.plan(1) - - const oldStop = start.stop - t.teardown(() => { start.stop = oldStop }) - start.stop = function (err) { - t.ok(/Async\/Await plugin function should contain 2 arguments./.test(err.message), err.message) - } - - const argv = ['./test/data/async-plugin-with-one-argument.js'] - await start.start(argv) -}) - -test('should start fastify with custom plugin options with a ESM typescript compiled plugin', { skip: !moduleSupport }, async t => { - t.plan(4) - - const argv = [ - '-p', - getPort(), - './examples/ts-plugin-with-custom-options.mjs', - '--', - '-abc', - '--hello', - 'world' - ] - const fastify = await start.start(argv) - - const { response, body } = await sget({ - method: 'GET', - url: `http://localhost:${fastify.server.address().port}` - }) - - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { - a: true, - b: true, - c: true, - hello: 'world' - }) - - await fastify.close() - t.pass('server closed') - t.end() -}) - -test('should start fastify with custom plugin default options with a ESM typescript compiled plugin', { skip: !moduleSupport }, async t => { - t.plan(4) - - const argv = [ - '-o', - '-p', - getPort(), - './examples/ts-plugin-with-custom-options.mjs' - ] - const fastify = await start.start(argv) - - const { response, body } = await sget({ - method: 'GET', - url: `http://localhost:${fastify.server.address().port}` - }) - - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { - hello: 'test' - }) - - await fastify.close() - t.pass('server closed') - t.end() -}) - -test('should throw an error when loading ESM typescript compiled plugin and ESM is not supported', { skip: moduleSupport }, async t => { - t.plan(1) - - const oldStop = start.stop - t.teardown(() => { start.stop = oldStop }) - start.stop = function (err) { - t.ok(/Your version of node does not support ES modules./.test(err.message), err.message) - } - - const argv = ['./examples/ts-plugin-with-custom-options.mjs'] - await start.start(argv) - t.end() -}) - -test('should start fastify with custom plugin options with a ESM plugin with package.json "type":"module"', { skip: !moduleSupport }, async t => { - t.plan(4) - - const argv = [ - '-p', - getPort(), - './examples/package-type-module/ESM-plugin-with-custom-options.js', - '--', - '-abc', - '--hello', - 'world' - ] - const fastify = await start.start(argv) - - const { response, body } = await sget({ - method: 'GET', - url: `http://localhost:${fastify.server.address().port}` - }) - - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { - a: true, - b: true, - c: true, - hello: 'world' - }) - - await fastify.close() - t.pass('server closed') - t.end() -}) - -test('should start fastify with custom server options (ignoreTrailingSlash) with a ESM plugin with package.json "type":"module"', { skip: !moduleSupport }, async t => { - t.plan(5) - - const argv = [ - '-p', - getPort(), - './examples/package-type-module/ESM-plugin-with-custom-server-options.js', - '--options' - ] - const fastify = await start.start(argv) - - const { response, body } = await sget({ - method: 'GET', - url: `http://localhost:${fastify.server.address().port}/foo` - }) - - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - - const { response: response2, body: body2 } = await sget({ - method: 'GET', - url: `http://localhost:${fastify.server.address().port}/foo/` - }) - - t.equal(response2.statusCode, 200) - t.equal(response2.headers['content-length'], '' + body2.length) - - await fastify.close() - t.pass('server closed') - t.end() -}) - -test('should start fastify with custom plugin options with a CJS plugin with package.json "type":"module"', { skip: !moduleSupport }, async t => { - t.plan(4) - - const argv = [ - '-p', - getPort(), - './examples/package-type-module/CJS-plugin-with-custom-options.cjs', - '--', - '-abc', - '--hello', - 'world' - ] - const fastify = await start.start(argv) - - const { response, body } = await sget({ - method: 'GET', - url: `http://localhost:${fastify.server.address().port}` - }) - - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { - a: true, - b: true, - c: true, - hello: 'world' - }) - - await fastify.close() - t.pass('server closed') - t.end() -}) - -test('should throw error for invalid fastify plugin (object)', async t => { - t.plan(1) - try { - const port = getPort() - const argv = ['-p', port, '-T', '100', './test/data/object.js'] - await start.start(argv) - t.fail('should not start') - } catch (err) { - t.equal(err.code, 'AVV_ERR_PLUGIN_NOT_VALID') - } -}) diff --git a/test/swaggerplugindir/package.json b/test/swaggerplugindir/package.json deleted file mode 100644 index ec4153a2..00000000 --- a/test/swaggerplugindir/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "swaggerplugindir", - "version": "1.0.0", - "description": "test for generator-swagger", - "main": "plugin.js", - "scripts": { - "test": "" - }, - "dependencies": { - "fastify-plugin": "^1.4.0" - }, - "author": "", - "license": "ISC" -} diff --git a/test/swaggerplugindir/plugin.js b/test/swaggerplugindir/plugin.js deleted file mode 100644 index fee6af1e..00000000 --- a/test/swaggerplugindir/plugin.js +++ /dev/null @@ -1,61 +0,0 @@ -'use strict' - -const fp = require('fastify-plugin') - -module.exports = fp(function (fastify, opts, next) { - fastify.decorate('swagger', function (opts) { - if (opts && opts.yaml) { - return `\ -openapi: 3.0.3 -info: -version: 8.1.0 -title: "@fastify/swagger" -components: -schemas: {} -paths: -"/": - get: - responses: - '200': - description: Default Response -"/example/": - get: - responses: - '200': - description: Default Response -` - } else { - return { - openapi: '3.0.3', - info: { - version: '8.1.0', - title: '@fastify/swagger' - }, - components: { - schemas: {} - }, - paths: { - '/': { - get: { - responses: { - 200: { - description: 'Default Response' - } - } - } - }, - '/example/': { - get: { - responses: { - 200: { - description: 'Default Response' - } - } - } - } - } - } - } - }) - next() -}) diff --git a/test/templates/app-ts.test.ts b/test/templates/app-ts.test.ts deleted file mode 100644 index 3e396ad3..00000000 --- a/test/templates/app-ts.test.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { fastify } from 'fastify' -import { test } from 'tap' -const sgetOriginal = require('simple-get').concat - -import appDefault, { app } from '../../templates/app-ts/src/app' -import {AddressInfo} from "net"; - -const sget = (opts: Record): Record => { - return new Promise((resolve, reject) => { - sgetOriginal(opts, (err: Error, response: any, body: any) => { - if (err) return reject(err) - return resolve({ response, body }) - }) - }) -} - -test('should print routes for TS app', async t => { - t.plan(4) - - const fastifyApp = fastify({}, ); - await app(fastifyApp, {}); - await fastifyApp.ready(); - await fastifyApp.listen({ port: 3000 }) - - const { response, body } = await sget({ - method: 'GET', - url: `http://localhost:${(fastifyApp.server.address() as AddressInfo).port}` - }) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { root: true }) - - await fastifyApp.close(); - t.pass('server closed') -}) - -test('should print routes for default TS app', async t => { - t.plan(4) - - const fastifyApp = fastify({}, ); - await appDefault(fastifyApp, {}); - await fastifyApp.ready(); - await fastifyApp.listen({ port: 3000 }) - - const { response, body } = await sget({ - method: 'GET', - url: `http://localhost:${(fastifyApp.server.address() as AddressInfo).port}` - }) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-length'], '' + body.length) - t.same(JSON.parse(body), { root: true }) - - await fastifyApp.close(); - t.pass('server closed') -}) diff --git a/test/tsconfig.json b/test/tsconfig.json new file mode 100644 index 00000000..95898fce --- /dev/null +++ b/test/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../tsconfig", + "compilerOptions": { + "noEmit": true + }, + "references": [ + {"path": ".."} + ] +} diff --git a/test/watch.test.js b/test/watch.test.js deleted file mode 100644 index 3b8bfabd..00000000 --- a/test/watch.test.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict' - -const { arrayToRegExp } = require('../lib/watch/utils') - -const t = require('tap') -const test = t.test - -test('should equal expect RegExp', t => { - t.plan(1) - - const expectRegExp = /(node_modules|build|dist|\.git|bower_components|logs)/ - const regExp = arrayToRegExp(['node_modules', 'build', 'dist', '.git', 'bower_components', 'logs']) - - t.same(regExp, expectRegExp) -}) diff --git a/tsconfig.json b/tsconfig.json index 4ccf1bdb..e144b788 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,13 +1,15 @@ { - "extends": "fastify-tsconfig", - "compilerOptions": { - "esModuleInterop": true, - "noEmit": true - }, - "include": [ - "routes/**/*.ts", - "plugins/**/*.ts", - "test/**/*.ts", - "app.ts" - ] + "compilerOptions": { + "declaration": true, + "module": "Node16", + "outDir": "dist", + "rootDir": "src", + "strict": true, + "target": "es2022", + "moduleResolution": "node16" + }, + "include": ["./src/**/*"], + "ts-node": { + "esm": true + } } diff --git a/util.js b/util.js deleted file mode 100644 index 74f09717..00000000 --- a/util.js +++ /dev/null @@ -1,124 +0,0 @@ -'use strict' - -const fs = require('node:fs') -const path = require('node:path') -const url = require('node:url') -const semver = require('semver') -const pkgUp = require('pkg-up') -const resolveFrom = require('resolve-from') - -const moduleSupport = semver.satisfies(process.version, '>= 14 || >= 12.17.0 < 13.0.0') - -function exit (message) { - if (message instanceof Error) { - console.log(message) - return process.exit(1) - } else if (message) { - console.log(`Warn: ${message}`) - return process.exit(1) - } - - process.exit() -} - -function requireModule (moduleName) { - if (fs.existsSync(moduleName)) { - const moduleFilePath = path.resolve(moduleName) - return require(moduleFilePath) - } else { - return require(moduleName) - } -} - -async function requireESModule (moduleName) { - if (fs.existsSync(moduleName)) { - const moduleFilePath = path.resolve(moduleName) - return import(url.pathToFileURL(moduleFilePath)) - } else { - return import(moduleName) - } -} - -function requireFastifyForModule (modulePath) { - try { - const basedir = path.resolve(process.cwd(), modulePath) - const module = require(resolveFrom.silent(basedir, 'fastify') || 'fastify') - - return { module } - } catch (e) { - exit('unable to load fastify module') - } -} - -function isInvalidAsyncPlugin (plugin) { - return plugin && plugin.length !== 2 && plugin.constructor.name === 'AsyncFunction' -} - -async function getPackageType (cwd) { - const nearestPackage = await pkgUp({ cwd }) - if (nearestPackage) { - return require(nearestPackage).type - } -} - -function getScriptType (fname, packageType) { - const modulePattern = /\.mjs$/i - const commonjsPattern = /\.cjs$/i - return (modulePattern.test(fname) ? 'module' : commonjsPattern.test(fname) ? 'commonjs' : packageType) || 'commonjs' -} - -async function requireServerPluginFromPath (modulePath) { - const resolvedModulePath = path.resolve(process.cwd(), modulePath) - - if (!fs.existsSync(resolvedModulePath)) { - throw new Error(`${resolvedModulePath} doesn't exist within ${process.cwd()}`) - } - - const packageType = await getPackageType(resolvedModulePath) - const type = getScriptType(resolvedModulePath, packageType) - - let serverPlugin - if (type === 'module') { - if (moduleSupport) { - serverPlugin = await import(url.pathToFileURL(resolvedModulePath).href) - } else { - throw new Error(`fastify-cli cannot import plugin at '${resolvedModulePath}'. Your version of node does not support ES modules. To fix this error upgrade to Node 14 or use CommonJS syntax.`) - } - } else { - serverPlugin = require(resolvedModulePath) - } - - if (isInvalidAsyncPlugin(type === 'commonjs' ? serverPlugin : serverPlugin.default)) { - throw new Error('Async/Await plugin function should contain 2 arguments. ' + - 'Refer to documentation for more information.') - } - - return serverPlugin -} - -function showHelpForCommand (commandName) { - const helpFilePath = path.join(__dirname, 'help', `${commandName}.txt`) - - try { - console.log(fs.readFileSync(helpFilePath, 'utf8')) - exit() - } catch (e) { - exit(`unable to get help for command "${commandName}"`) - } -} - -function isKubernetes () { - // Detection based on https://kubernetes.io/docs/reference/kubectl/#in-cluster-authentication-and-namespace-overrides - return process.env.KUBERNETES_SERVICE_HOST !== undefined || - fs.existsSync('/run/secrets/kubernetes.io/serviceaccount/token') -} - -module.exports = { - isKubernetes, - exit, - requireModule, - requireESModule, - requireFastifyForModule, - showHelpForCommand, - requireServerPluginFromPath -}