diff --git a/.credo.exs b/.credo.exs index bd728584..61a8b4d6 100644 --- a/.credo.exs +++ b/.credo.exs @@ -31,7 +31,13 @@ "apps/*/test/", "apps/*/web/" ], - excluded: [~r"/_build/", ~r"/deps/", ~r"/node_modules/"] + excluded: [ + ~r"/_build/", + ~r"/deps/", + ~r"/node_modules/", + ~r"/lib/nimble_template/addons/addon.ex", + ~r"/test/support/addon_case.ex" + ] }, # # Load and configure plugins here: @@ -87,7 +93,7 @@ # If you don't want TODO comments to cause `mix credo` to fail, just # set this value to 0 (zero). # - {Credo.Check.Design.TagTODO, [exit_status: 2]}, + {Credo.Check.Design.TagTODO, [exit_status: 0]}, {Credo.Check.Design.TagFIXME, []}, # diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 20b309db..01a7b0ac 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -2,12 +2,12 @@ https://github.com/nimblehq/elixir-templates/issues/?? ## What happened -Describe the big picture of your changes here to communicate to the team why we should accept this pull request. - +Describe the big picture of your changes here to communicate to the team why we should accept this pull request. + ## Insight Describe in details how to test the changes, which solution you tried but did not go with, referenced documentation is welcome as well. - + ## Proof Of Work Show us the implementation: screenshots, gif, etc. diff --git a/.github/wiki/Docker.md b/.github/wiki/Docker.md new file mode 100644 index 00000000..c0775c75 --- /dev/null +++ b/.github/wiki/Docker.md @@ -0,0 +1,32 @@ +Prefer to use the image from the Hexpm team instead of the Docker team, the reason below +was mentioned on the [Elixir Forums](https://elixirforum.com/t/yet-another-elixir-and-erlang-docker-image/28740): + +- Image tags are not immutable. +- Delay in the availability of new versions. +- Cannot pick the combination of Elixir and Erlang versions you want. + +Refer: + +- [hexpm/elixir](https://hub.docker.com/r/hexpm/elixir) +- [hexpm/erlang](https://hub.docker.com/r/hexpm/erlang) + +Example Dockerfile: + +```dockerfile +FROM hexpm/elixir:1.11.0-erlang-23.1.1-alpine-3.12.0 AS build + +.... +FROM alpine:3.12.0 AS app +... + +COPY --from=build --chown=nobody:nobody /app/_build/prod/rel/app ./ +... +``` + +Based on the image tag, it's clear which versions we use in the Build Release step + +- Elixir: 1.11.0 +- Erlang: 23.1.1 +- Alpine: 3.12.0 + +Easily we can choose the OS to run the app on, that is `alpine:3.12.0` diff --git a/.github/wiki/Home.md b/.github/wiki/Home.md new file mode 100644 index 00000000..f24c7d3a --- /dev/null +++ b/.github/wiki/Home.md @@ -0,0 +1 @@ +Phoenix/Mix template for projects at Nimble. diff --git a/.github/wiki/Testing.md b/.github/wiki/Testing.md new file mode 100644 index 00000000..8c6760fe --- /dev/null +++ b/.github/wiki/Testing.md @@ -0,0 +1,135 @@ +NimbleTemplate uses Github Actions as the CI, the workflow files are located under the [.github/workflows/](https://github.com/nimblehq/elixir-templates/tree/develop/.github/workflows) directory. + +There are 2 types of tests, **Template tests** and **Variant tests** + +## 1/ Template test + +All test files are located under `test/` directory. + +```text +. +├── ... +├── test +│ ├── ... +│ ├── nimble_template +│ │ └── addons +│ │ │ ├── ... +│ │ │ ├── common_mix_phoenix_addon_test.exs +│ │ │ └── variants +│ │ │ │ └── mix +│ │ │ │ │ ├── ... +│ │ │ │ │ └── mix_addon_test.exs +│ │ │ │ └── phoenix +│ │ │ │ │ └── common_phoenix_addon_test.exs +│ │ │ │ │ └── api +│ │ │ │ │ │ ├── ... +│ │ │ │ │ │ └── api_addon_test.exs +│ │ │ │ │ └── live +│ │ │ │ │ │ ├── ... +│ │ │ │ │ │ └── live_addon_test.exs +│ │ │ │ │ └── web +│ │ │ │ │ │ ├── ... +│ │ │ │ │ │ └── web_addon_test.exs +``` + +## 2/ Variant test + +NimbleTemplate supports 4 variants: + +- Mix +- Web +- API +- Live + +### 2.1/ Mix project + +A Mix project could be either a Standard project or a Custom project. + +- `mix new awesome_project` +- `mix new awesome_project --module=CustomModuleName` +- `mix new awesome_project --app=custom_otp_app_name` +- `mix new awesome_project --module=CustomModuleName --app=custom_otp_app_name` + +Each project could include a `supervision tree`. + +- `mix new awesome_project` +- `mix new awesome_project --sup` +- `mix new awesome_project --module=CustomModuleName --app=custom_otp_app_name` +- `mix new awesome_project --module=CustomModuleName --app=custom_otp_app_name --sup` + +Adding it all together, totals to 4 variant test cases. + +- Applying the `Mix variant` to a `Standard Mix project` +- Applying the `Mix variant` to a `Custom Mix project` +- Applying the `Mix variant` to a `Standard Mix project with the --sup option` +- Applying the `Mix variant` to a `Custom Mix project with the --sup option` + +### 2.2/ Phoenix project + +A Phoenix project could be either a Web, LiveView, or API. + +- Web variants support HTML and Assets. + +```bash +mix phx.new awesome_project --no-live +``` + +- LiveView projects include HTML and Assets configuration. + +```bash +mix phx.new awesome_project +``` + +- API variants do NOT support HTML and Assets configuration. + +```bash +mix phx.new awesome_project --no-html --no-assets +``` + +- Custom project variants allow us to modify the app name or module name. + +```bash +# Use CustomModuleName +mix phx.new awesome_project --module=CustomModuleName + +# Use custom OTP app name +mix phx.new awesome_project --app=custom_otp_app_name + +# Use custom module and app name +mix phx.new awesome_project --module=CustomModuleName --app=custom_otp_app_name +``` + +So it ends up with 6 project types: + +Web project + +- Standard (`mix phx.new awesome_project --no-live`) +- Custom (`mix phx.new awesome_project --no-live --module=CustomModuleName --app=custom_otp_app_name`) + +API project + +- Standard (`mix phx.new awesome_project --no-html --no-assets`) +- Custom (`mix phx.new awesome_project --no-html --no-assets --module=CustomModuleName --app=custom_otp_app_name`) + +LiveView project + +- Standard (`mix phx.new awesome_project`) +- Custom (`mix phx.new awesome_project --module=CustomModuleName --app=custom_otp_app_name`) + +Putting it all together, there are 8 variants of test cases. + +- Applying the `API variant` to a `Standard Web project` +- Applying the `API variant` to a `Custom Web project` +- Applying the `API variant` to a `Standard API project` +- Applying the `API variant` to a `Custom API project` +- Applying the `Web variant` to a `Standard Web project` +- Applying the `Web variant` to a `Custom Web project` +- Applying the `Live variant` to a `Standard LiveView project` +- Applying the `Live variant` to a `Custom LiveView project` + +## Note + +Make sure the Phoenix version is the same between local development and CI, otherwise there will be some error in the unit test. + +- Phoenix version on CI can be found in: `.github/workflows/test_template.yml` +- Install a correct Phoenix version on local via command: `mix archive.install hex phx_new __PHOENIX_VERSION__` diff --git a/.github/wiki/_Footer.md b/.github/wiki/_Footer.md new file mode 100644 index 00000000..3eac4808 --- /dev/null +++ b/.github/wiki/_Footer.md @@ -0,0 +1 @@ +**Developed by [Nimble](https://nimblehq.co/)** diff --git a/.github/wiki/_Sidebar.md b/.github/wiki/_Sidebar.md new file mode 100644 index 00000000..a5d6967b --- /dev/null +++ b/.github/wiki/_Sidebar.md @@ -0,0 +1,8 @@ +## Table of Contents + +- [[Home]] + +## Architecture + +- [[Docker]] +- [[Testing]] diff --git a/lib/nimble_template/addons/variants/phoenix/api/.keep b/.github/wiki/assets/images/.keep similarity index 100% rename from lib/nimble_template/addons/variants/phoenix/api/.keep rename to .github/wiki/assets/images/.keep diff --git a/.github/workflows/apply_api_variant.yml b/.github/workflows/apply_api_variant.yml new file mode 100644 index 00000000..3dc6f391 --- /dev/null +++ b/.github/workflows/apply_api_variant.yml @@ -0,0 +1,32 @@ +name: Apply API variant + +on: push + +jobs: + standard_api_project: + name: Test on a Standard API project + uses: ./.github/workflows/reusable_phoenix_project.yml + with: + new_project_options: "--no-html --no-assets" + variant: "api" + + non-standard_api_project: + name: Test on a Non-Standard API project + uses: ./.github/workflows/reusable_phoenix_project.yml + with: + new_project_options: "--no-html --no-assets --module=SampleCustomModule --app=sample_custom_app" + variant: "api" + + standard_web_project: + name: Test on a Standard Web project + uses: ./.github/workflows/reusable_phoenix_project.yml + with: + new_project_options: "" + variant: "api" + + non-standard_web_project: + name: Test on a Non-Standard Web project + uses: ./.github/workflows/reusable_phoenix_project.yml + with: + new_project_options: "--module=SampleCustomModule --app=sample_custom_app" + variant: "api" diff --git a/.github/workflows/apply_live_variant.yml b/.github/workflows/apply_live_variant.yml new file mode 100644 index 00000000..32df74e1 --- /dev/null +++ b/.github/workflows/apply_live_variant.yml @@ -0,0 +1,18 @@ +name: Apply Live variant + +on: push + +jobs: + standard_project: + name: Test on a Standard Live project + uses: ./.github/workflows/reusable_phoenix_project.yml + with: + new_project_options: "" + variant: "live" + + non-standard_project: + name: Test on a Non-Standard Live project + uses: ./.github/workflows/reusable_phoenix_project.yml + with: + new_project_options: "--module=SampleCustomModule --app=sample_custom_app" + variant: "live" diff --git a/.github/workflows/apply_mix_variant.yml b/.github/workflows/apply_mix_variant.yml new file mode 100644 index 00000000..079e3fdc --- /dev/null +++ b/.github/workflows/apply_mix_variant.yml @@ -0,0 +1,28 @@ +name: Apply Mix variant + +on: push + +jobs: + standard_mix_project: + name: Test on a Standard Mix project + uses: ./.github/workflows/reusable_mix_project.yml + with: + new_project_options: "" + + non-standard_mix_project: + name: Test on a Non-Standard Mix project + uses: ./.github/workflows/reusable_mix_project.yml + with: + new_project_options: "--module=SampleCustomModule --app=sample_custom_app" + + standard_mix_supervision_project: + name: Test on a Standard Supervision Mix project + uses: ./.github/workflows/reusable_mix_project.yml + with: + new_project_options: "--sup" + + non-standard_mix_supervision_project: + name: Test on a Non-Standard Supervision Mix project + uses: ./.github/workflows/reusable_mix_project.yml + with: + new_project_options: "--sup --module=SampleCustomModule --app=sample_custom_app" diff --git a/.github/workflows/apply_web_variant.yml b/.github/workflows/apply_web_variant.yml new file mode 100644 index 00000000..4f71bd8f --- /dev/null +++ b/.github/workflows/apply_web_variant.yml @@ -0,0 +1,18 @@ +name: Apply Web variant + +on: push + +jobs: + standard_project: + name: Test on a Standard Web project + uses: ./.github/workflows/reusable_phoenix_project.yml + with: + new_project_options: "--no-live" + variant: "web" + + non-standard_project: + name: Test on a Non-Standard Web project + uses: ./.github/workflows/reusable_phoenix_project.yml + with: + new_project_options: "--no-live --module=SampleCustomModule --app=sample_custom_app" + variant: "web" diff --git a/.github/workflows/publish_to_hex_pm.yml b/.github/workflows/publish_to_hex_pm.yml index 5614d9fb..5a0718bb 100644 --- a/.github/workflows/publish_to_hex_pm.yml +++ b/.github/workflows/publish_to_hex_pm.yml @@ -1,4 +1,4 @@ -name: "Publish to Hex.pm" +name: Publish to Hex package manager on: workflow_run: @@ -11,24 +11,30 @@ on: workflow_dispatch: env: - OTP_VERSION: 23.3 - ELIXIR_VERSION: 1.11.4 + OTP_VERSION: 24.2.2 + ELIXIR_VERSION: 1.13.3 -jobs: +jobs: publish: + name: Publish hex package + runs-on: ubuntu-latest + if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }} + steps: - name: Cancel Previous Runs uses: styfle/cancel-workflow-action@0.8.0 with: access_token: ${{ github.token }} - - - uses: actions/checkout@v2 + + - name: Checkout repository + uses: actions/checkout@v2 with: ref: ${{ github.event.workflow_run.head_branch || github.ref }} - - uses: erlef/setup-elixir@v1 + - name: Set up Elixir ${{ env.ELIXIR_VERSION }} and OTP ${{ env.OTP_VERSION }} + uses: erlef/setup-elixir@v1 with: otp-version: ${{ env.OTP_VERSION }} elixir-version: ${{ env.ELIXIR_VERSION }} diff --git a/.github/workflows/publish_wiki.yml b/.github/workflows/publish_wiki.yml new file mode 100644 index 00000000..7439d0e4 --- /dev/null +++ b/.github/workflows/publish_wiki.yml @@ -0,0 +1,18 @@ +name: Publish Wiki + +on: + push: + branches: + - develop + paths: + - .github/wiki/** + +jobs: + publish: + name: Publish wiki + uses: nimblehq/github-actions-workflows/.github/workflows/publish_wiki.yml@0.1.0 + with: + USER_NAME: github-wiki-action + USER_EMAIL: dev@nimblehq.co + secrets: + USER_TOKEN: ${{ secrets.WIKI_ACTION_TOKEN }} diff --git a/.github/workflows/reusable_mix_project.yml b/.github/workflows/reusable_mix_project.yml new file mode 100644 index 00000000..e4340afc --- /dev/null +++ b/.github/workflows/reusable_mix_project.yml @@ -0,0 +1,56 @@ +name: Reusable Mix project + +on: + workflow_call: + inputs: + new_project_options: + required: true + type: string + +env: + OTP_VERSION: 24.2.2 + ELIXIR_VERSION: 1.13.3 + BASE_PROJECT_DIRECTORY: sample_project + MIX_ENV: test + +jobs: + unit_test: + name: Unit test + runs-on: ubuntu-latest + + steps: + - name: Cancel Previous Runs + uses: styfle/cancel-workflow-action@0.8.0 + with: + access_token: ${{ github.token }} + + - name: Checkout repository + uses: actions/checkout@v2 + with: + ref: ${{ github.head_ref }} + + - name: Set up Elixir ${{ env.ELIXIR_VERSION }} and OTP ${{ env.OTP_VERSION }} + uses: erlef/setup-elixir@v1 + with: + otp-version: ${{ env.OTP_VERSION }} + elixir-version: ${{ env.ELIXIR_VERSION }} + + - name: Create Mix project + run: make create_mix_project PROJECT_DIRECTORY=$BASE_PROJECT_DIRECTORY OPTIONS="${{ inputs.new_project_options }}" + + - name: Apply Mix template + run: make apply_mix_template PROJECT_DIRECTORY=$BASE_PROJECT_DIRECTORY + + # Verify the new project after applying the nimble_template + + - name: Install Elixir Dependencies + run: cd $BASE_PROJECT_DIRECTORY && mix deps.get + + - name: Compile dependencies + run: cd $BASE_PROJECT_DIRECTORY && mix compile --warnings-as-errors --all-warnings + + - name: Run mix codebase + run: cd $BASE_PROJECT_DIRECTORY && mix codebase + + - name: Run mix test + run: cd $BASE_PROJECT_DIRECTORY && mix test diff --git a/.github/workflows/reusable_phoenix_project.yml b/.github/workflows/reusable_phoenix_project.yml new file mode 100644 index 00000000..8c71fb51 --- /dev/null +++ b/.github/workflows/reusable_phoenix_project.yml @@ -0,0 +1,100 @@ +name: Reusable Phoenix project + +on: + workflow_call: + inputs: + new_project_options: + required: true + type: string + variant: + required: true + type: string + +env: + OTP_VERSION: 24.2.2 + ELIXIR_VERSION: 1.13.3 + PHOENIX_VERSION: 1.6.6 + NODE_VERSION: 16 + BASE_PROJECT_DIRECTORY: sample_project + DB_HOST: localhost + +jobs: + unit_test: + name: Unit test + runs-on: ubuntu-latest + + services: + db: + image: postgres:12.3 + ports: ['5432:5432'] + env: + POSTGRES_PASSWORD: postgres + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + steps: + - name: Cancel Previous Runs + uses: styfle/cancel-workflow-action@0.8.0 + with: + access_token: ${{ github.token }} + + - name: Checkout repository + uses: actions/checkout@v2 + with: + ref: ${{ github.head_ref }} + + - name: Set up Elixir ${{ env.ELIXIR_VERSION }} and OTP ${{ env.OTP_VERSION }} + uses: erlef/setup-elixir@v1 + with: + otp-version: ${{ env.OTP_VERSION }} + elixir-version: ${{ env.ELIXIR_VERSION }} + + - name: Set up Node ${{ env.NODE_VERSION }} + uses: actions/setup-node@v2.1.5 + with: + node-version: ${{ env.NODE_VERSION }} + + - name: Install Phoenix ${{ env.PHOENIX_VERSION }} + run: make install_phoenix PHOENIX_VERSION=${{ env.PHOENIX_VERSION }} + + - name: Create a new project + run: printf "Y\n" | make create_phoenix_project PROJECT_DIRECTORY=$BASE_PROJECT_DIRECTORY OPTIONS="${{ inputs.new_project_options }}" + + - name: Apply ${{ inputs.variant }} variant into the new project + run: make apply_phoenix_template PROJECT_DIRECTORY=$BASE_PROJECT_DIRECTORY VARIANT="${{ inputs.variant }}" + + # Verify the new project after applying the nimble_template + + - name: Install Elixir Dependencies + run: cd $BASE_PROJECT_DIRECTORY && mix deps.get + + - name: Compile dependencies + run: cd $BASE_PROJECT_DIRECTORY && mix compile --warnings-as-errors --all-warnings + + - name: Install Node Dependencies + if: ${{ inputs.variant != 'api' }} + run: cd $BASE_PROJECT_DIRECTORY && npm install --prefix assets + + - name: Run mix ecto.create + run: cd $BASE_PROJECT_DIRECTORY && mix ecto.create + env: + MIX_ENV: test + + - name: Run mix ecto.migrate + run: cd $BASE_PROJECT_DIRECTORY && mix ecto.migrate + env: + MIX_ENV: test + + - name: Run mix codebase + run: cd $BASE_PROJECT_DIRECTORY && mix codebase + + - name: Run mix test + run: cd $BASE_PROJECT_DIRECTORY && mix coverage + + - name: Remove nimble_template dependency + run: make remove_nimble_template PROJECT_DIRECTORY=$BASE_PROJECT_DIRECTORY + + - name: Test Production Docker image build + run: cd $BASE_PROJECT_DIRECTORY && docker-compose build diff --git a/.github/workflows/test_api_variant_on_custom_api_project.yml b/.github/workflows/test_api_variant_on_custom_api_project.yml deleted file mode 100644 index 146f401e..00000000 --- a/.github/workflows/test_api_variant_on_custom_api_project.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: "Test API variant on custom API project" - -on: push - -env: - OTP_VERSION: 23.3 - ELIXIR_VERSION: 1.11.4 - PHOENIX_VERSION: 1.5.7 - -jobs: - unit_test: - runs-on: ubuntu-latest - - services: - db: - image: postgres:12.3 - ports: ['5432:5432'] - env: - POSTGRES_PASSWORD: postgres - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - - steps: - - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.8.0 - with: - access_token: ${{ github.token }} - - - uses: actions/checkout@v2 - with: - ref: ${{ github.head_ref }} - - - uses: erlef/setup-elixir@v1 - with: - otp-version: ${{ env.OTP_VERSION }} - elixir-version: ${{ env.ELIXIR_VERSION }} - - - name: Cache Elixir build - uses: actions/cache@v2 - with: - path: | - _build - deps - key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} - restore-keys: | - ${{ runner.os }}-mix- - - - uses: nimblehq/elixir-templates@composite_1.4 - with: - new_project_options: '--no-html --no-webpack --module=CustomModule --app=custom_app' - variant: 'api' diff --git a/.github/workflows/test_api_variant_on_standard_api_project.yml b/.github/workflows/test_api_variant_on_standard_api_project.yml deleted file mode 100644 index 9841036c..00000000 --- a/.github/workflows/test_api_variant_on_standard_api_project.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: "Test API variant on standard API project" - -on: push - -env: - OTP_VERSION: 23.3 - ELIXIR_VERSION: 1.11.4 - PHOENIX_VERSION: 1.5.7 - -jobs: - unit_test: - runs-on: ubuntu-latest - - services: - db: - image: postgres:12.3 - ports: ['5432:5432'] - env: - POSTGRES_PASSWORD: postgres - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - - steps: - - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.8.0 - with: - access_token: ${{ github.token }} - - - uses: actions/checkout@v2 - with: - ref: ${{ github.head_ref }} - - - uses: erlef/setup-elixir@v1 - with: - otp-version: ${{ env.OTP_VERSION }} - elixir-version: ${{ env.ELIXIR_VERSION }} - - - name: Cache Elixir build - uses: actions/cache@v2 - with: - path: | - _build - deps - key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} - restore-keys: | - ${{ runner.os }}-mix- - - - uses: nimblehq/elixir-templates@composite_1.4 - with: - new_project_options: '--no-html --no-webpack' - variant: 'api' diff --git a/.github/workflows/test_live_variant_on_custom_liveview_project.yml b/.github/workflows/test_live_variant_on_custom_liveview_project.yml deleted file mode 100644 index 5cc4dd99..00000000 --- a/.github/workflows/test_live_variant_on_custom_liveview_project.yml +++ /dev/null @@ -1,68 +0,0 @@ -name: "Test Live variant on custom LiveView project" - -on: push - -env: - OTP_VERSION: 23.3 - ELIXIR_VERSION: 1.11.4 - PHOENIX_VERSION: 1.5.7 - NODE_VERSION: 14 - -jobs: - unit_test: - runs-on: ubuntu-latest - - services: - db: - image: postgres:12.3 - ports: ['5432:5432'] - env: - POSTGRES_PASSWORD: postgres - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - - steps: - - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.8.0 - with: - access_token: ${{ github.token }} - - - uses: actions/checkout@v2 - with: - ref: ${{ github.head_ref }} - - - uses: erlef/setup-elixir@v1 - with: - otp-version: ${{ env.OTP_VERSION }} - elixir-version: ${{ env.ELIXIR_VERSION }} - - - uses: actions/setup-node@v2.1.5 - with: - node-version: ${{ env.NODE_VERSION }} - - - name: Cache Elixir build - uses: actions/cache@v2 - with: - path: | - _build - deps - key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} - restore-keys: | - ${{ runner.os }}-mix- - - - name: Cache Node npm - uses: actions/cache@v2 - with: - path: | - **/node_modules - key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-node- - - - uses: nimblehq/elixir-templates@composite_1.4 - with: - new_project_options: '--live --module=CustomModule --app=custom_app' - variant: 'live' diff --git a/.github/workflows/test_live_variant_on_standard_liveview_project.yml b/.github/workflows/test_live_variant_on_standard_liveview_project.yml deleted file mode 100644 index 6ba329d5..00000000 --- a/.github/workflows/test_live_variant_on_standard_liveview_project.yml +++ /dev/null @@ -1,68 +0,0 @@ -name: "Test Live variant on standard LiveView project" - -on: push - -env: - OTP_VERSION: 23.3 - ELIXIR_VERSION: 1.11.4 - PHOENIX_VERSION: 1.5.7 - NODE_VERSION: 14 - -jobs: - unit_test: - runs-on: ubuntu-latest - - services: - db: - image: postgres:12.3 - ports: ['5432:5432'] - env: - POSTGRES_PASSWORD: postgres - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - - steps: - - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.8.0 - with: - access_token: ${{ github.token }} - - - uses: actions/checkout@v2 - with: - ref: ${{ github.head_ref }} - - - uses: erlef/setup-elixir@v1 - with: - otp-version: ${{ env.OTP_VERSION }} - elixir-version: ${{ env.ELIXIR_VERSION }} - - - uses: actions/setup-node@v2.1.5 - with: - node-version: ${{ env.NODE_VERSION }} - - - name: Cache Elixir build - uses: actions/cache@v2 - with: - path: | - _build - deps - key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} - restore-keys: | - ${{ runner.os }}-mix- - - - name: Cache Node npm - uses: actions/cache@v2 - with: - path: | - **/node_modules - key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-node- - - - uses: nimblehq/elixir-templates@composite_1.4 - with: - new_project_options: '--live' - variant: 'live' diff --git a/.github/workflows/test_mix_variant_on_custom_mix_project.yml b/.github/workflows/test_mix_variant_on_custom_mix_project.yml deleted file mode 100644 index 17c8767a..00000000 --- a/.github/workflows/test_mix_variant_on_custom_mix_project.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: "Test Mix variant on custom Mix project" - -on: push - -env: - OTP_VERSION: 23.3 - ELIXIR_VERSION: 1.11.4 - MIX_ENV: test - -jobs: - unit_test: - runs-on: ubuntu-latest - - steps: - - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.8.0 - with: - access_token: ${{ github.token }} - - - uses: actions/checkout@v2 - with: - ref: ${{ github.head_ref }} - - - uses: erlef/setup-elixir@v1 - with: - otp-version: ${{ env.OTP_VERSION }} - elixir-version: ${{ env.ELIXIR_VERSION }} - - - name: Cache Elixir build - uses: actions/cache@v2 - with: - path: | - _build - deps - key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} - restore-keys: | - ${{ runner.os }}-mix- - - - name: Create Mix project - run: make create_mix_project PROJECT_DIRECTORY=sample_project OPTIONS="--module=CustomModule --app=custom_app" - - - name: Apply Mix template - run: make apply_mix_template PROJECT_DIRECTORY=sample_project - - - name: Install Elixir Dependencies - run: cd sample_project && mix deps.get - - - name: Compile dependencies - run: cd sample_project && mix compile --warnings-as-errors --all-warnings - - - name: Run mix codebase - run: cd sample_project && mix codebase - - - name: Run mix test - run: cd sample_project && mix test diff --git a/.github/workflows/test_mix_variant_on_custom_mix_project_with_supervision.yml b/.github/workflows/test_mix_variant_on_custom_mix_project_with_supervision.yml deleted file mode 100644 index e240d59a..00000000 --- a/.github/workflows/test_mix_variant_on_custom_mix_project_with_supervision.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: "Test Mix variant on custom Mix project with --sup option" - -on: push - -env: - OTP_VERSION: 23.3 - ELIXIR_VERSION: 1.11.4 - MIX_ENV: test - -jobs: - unit_test: - runs-on: ubuntu-latest - - steps: - - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.8.0 - with: - access_token: ${{ github.token }} - - - uses: actions/checkout@v2 - with: - ref: ${{ github.head_ref }} - - - uses: erlef/setup-elixir@v1 - with: - otp-version: ${{ env.OTP_VERSION }} - elixir-version: ${{ env.ELIXIR_VERSION }} - - - name: Cache Elixir build - uses: actions/cache@v2 - with: - path: | - _build - deps - key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} - restore-keys: | - ${{ runner.os }}-mix- - - - name: Create Mix project - run: make create_mix_project PROJECT_DIRECTORY=sample_project OPTIONS="--module=CustomModule --app=custom_app --sup" - - - name: Apply Mix template - run: make apply_mix_template PROJECT_DIRECTORY=sample_project - - - name: Install Elixir Dependencies - run: cd sample_project && mix deps.get - - - name: Compile dependencies - run: cd sample_project && mix compile --warnings-as-errors --all-warnings - - - name: Run mix codebase - run: cd sample_project && mix codebase - - - name: Run mix test - run: cd sample_project && mix test diff --git a/.github/workflows/test_mix_variant_on_standard_mix_project.yml b/.github/workflows/test_mix_variant_on_standard_mix_project.yml deleted file mode 100644 index bd62d7ce..00000000 --- a/.github/workflows/test_mix_variant_on_standard_mix_project.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: "Test Mix variant on standard Mix project" - -on: push - -env: - OTP_VERSION: 23.3 - ELIXIR_VERSION: 1.11.4 - MIX_ENV: test - -jobs: - unit_test: - runs-on: ubuntu-latest - - steps: - - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.8.0 - with: - access_token: ${{ github.token }} - - - uses: actions/checkout@v2 - with: - ref: ${{ github.head_ref }} - - - uses: erlef/setup-elixir@v1 - with: - otp-version: ${{ env.OTP_VERSION }} - elixir-version: ${{ env.ELIXIR_VERSION }} - - - name: Cache Elixir build - uses: actions/cache@v2 - with: - path: | - _build - deps - key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} - restore-keys: | - ${{ runner.os }}-mix- - - - name: Create Mix project - run: make create_mix_project PROJECT_DIRECTORY=sample_project OPTIONS="" - - - name: Apply Mix template - run: make apply_mix_template PROJECT_DIRECTORY=sample_project - - - name: Install Elixir Dependencies - run: cd sample_project && mix deps.get - - - name: Compile dependencies - run: cd sample_project && mix compile --warnings-as-errors --all-warnings - - - name: Run mix codebase - run: cd sample_project && mix codebase - - - name: Run mix test - run: cd sample_project && mix test diff --git a/.github/workflows/test_mix_variant_on_standard_mix_project_with_supervision.yml b/.github/workflows/test_mix_variant_on_standard_mix_project_with_supervision.yml deleted file mode 100644 index 6d515a2f..00000000 --- a/.github/workflows/test_mix_variant_on_standard_mix_project_with_supervision.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: "Test Mix variant on standard Mix project with --sup option" - -on: push - -env: - OTP_VERSION: 23.3 - ELIXIR_VERSION: 1.11.4 - MIX_ENV: test - -jobs: - unit_test: - runs-on: ubuntu-latest - - steps: - - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.8.0 - with: - access_token: ${{ github.token }} - - - uses: actions/checkout@v2 - with: - ref: ${{ github.head_ref }} - - - uses: erlef/setup-elixir@v1 - with: - otp-version: ${{ env.OTP_VERSION }} - elixir-version: ${{ env.ELIXIR_VERSION }} - - - name: Cache Elixir build - uses: actions/cache@v2 - with: - path: | - _build - deps - key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} - restore-keys: | - ${{ runner.os }}-mix- - - - name: Create Mix project - run: make create_mix_project PROJECT_DIRECTORY=sample_project OPTIONS="--sup" - - - name: Apply Mix template - run: make apply_mix_template PROJECT_DIRECTORY=sample_project - - - name: Install Elixir Dependencies - run: cd sample_project && mix deps.get - - - name: Compile dependencies - run: cd sample_project && mix compile --warnings-as-errors --all-warnings - - - name: Run mix codebase - run: cd sample_project && mix codebase - - - name: Run mix test - run: cd sample_project && mix test diff --git a/.github/workflows/test_release_version.yml b/.github/workflows/test_release_version.yml deleted file mode 100644 index 5a48b40e..00000000 --- a/.github/workflows/test_release_version.yml +++ /dev/null @@ -1,53 +0,0 @@ -# name: "Test Release version" - -# on: -# push: -# branches: -# - 'release/**' - -# env: -# OTP_VERSION: 23.3 -# ELIXIR_VERSION: 1.11.4 -# PHOENIX_VERSION: 1.5.7 -# MIX_ENV: test - -# jobs: -# release_version_test: -# runs-on: ubuntu-latest - -# steps: -# - name: Cancel Previous Runs -# uses: styfle/cancel-workflow-action@0.8.0 -# with: -# access_token: ${{ github.token }} - -# - uses: actions/checkout@v2 -# with: -# ref: ${{ github.head_ref }} - -# - uses: erlef/setup-elixir@v1 -# with: -# otp-version: ${{ env.OTP_VERSION }} -# elixir-version: ${{ env.ELIXIR_VERSION }} - -# - name: Cache Elixir build -# uses: actions/cache@v2 -# with: -# path: | -# _build -# deps -# key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} -# restore-keys: | -# ${{ runner.os }}-mix- - -# - name: Install Dependencies -# run: mix deps.get - -# - name: Compile dependencies -# run: mix compile --warnings-as-errors --all-warnings - -# - name: Install Phoenix ${{ env.PHOENIX_VERSION }} -# run: make install_phoenix PHOENIX_VERSION=${{ env.PHOENIX_VERSION }} - -# - name: Run Tests -# run: mix test --only release_version diff --git a/.github/workflows/test_template.yml b/.github/workflows/test_template.yml index 6aff76c7..ccf59b8a 100644 --- a/.github/workflows/test_template.yml +++ b/.github/workflows/test_template.yml @@ -1,15 +1,16 @@ -name: "Test template" +name: Test template on: push env: - OTP_VERSION: 23.3 - ELIXIR_VERSION: 1.11.4 - PHOENIX_VERSION: 1.5.7 + OTP_VERSION: 24.2.2 + ELIXIR_VERSION: 1.13.3 + PHOENIX_VERSION: 1.6.6 MIX_ENV: test jobs: - unit_test: + install_and_compile_dependencies: + name: Install and Compile Dependencies runs-on: ubuntu-latest steps: @@ -18,11 +19,53 @@ jobs: with: access_token: ${{ github.token }} - - uses: actions/checkout@v2 + - name: Checkout repository + uses: actions/checkout@v2 with: ref: ${{ github.head_ref }} - - uses: erlef/setup-elixir@v1 + - name: Set up Elixir ${{ env.ELIXIR_VERSION }} and OTP ${{ env.OTP_VERSION }} + uses: erlef/setup-elixir@v1 + with: + otp-version: ${{ env.OTP_VERSION }} + elixir-version: ${{ env.ELIXIR_VERSION }} + + - name: Cache Elixir build + uses: actions/cache@v2 + with: + path: | + _build + deps + key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} + restore-keys: | + ${{ runner.os }}-mix- + + - name: Install Dependencies + run: mix deps.get + + - name: Compile dependencies + run: mix compile + + lint_codebase: + name: Linting + + needs: install_and_compile_dependencies + + runs-on: ubuntu-latest + + steps: + - name: Cancel Previous Runs + uses: styfle/cancel-workflow-action@0.8.0 + with: + access_token: ${{ github.token }} + + - name: Checkout repository + uses: actions/checkout@v2 + with: + ref: ${{ github.head_ref }} + + - name: Set up Elixir ${{ env.ELIXIR_VERSION }} and OTP ${{ env.OTP_VERSION }} + uses: erlef/setup-elixir@v1 with: otp-version: ${{ env.OTP_VERSION }} elixir-version: ${{ env.ELIXIR_VERSION }} @@ -45,9 +88,52 @@ jobs: - name: Install Phoenix ${{ env.PHOENIX_VERSION }} run: make install_phoenix PHOENIX_VERSION=${{ env.PHOENIX_VERSION }} - + - name: Run codebase check run: mix codebase - + + unit_test: + name: Unit test + + needs: lint_codebase + + runs-on: ubuntu-latest + + steps: + - name: Cancel Previous Runs + uses: styfle/cancel-workflow-action@0.8.0 + with: + access_token: ${{ github.token }} + + - name: Checkout repository + uses: actions/checkout@v2 + with: + ref: ${{ github.head_ref }} + + - name: Set up Elixir ${{ env.ELIXIR_VERSION }} and OTP ${{ env.OTP_VERSION }} + uses: erlef/setup-elixir@v1 + with: + otp-version: ${{ env.OTP_VERSION }} + elixir-version: ${{ env.ELIXIR_VERSION }} + + - name: Cache Elixir build + uses: actions/cache@v2 + with: + path: | + _build + deps + key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} + restore-keys: | + ${{ runner.os }}-mix- + + - name: Install Dependencies + run: mix deps.get + + - name: Compile dependencies + run: mix compile + + - name: Install Phoenix ${{ env.PHOENIX_VERSION }} + run: make install_phoenix PHOENIX_VERSION=${{ env.PHOENIX_VERSION }} + - name: Run Tests run: mix test diff --git a/.github/workflows/test_variant_on_custom_web_project.yml b/.github/workflows/test_variant_on_custom_web_project.yml deleted file mode 100644 index c2796562..00000000 --- a/.github/workflows/test_variant_on_custom_web_project.yml +++ /dev/null @@ -1,72 +0,0 @@ -name: "Test variant on custom Web project" - -on: push - -env: - OTP_VERSION: 23.3 - ELIXIR_VERSION: 1.11.4 - PHOENIX_VERSION: 1.5.7 - NODE_VERSION: 14 - -jobs: - unit_test: - runs-on: ubuntu-latest - - strategy: - matrix: - variant: [web, api] - - services: - db: - image: postgres:12.3 - ports: ['5432:5432'] - env: - POSTGRES_PASSWORD: postgres - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - - steps: - - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.8.0 - with: - access_token: ${{ github.token }} - - - uses: actions/checkout@v2 - with: - ref: ${{ github.head_ref }} - - - uses: erlef/setup-elixir@v1 - with: - otp-version: ${{ env.OTP_VERSION }} - elixir-version: ${{ env.ELIXIR_VERSION }} - - - uses: actions/setup-node@v2.1.5 - with: - node-version: ${{ env.NODE_VERSION }} - - - name: Cache Elixir build - uses: actions/cache@v2 - with: - path: | - _build - deps - key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} - restore-keys: | - ${{ runner.os }}-mix- - - - name: Cache Node npm - uses: actions/cache@v2 - with: - path: | - **/node_modules - key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-node- - - - uses: nimblehq/elixir-templates@composite_1.4 - with: - new_project_options: '--module=CustomModule --app=custom_app' - variant: ${{ matrix.variant }} diff --git a/.github/workflows/test_variant_on_standard_web_project.yml b/.github/workflows/test_variant_on_standard_web_project.yml deleted file mode 100644 index 0e365e40..00000000 --- a/.github/workflows/test_variant_on_standard_web_project.yml +++ /dev/null @@ -1,72 +0,0 @@ -name: "Test variant on standard Web project" - -on: push - -env: - OTP_VERSION: 23.3 - ELIXIR_VERSION: 1.11.4 - PHOENIX_VERSION: 1.5.7 - NODE_VERSION: 14 - -jobs: - unit_test: - runs-on: ubuntu-latest - - strategy: - matrix: - variant: [web, api] - - services: - db: - image: postgres:12.3 - ports: ['5432:5432'] - env: - POSTGRES_PASSWORD: postgres - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - - steps: - - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.8.0 - with: - access_token: ${{ github.token }} - - - uses: actions/checkout@v2 - with: - ref: ${{ github.head_ref }} - - - uses: erlef/setup-elixir@v1 - with: - otp-version: ${{ env.OTP_VERSION }} - elixir-version: ${{ env.ELIXIR_VERSION }} - - - uses: actions/setup-node@v2.1.5 - with: - node-version: ${{ env.NODE_VERSION }} - - - name: Cache Elixir build - uses: actions/cache@v2 - with: - path: | - _build - deps - key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} - restore-keys: | - ${{ runner.os }}-mix- - - - name: Cache Node npm - uses: actions/cache@v2 - with: - path: | - **/node_modules - key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-node- - - - uses: nimblehq/elixir-templates@composite_1.4 - with: - new_project_options: '' - variant: ${{ matrix.variant }} diff --git a/.github/workflows/verify_release_version.yml b/.github/workflows/verify_release_version.yml new file mode 100644 index 00000000..b4ef730b --- /dev/null +++ b/.github/workflows/verify_release_version.yml @@ -0,0 +1,56 @@ +name: Verify Release version + +on: + push: + branches: + - 'release/**' + +env: + OTP_VERSION: 23.3 + ELIXIR_VERSION: 1.11.4 + PHOENIX_VERSION: 1.6.6 + MIX_ENV: test + +jobs: + release_version_test: + name: Verify the Release version + runs-on: ubuntu-latest + + steps: + - name: Cancel Previous Runs + uses: styfle/cancel-workflow-action@0.8.0 + with: + access_token: ${{ github.token }} + + - name: Checkout repository + uses: actions/checkout@v2 + with: + ref: ${{ github.head_ref }} + + - name: Set up Elixir ${{ env.ELIXIR_VERSION }} and OTP ${{ env.OTP_VERSION }} + uses: erlef/setup-elixir@v1 + with: + otp-version: ${{ env.OTP_VERSION }} + elixir-version: ${{ env.ELIXIR_VERSION }} + + - name: Cache Elixir build + uses: actions/cache@v2 + with: + path: | + _build + deps + key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} + restore-keys: | + ${{ runner.os }}-mix- + + - name: Install Dependencies + run: mix deps.get + + - name: Compile dependencies + run: mix compile --warnings-as-errors --all-warnings + + - name: Install Phoenix ${{ env.PHOENIX_VERSION }} + run: make install_phoenix PHOENIX_VERSION=${{ env.PHOENIX_VERSION }} + + - name: Run Tests + run: mix test --only release_version diff --git a/.gitignore b/.gitignore index 2e3f8871..30339657 100644 --- a/.gitignore +++ b/.gitignore @@ -27,5 +27,6 @@ nimble_template-*.tar # Ignore folder information and IDE-specific files .DS_Store .idea/* +nimble_template.iml **/tmp diff --git a/.tool-versions b/.tool-versions index 7bc7f2ef..cdf395d3 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ -erlang 23.3 -elixir 1.11.4-otp-23 +erlang 24.2.2 +elixir 1.13.3-otp-24 diff --git a/Makefile b/Makefile index 97ab82f1..ff39d38d 100644 --- a/Makefile +++ b/Makefile @@ -6,31 +6,40 @@ install_phoenix: create_phoenix_project: mix phx.new ${PROJECT_DIRECTORY} ${OPTIONS} - + create_mix_project: mix new ${PROJECT_DIRECTORY} ${OPTIONS} # Y - in response to Will you host this project on Github? # Y - in response to Do you want to generate the .github/ISSUE_TEMPLATE and .github/PULL_REQUEST_TEMPLATE? -# Y - in response to Do you want to generate the Github Action workflow? +# Y - in response to Do you want to generate the Github Action workflows: Test? +# Y - in response to Do you want to generate the Github Action workflows: Deploy to Heroku? +# Y - in response to Do you want to generate the .github/.workflow/README file? +# Y - in response to Do you want to publish a Github Wiki for this project? You'd need to manually create the first Github Wiki Page and set the GH_TOKEN and GH_EMAIL secret for this to properly function. # Y - in response to Would you like to add the Oban addon? # Y - in response to Would you like to add the ExVCR addon? -common_addon_prompts = Y\nY\nY\nY\nY\n +common_addon_prompts = Y\nY\nY\nY\nY\nY\nY\nY\n -web_addon_prompts = +# Y - in response to Would you like to add the SVG Sprite addon? +# Y - in response to Would you like to add the Dart Sass addon? +# Y - in response to Would you like to add the Nimble CSS addon? +# Y - in response to Would you like to add the Nimble JS addon? +# Y - in response to Would you like to add the Boostrap addon? +web_addon_prompts = Y\nY\nY\nY\nY\n -api_addon_prompts = +api_addon_prompts = -live_addon_prompts = +live_addon_prompts = # Y - in response to Will you host this project on Github? # Y - in response to Do you want to generate the .github/ISSUE_TEMPLATE and .github/PULL_REQUEST_TEMPLATE? -# Y - in response to Do you want to generate the Github Action workflow? +# Y - in response to Do you want to generate the Github Action workflow: Test? +# Y - in response to Do you want to generate the .github/.workflow/README file? +# Y - in response to Do you want to publish a Github Wiki for this project? You'd need to manually create the first Github Wiki Page and set the GH_TOKEN and GH_EMAIL secret for this to properly function. # Y - in response to Would you like to add the Mimic addon? -mix_addon_prompts = Y\nY\nY\nY\n +mix_addon_prompts = Y\nY\nY\nY\nY\nY\n -# Y - in response to Fetch and install dependencies? -post_setup_addon_prompts = Y\n +post_setup_addon_prompts = apply_phoenix_template: cd ${PROJECT_DIRECTORY} && \ @@ -47,7 +56,7 @@ apply_phoenix_template: elif [ $(VARIANT) = live ]; then \ printf "${common_addon_prompts}${web_addon_prompts}${live_addon_prompts}${post_setup_addon_prompts}" | mix nimble_template.gen --live; \ fi; - + apply_mix_template: cd ${PROJECT_DIRECTORY} && \ echo '{:nimble_template, path: "../", only: :dev, runtime: false}' > nimble_template.txt && \ diff --git a/README.md b/README.md index 67ff890e..3a4b6173 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@ Phoenix/Mix template for projects at [Nimble](https://nimblehq.co/). NimbleTemplate has been developed and actively tested with the below environment: -- Mix 1.11.4 -- Elixir 1.11.4 -- Erlang/OTP 23.3 -- Phoenix 1.5.7 +- Mix 1.13.3 +- Elixir 1.13.3 +- Erlang/OTP 24.2.2 +- Phoenix 1.6.6 ## Installation @@ -30,7 +30,7 @@ Step 2: Add `nimble_template` dependency to `mix.exs`: ```elixir def deps do [ - {:nimble_template, "~> 3.0", only: :dev, runtime: false}, + {:nimble_template, "~> 4.0", only: :dev, runtime: false}, # other dependencies ... ] end @@ -62,133 +62,7 @@ mix nimble_template.gen --mix # Apply the Mix template ## Running tests -NimbleTemplate uses Github Action as the CI, the workflow files locate under [.github/workflows/](https://github.com/nimblehq/elixir-templates/tree/develop/.github/workflows) directory. - -There are 2 types of test **Template test** and **Variant test** - - -### 1/ Template test - -All test files are located under `test/` directory. - -``` -. -├── ... -├── test -│ ├── ... -│ ├── nimble_template -│ │ └── addons -│ │ │ ├── ... -│ │ │ ├── common_addon_test.exs -│ │ │ └── variants -│ │ │ │ └── mix -│ │ │ │ │ ├── ... -│ │ │ │ │ └── mix_addon_test.exs -│ │ │ │ └── phoenix -│ │ │ │ │ └── api -│ │ │ │ │ │ ├── ... -│ │ │ │ │ │ └── api_addon_test.exs -│ │ │ │ │ └── live -│ │ │ │ │ │ ├── ... -│ │ │ │ │ │ └── live_addon_test.exs -│ │ │ │ │ └── web -│ │ │ │ │ │ ├── ... -│ │ │ │ │ │ └── web_addon_test.exs -``` - -### 2/ Variant test - -#### 2.1/ Variant - -NimbleTemplate supports 4 variants: - -- API -- Live -- Web -- Mix - -#### 2.2/ Phoenix project - -The Phoenix project could be either a Web or API project. - -- Web variant supports HTML and Webpack configuration. - -```bash -mix phx.new awesome_project -``` - -- LiveView project is including HTML and Webpack configuration. - -```bash -mix phx.new awesome_project --live -``` - -- API variant does NOT support HTML and Webpack configuration. - -```bash -mix phx.new awesome_project --no-html --no-webpack -``` - -- Custom project variant allow us to modify the app name or module name. - -```bash -# Use CustomModuleName -mix phx.new awesome_project --module=CustomModuleName - -# Use custom OTP app name -mix phx.new awesome_project --app=custom_otp_app_name - -# Use custom module and app name -mix phx.new awesome_project --module=CustomModuleName --app=custom_otp_app_name -``` - -So it ends up with 6 project types: - -Web project -- Standard (`mix phx.new awesome_project`) -- Custom (`mix phx.new awesome_project --module=CustomModuleName --app=custom_otp_app_name`) - -API project -- Standard (`mix phx.new awesome_project --no-html --no-webpack`) -- Custom (`mix phx.new awesome_project --no-html --no-webpack --module=CustomModuleName --app=custom_otp_app_name`) - -LiveView project -- Standard (`mix phx.new awesome_project --live`) -- Custom (`mix phx.new awesome_project --live --module=CustomModuleName --app=custom_otp_app_name`) - -Putting it all together, there are 8 variants of test cases. - -- Applying the `API variant` to a `Standard Web project` -- Applying the `API variant` to a `Custom Web project` -- Applying the `API variant` to a `Standard API project` -- Applying the `API variant` to a `Custom API project` -- Applying the `Web variant` to a `Standard Web project` -- Applying the `Web variant` to a `Custom Web project` -- Applying the `Live variant` to a `Standard LiveView project` -- Applying the `Live variant` to a `Custom LiveView project` - -##### 2.2/ Mix project - -The Mix project could be either a Standard project or a Custom project. - -- `mix new awesome_project` -- `mix new awesome_project --module=CustomModuleName` -- `mix new awesome_project --app=custom_otp_app_name` -- `mix new awesome_project --module=CustomModuleName --app=custom_otp_app_name` - -Each project could be include the `supervision tree` or not. - -- `mix new awesome_project` -- `mix new awesome_project --sup` -- `mix new awesome_project --module=CustomModuleName --app=custom_otp_app_name` -- `mix new awesome_project --module=CustomModuleName --app=custom_otp_app_name --sup` - -Putting it all together, it will has 4 variant test cases. - -- Applying the `Mix variant` to a `Standard Mix project` -- Applying the `Mix variant` to a `Custom Mix project` -- Applying the `Mix variant` to a `Standard Mix project with the --sup option` -- Applying the `Mix variant` to a `Custom Mix project with the --sup option` +The testing documentation is on [Wiki](https://github.com/nimblehq/elixir-templates/wiki) ### Release @@ -200,10 +74,9 @@ Once a `release/` is created, to publish the new version to Hex. Once the release branch is merged into the `master` branch, Github Action automatically publishes the template to [https://hex.pm/packages/nimble_template](https://hex.pm/packages/nimble_template). - ## Contributing -Contributions, issues, and feature requests are welcome!
Feel free to check [issues page](https://github.com/nimblehq/elixir-templates/issues). +Contributions, issues, and feature requests are welcome!
Feel free to check [issues page](https://github.com/nimblehq/elixir-templates/issues). ## License diff --git a/action.yml b/action.yml deleted file mode 100644 index e8ee3237..00000000 --- a/action.yml +++ /dev/null @@ -1,78 +0,0 @@ -inputs: - new_project_options: - required: true - variant: - required: true - -runs: - using: "composite" - steps: - - name: Install Elixir Dependencies - run: mix deps.get - shell: bash - - - name: Install Phoenix - run: make install_phoenix PHOENIX_VERSION=${{ env.PHOENIX_VERSION }} - shell: bash - - # Y - in response to Fetch and install dependencies? - - name: Create Phoenix project - run: printf "Y\n" | make create_phoenix_project PROJECT_DIRECTORY=sample_project OPTIONS="${{ inputs.new_project_options }}" - shell: bash - - - name: Apply Phoenix template - run: make apply_phoenix_template PROJECT_DIRECTORY=sample_project VARIANT=${{ inputs.variant }} - shell: bash - - - name: Install Elixir Dependencies - run: cd sample_project && mix deps.get - shell: bash - - - name: Compile dependencies - run: cd sample_project && mix compile --warnings-as-errors --all-warnings - shell: bash - env: - MIX_ENV: test - - - name: Install Node Dependencies - run: cd sample_project && npm install --prefix assets - shell: bash - - - name: Run mix ecto.create - run: cd sample_project && mix ecto.create - shell: bash - env: - MIX_ENV: test - DB_HOST: localhost - - - name: Run mix ecto.migrate - run: cd sample_project && mix ecto.migrate - shell: bash - env: - MIX_ENV: test - DB_HOST: localhost - - - name: Run mix codebase - run: cd sample_project && mix codebase - shell: bash - env: - MIX_ENV: test - DB_HOST: localhost - - - name: Run mix test - run: cd sample_project && mix coverage - shell: bash - env: - MIX_ENV: test - DB_HOST: localhost - - - name: Remove nimble_template - run: make remove_nimble_template PROJECT_DIRECTORY=sample_project - shell: bash - - - name: Build release app - run: cd sample_project && docker-compose build - shell: bash - - - run: rm -rf sample_project - shell: bash diff --git a/config/config.exs b/config/config.exs index f49e6184..95a3373f 100644 --- a/config/config.exs +++ b/config/config.exs @@ -1,3 +1,3 @@ -use Mix.Config +import Config config :phoenix, :json_library, Jason diff --git a/lib/mix/tasks/nimble_template.gen.ex b/lib/mix/tasks/nimble_template.gen.ex index 85533749..7b68c09b 100644 --- a/lib/mix/tasks/nimble_template.gen.ex +++ b/lib/mix/tasks/nimble_template.gen.ex @@ -25,7 +25,8 @@ defmodule Mix.Tasks.NimbleTemplate.Gen do use Mix.Task - alias NimbleTemplate.{Project, Template} + alias NimbleTemplate.Projects.Project + alias NimbleTemplate.Templates.Template @version Mix.Project.config()[:version] @variants [api: :boolean, web: :boolean, live: :boolean, mix: :boolean] diff --git a/lib/nimble_template/addon.ex b/lib/nimble_template/addons/addon.ex similarity index 72% rename from lib/nimble_template/addon.ex rename to lib/nimble_template/addons/addon.ex index 19d86f23..32eed75b 100644 --- a/lib/nimble_template/addon.ex +++ b/lib/nimble_template/addons/addon.ex @@ -1,7 +1,8 @@ -defmodule NimbleTemplate.Addon do +defmodule NimbleTemplate.Addons.Addon do @moduledoc false - alias NimbleTemplate.{Addon, Project} + alias __MODULE__ + alias NimbleTemplate.Projects.Project @callback apply(%Project{}, %{}) :: %Project{} @callback do_apply(%Project{}, %{}) :: %Project{} @@ -10,8 +11,11 @@ defmodule NimbleTemplate.Addon do quote location: :keep, bind_quoted: [opts: opts] do @behaviour Addon - alias NimbleTemplate.{Generator, Project} + import NimbleTemplate.GithubHelper, only: [has_github_wiki_directory?: 0] + + alias NimbleTemplate.Generator alias NimbleTemplate.Hex.Package + alias NimbleTemplate.Projects.Project def apply(%Project{} = project, opts \\ %{}) when is_map(opts) do Generator.print_log("* applying ", inspect(__MODULE__)) diff --git a/lib/nimble_template/addons/credo.ex b/lib/nimble_template/addons/credo.ex index 43be4a91..472f92d7 100644 --- a/lib/nimble_template/addons/credo.ex +++ b/lib/nimble_template/addons/credo.ex @@ -1,7 +1,7 @@ defmodule NimbleTemplate.Addons.Credo do @moduledoc false - use NimbleTemplate.Addon + use NimbleTemplate.Addons.Addon @impl true def do_apply(%Project{} = project, _opts) do @@ -30,28 +30,15 @@ defmodule NimbleTemplate.Addons.Credo do project end - defp edit_mix(%Project{mix_project?: true} = project) do - Generator.replace_content( - "mix.exs", - """ - codebase: ["deps.unlock --check-unused", "format --check-formatted"] - """, - """ - codebase: ["deps.unlock --check-unused", "format --check-formatted", "credo --strict"] - """ - ) - - project - end - defp edit_mix(project) do Generator.replace_content( "mix.exs", """ - codebase: ["deps.unlock --check-unused", "format --check-formatted"], + codebase: [ """, """ - codebase: ["deps.unlock --check-unused", "format --check-formatted", "credo --strict"], + codebase: [ + "credo --strict", """ ) diff --git a/lib/nimble_template/addons/dialyxir.ex b/lib/nimble_template/addons/dialyxir.ex index 5e4098a5..7015027d 100644 --- a/lib/nimble_template/addons/dialyxir.ex +++ b/lib/nimble_template/addons/dialyxir.ex @@ -1,7 +1,7 @@ defmodule NimbleTemplate.Addons.Dialyxir do @moduledoc false - use NimbleTemplate.Addon + use NimbleTemplate.Addons.Addon @impl true def do_apply(%Project{} = project, _opts) do diff --git a/lib/nimble_template/addons/elixir_version.ex b/lib/nimble_template/addons/elixir_version.ex index ae6b2af0..64cd5c28 100644 --- a/lib/nimble_template/addons/elixir_version.ex +++ b/lib/nimble_template/addons/elixir_version.ex @@ -1,7 +1,7 @@ defmodule NimbleTemplate.Addons.ElixirVersion do @moduledoc false - use NimbleTemplate.Addon + use NimbleTemplate.Addons.Addon @impl true def do_apply( diff --git a/lib/nimble_template/addons/ex_coveralls.ex b/lib/nimble_template/addons/ex_coveralls.ex index 50c00f54..3126c834 100644 --- a/lib/nimble_template/addons/ex_coveralls.ex +++ b/lib/nimble_template/addons/ex_coveralls.ex @@ -1,7 +1,7 @@ defmodule NimbleTemplate.Addons.ExCoveralls do @moduledoc false - use NimbleTemplate.Addon + use NimbleTemplate.Addons.Addon @impl true def do_apply(%Project{} = project, _opts) do @@ -36,14 +36,12 @@ defmodule NimbleTemplate.Addons.ExCoveralls do project end - defp edit_files(%Project{live_project?: live_project?} = project) do + defp edit_files(%Project{} = project) do project |> inject_mix_dependency() |> edit_mix() |> edit_web_router() - if live_project?, do: edit_page_live(project) - project end @@ -87,21 +85,6 @@ defmodule NimbleTemplate.Addons.ExCoveralls do project end - defp edit_page_live(%Project{web_path: web_path, web_module: web_module} = project) do - Generator.replace_content( - "#{web_path}/live/page_live.ex", - """ - defmodule #{web_module}.PageLive do - """, - """ - # coveralls-ignore-start - defmodule #{web_module}.PageLive do - """ - ) - - project - end - defp edit_web_router(%Project{} = project) do project |> ignore_ex_coverall_on_api_pipeline() diff --git a/lib/nimble_template/addons/ex_unit.ex b/lib/nimble_template/addons/ex_unit.ex index 50d02535..38d997cf 100644 --- a/lib/nimble_template/addons/ex_unit.ex +++ b/lib/nimble_template/addons/ex_unit.ex @@ -1,7 +1,7 @@ defmodule NimbleTemplate.Addons.ExUnit do @moduledoc false - use NimbleTemplate.Addon + use NimbleTemplate.Addons.Addon @impl true def do_apply(%Project{} = project, _opts), do: edit_test_helper(project) diff --git a/lib/nimble_template/addons/faker.ex b/lib/nimble_template/addons/faker.ex new file mode 100644 index 00000000..5bb81b06 --- /dev/null +++ b/lib/nimble_template/addons/faker.ex @@ -0,0 +1,14 @@ +defmodule NimbleTemplate.Addons.Faker do + @moduledoc false + + use NimbleTemplate.Addons.Addon + + @impl true + def do_apply(%Project{} = project, _opts) do + Generator.inject_mix_dependency( + {:faker, latest_package_version(:faker), only: [:dev, :test], runtime: false} + ) + + project + end +end diff --git a/lib/nimble_template/addons/github.ex b/lib/nimble_template/addons/github.ex index 4878171e..8606ed4d 100644 --- a/lib/nimble_template/addons/github.ex +++ b/lib/nimble_template/addons/github.ex @@ -1,15 +1,17 @@ defmodule NimbleTemplate.Addons.Github do @moduledoc false - use NimbleTemplate.Addon + use NimbleTemplate.Addons.Addon @impl true - def do_apply(%Project{} = project, opts) when is_map_key(opts, :github_template) do + def do_apply(%Project{} = project, %{github_template: true}) do files = [ {:text, Path.join([".github", "ISSUE_TEMPLATE.md"]), Path.join([".github", "ISSUE_TEMPLATE.md"])}, {:text, Path.join([".github", "PULL_REQUEST_TEMPLATE.md"]), - Path.join([".github", "PULL_REQUEST_TEMPLATE.md"])} + Path.join([".github", "PULL_REQUEST_TEMPLATE.md"])}, + {:text, Path.join([".github", "PULL_REQUEST_TEMPLATE", "RELEASE_TEMPLATE.md"]), + Path.join([".github", "PULL_REQUEST_TEMPLATE", "RELEASE_TEMPLATE.md"])} ] Generator.copy_file(files) @@ -26,9 +28,8 @@ defmodule NimbleTemplate.Addons.Github do elixir_version: elixir_version, node_version: node_version } = project, - opts - ) - when is_map_key(opts, :github_action) do + %{github_action_test: true} + ) do binding = [ erlang_version: erlang_version, elixir_version: elixir_version, @@ -51,4 +52,138 @@ defmodule NimbleTemplate.Addons.Github do project end + + @impl true + def do_apply( + %Project{} = project, + %{ + github_workflows_readme: true, + with_test_workflow?: with_test_workflow?, + with_deploy_to_heroku_workflow?: with_deploy_to_heroku_workflow? + } + ) do + Generator.copy_file( + [ + {:eex, ".github/workflows/README.md.eex", ".github/workflows/README.md"} + ], + with_test_workflow?: with_test_workflow?, + with_deploy_to_heroku_workflow?: with_deploy_to_heroku_workflow? + ) + + project + end + + @impl true + def do_apply(%Project{mix_project?: false, otp_app: otp_app, web_module: web_module} = project, %{ + github_action_deploy_heroku: true + }) do + Generator.copy_file([ + {:eex, ".github/workflows/deploy_heroku.yml", ".github/workflows/deploy_heroku.yml"} + ]) + + Generator.replace_content("config/runtime.exs", "# ssl: true,", "ssl: true,") + + Generator.replace_content( + "config/runtime.exs", + "url: [host: host,", + "url: [scheme: \"https\", host: host," + ) + + Generator.append_content( + "config/prod.exs", + """ + config :#{otp_app}, #{web_module}.Endpoint, + force_ssl: [rewrite_on: [:x_forwarded_proto]] + """ + ) + + project + end + + @impl true + def do_apply(%Project{} = project, %{github_wiki: true}) do + project + |> copy_wiki_files() + |> append_wiki_into_readme() + + project + end + + defp copy_wiki_files( + %Project{ + mix_project?: true, + erlang_version: erlang_version, + elixir_version: elixir_version + } = project + ) do + binding = [ + erlang_version: erlang_version, + elixir_version: elixir_version + ] + + publish_wiki_workflow_path = ".github/workflows/publish_wiki.yml" + template_getting_started_path = ".github/wiki/Getting-Started.md.mix.eex" + homepage_path = ".github/wiki/Home.md" + sidebar_path = ".github/wiki/_Sidebar.md.mix" + + files = [ + {:text, publish_wiki_workflow_path, publish_wiki_workflow_path}, + {:text, homepage_path, homepage_path}, + {:eex, template_getting_started_path, ".github/wiki/Getting-Started.md"}, + {:text, sidebar_path, ".github/wiki/_Sidebar.md"} + ] + + Generator.copy_file(files, binding) + + project + end + + defp copy_wiki_files( + %Project{ + web_project?: web_project?, + mix_project?: false, + erlang_version: erlang_version, + elixir_version: elixir_version + } = project + ) do + binding = [ + web_project?: web_project?, + erlang_version: erlang_version, + elixir_version: elixir_version + ] + + publish_wiki_workflow_path = ".github/workflows/publish_wiki.yml" + template_getting_started_path = ".github/wiki/Getting-Started.md.eex" + homepage_path = ".github/wiki/Home.md" + sidebar_path = ".github/wiki/_Sidebar.md" + application_status_path = ".github/wiki/Application-Status.md" + environment_variables_path = ".github/wiki/Environment-Variables.md.eex" + + files = [ + {:text, publish_wiki_workflow_path, publish_wiki_workflow_path}, + {:text, homepage_path, homepage_path}, + {:eex, template_getting_started_path, ".github/wiki/Getting-Started.md"}, + {:text, sidebar_path, sidebar_path}, + {:text, application_status_path, application_status_path}, + {:eex, environment_variables_path, ".github/wiki/Environment-Variables.md"} + ] + + Generator.copy_file(files, binding) + + project + end + + defp append_wiki_into_readme(project) do + Generator.append_content( + "README.md", + """ + + ## Project documentation + + Most of the documentation is located in the `.github/wiki` directory, which is published to the [project's Github wiki](https://github.com/[REPO]/wiki). + """ + ) + + project + end end diff --git a/lib/nimble_template/addons/mimic.ex b/lib/nimble_template/addons/mimic.ex index ccf17ca1..f7e31b90 100644 --- a/lib/nimble_template/addons/mimic.ex +++ b/lib/nimble_template/addons/mimic.ex @@ -1,7 +1,7 @@ defmodule NimbleTemplate.Addons.Mimic do @moduledoc false - use NimbleTemplate.Addon + use NimbleTemplate.Addons.Addon @impl true def do_apply(%Project{} = project, _opts) do @@ -12,6 +12,7 @@ defmodule NimbleTemplate.Addons.Mimic do project |> inject_mix_dependency() |> edit_test_helper() + |> edit_case() project end @@ -37,4 +38,45 @@ defmodule NimbleTemplate.Addons.Mimic do project end + + defp edit_case(%Project{mix_project?: false} = project) do + Generator.inject_content( + "test/support/channel_case.ex", + """ + quote do + """, + """ + use Mimic + + """ + ) + + Generator.inject_content( + "test/support/conn_case.ex", + """ + quote do + """, + """ + use Mimic + + """ + ) + + Generator.inject_content( + "test/support/data_case.ex", + """ + quote do + """, + """ + use Mimic + + """ + ) + + project + end + + defp edit_case(project) do + project + end end diff --git a/lib/nimble_template/addons/mix_release.ex b/lib/nimble_template/addons/mix_release.ex deleted file mode 100644 index ec815f1e..00000000 --- a/lib/nimble_template/addons/mix_release.ex +++ /dev/null @@ -1,109 +0,0 @@ -defmodule NimbleTemplate.Addons.MixRelease do - @moduledoc false - - use NimbleTemplate.Addon - - @prod_secret_path "config/prod.secret.exs" - - @impl true - def do_apply(%Project{} = project, _opts) do - project - |> edit_files() - |> copy_files() - |> delete_files() - end - - defp edit_files(project) do - project - |> edit_config_prod - |> edit_config_prod_secret - end - - defp edit_config_prod(project) do - Generator.delete_content( - "config/prod.exs", - """ - - # Finally import the config/prod.secret.exs which loads secrets - # and configuration from environment variables. - import_config "prod.secret.exs" - """ - ) - - project - end - - defp edit_config_prod_secret(%Project{otp_app: otp_app, web_module: web_module} = project) do - Generator.delete_content( - @prod_secret_path, - """ - # In this file, we load production configuration and secrets - # from environment variables. You can also hardcode secrets, - # although such is generally not recommended and you have to - # remember to add this file to your .gitignore. - use Mix.Config - - """ - ) - - Generator.replace_content( - @prod_secret_path, - """ - secret_key_base: secret_key_base - - # ## Using releases (Elixir v1.9+) - # - # If you are doing OTP releases, you need to instruct Phoenix - # to start each relevant endpoint: - # - # config :#{otp_app}, #{web_module}.Endpoint, server: true - # - # Then you can assemble a release by calling `mix release`. - # See `mix help release` for more information. - """, - """ - secret_key_base: secret_key_base, - server: true - """ - ) - - project - end - - defp copy_files( - %Project{otp_app: otp_app, base_module: base_module, base_path: base_path} = project - ) do - prod_secret_content = - @prod_secret_path - |> File.read!() - |> remove_last_new_line() - |> String.replace("\n ", "\n ") - |> String.replace("\n\n", "\n\n ") - - Generator.copy_file( - [ - {:eex, "config/runtime.exs.eex", "config/runtime.exs"}, - {:eex, "lib/otp_app/release_tasks.ex.eex", base_path <> "/release_tasks.ex"} - ], - prod_secret_content: prod_secret_content, - otp_app: otp_app, - base_module: base_module - ) - - project - end - - defp remove_last_new_line(content) do - if String.ends_with?(content, "\n") do - String.slice(content, 0..-2) - else - content - end - end - - defp delete_files(project) do - File.rm!(@prod_secret_path) - - project - end -end diff --git a/lib/nimble_template/addons/readme.ex b/lib/nimble_template/addons/readme.ex index 43297998..d3761f89 100644 --- a/lib/nimble_template/addons/readme.ex +++ b/lib/nimble_template/addons/readme.ex @@ -1,7 +1,7 @@ defmodule NimbleTemplate.Addons.Readme do @moduledoc false - use NimbleTemplate.Addon + use NimbleTemplate.Addons.Addon @impl true def do_apply(%Project{} = project, _opts) do diff --git a/lib/nimble_template/addons/test_env.ex b/lib/nimble_template/addons/test_env.ex index 1078d662..5a1aa464 100644 --- a/lib/nimble_template/addons/test_env.ex +++ b/lib/nimble_template/addons/test_env.ex @@ -1,7 +1,7 @@ defmodule NimbleTemplate.Addons.TestEnv do @moduledoc false - use NimbleTemplate.Addon + use NimbleTemplate.Addons.Addon @impl true def do_apply(%Project{mix_project?: true} = project, _opts) do @@ -42,7 +42,14 @@ defmodule NimbleTemplate.Addons.TestEnv do """ defp aliases do [ - codebase: ["deps.unlock --check-unused", "format --check-formatted"] + codebase: [ + "deps.unlock --check-unused", + "format --check-formatted" + ], + "codebase.fix": [ + "deps.clean --unlock --unused", + "format" + ] ] end @@ -65,7 +72,14 @@ defmodule NimbleTemplate.Addons.TestEnv do [ """, """ - codebase: ["deps.unlock --check-unused", "format --check-formatted"], + codebase: [ + "deps.unlock --check-unused", + "format --check-formatted" + ], + "codebase.fix": [ + "deps.clean --unlock --unused", + "format" + ], """ ) @@ -141,22 +155,14 @@ defmodule NimbleTemplate.Addons.TestEnv do Generator.replace_content( support_case_path, - """ - :ok = Ecto.Adapters.SQL.Sandbox.checkout(#{project.base_module}.Repo) - """, - """ - :ok = Sandbox.checkout(#{project.base_module}.Repo) - """ + "Ecto.Adapters.SQL.Sandbox.start_owner!", + "Sandbox.start_owner!" ) Generator.replace_content( support_case_path, - """ - Ecto.Adapters.SQL.Sandbox.mode(#{project.base_module}.Repo, {:shared, self()}) - """, - """ - Sandbox.mode(#{project.base_module}.Repo, {:shared, self()}) - """ + "Ecto.Adapters.SQL.Sandbox.stop_owner", + "Sandbox.stop_owner" ) project diff --git a/lib/nimble_template/addons/variants/phoenix/api/config.ex b/lib/nimble_template/addons/variants/phoenix/api/config.ex new file mode 100644 index 00000000..98134e49 --- /dev/null +++ b/lib/nimble_template/addons/variants/phoenix/api/config.ex @@ -0,0 +1,40 @@ +defmodule NimbleTemplate.Addons.Phoenix.Api.Config do + @moduledoc false + + use NimbleTemplate.Addons.Addon + + @impl true + def do_apply(%Project{} = project, _opts) do + edit_files(project) + end + + defp edit_files(%Project{} = project) do + edit_config_prod(project) + + project + end + + def edit_config_prod(%Project{otp_app: otp_app, web_module: web_module} = project) do + Generator.replace_content( + "config/prod.exs", + """ + config :#{otp_app}, #{web_module}.Endpoint, + """, + """ + config :#{otp_app}, #{web_module}.Endpoint.Anchor + """ + ) + + Generator.delete_content( + "config/prod.exs", + "config :#{otp_app}, #{web_module}.Endpoint.Anchor" + ) + + Generator.delete_content( + "config/prod.exs", + "cache_static_manifest: \"priv/static/cache_manifest.json\"" + ) + + project + end +end diff --git a/lib/nimble_template/addons/variants/phoenix/api/empty_body_params_plug.ex b/lib/nimble_template/addons/variants/phoenix/api/empty_body_params_plug.ex new file mode 100644 index 00000000..22a11bce --- /dev/null +++ b/lib/nimble_template/addons/variants/phoenix/api/empty_body_params_plug.ex @@ -0,0 +1,54 @@ +defmodule NimbleTemplate.Addons.Phoenix.Api.EmptyBodyParamsPlug do + @moduledoc false + + use NimbleTemplate.Addons.Addon + + @impl true + def do_apply(%Project{} = project, _opts) do + project + |> copy_files() + |> edit_files() + end + + defp copy_files( + %Project{ + web_module: web_module, + web_path: web_path, + web_test_path: web_test_path + } = project + ) do + binding = [ + web_module: web_module + ] + + files = [ + {:eex, "lib/otp_app_web/plugs/check_empty_body_params_plug.ex.eex", + "#{web_path}/plugs/check_empty_body_params_plug.ex"}, + {:eex, "test/otp_app_web/plugs/check_empty_body_params_plug_test.exs.eex", + "#{web_test_path}/plugs/check_empty_body_params_plug_test.exs"} + ] + + Generator.copy_file(files, binding) + + project + end + + defp edit_files(%Project{web_path: web_path, web_module: web_module} = project) do + Generator.replace_content( + "#{web_path}/router.ex", + """ + pipeline :api do + plug :accepts, ["json"] + end + """, + """ + pipeline :api do + plug :accepts, ["json"] + plug #{web_module}.CheckEmptyBodyParamsPlug + end + """ + ) + + project + end +end diff --git a/lib/nimble_template/addons/variants/phoenix/api/error_view.ex b/lib/nimble_template/addons/variants/phoenix/api/error_view.ex new file mode 100644 index 00000000..e13e5cfd --- /dev/null +++ b/lib/nimble_template/addons/variants/phoenix/api/error_view.ex @@ -0,0 +1,46 @@ +defmodule NimbleTemplate.Addons.Phoenix.Api.ErrorView do + @moduledoc false + + use NimbleTemplate.Addons.Addon + + @impl true + def do_apply(%Project{} = project, _opts) do + project + |> delete_files() + |> copy_files() + end + + defp delete_files(%Project{web_path: web_path} = project) do + File.rm!("#{web_path}/views/error_helpers.ex") + + project + end + + defp copy_files( + %Project{ + base_module: base_module, + web_module: web_module, + web_path: web_path, + web_test_path: web_test_path + } = project + ) do + binding = [ + web_module: web_module, + base_module: base_module + ] + + files = [ + {:eex, "lib/otp_app_web/views/error_helpers.ex.eex", "#{web_path}/views/error_helpers.ex"}, + {:eex, "lib/otp_app_web/views/api/error_view.ex.eex", "#{web_path}/views/api/error_view.ex"}, + {:eex, "test/otp_app_web/views/error_helpers_test.exs", + "#{web_test_path}/views/error_helpers_test.exs"}, + {:eex, "test/otp_app_web/views/api/error_view_test.exs", + "#{web_test_path}/views/api/error_view_test.exs"}, + {:eex, "test/support/view_case.ex.eex", "test/support/view_case.ex"} + ] + + Generator.copy_file(files, binding) + + project + end +end diff --git a/lib/nimble_template/addons/variants/phoenix/api/fallback_controller.ex.ex b/lib/nimble_template/addons/variants/phoenix/api/fallback_controller.ex.ex new file mode 100644 index 00000000..798198aa --- /dev/null +++ b/lib/nimble_template/addons/variants/phoenix/api/fallback_controller.ex.ex @@ -0,0 +1,59 @@ +defmodule NimbleTemplate.Addons.Phoenix.Api.FallbackController do + @moduledoc false + + use NimbleTemplate.Addons.Addon + + @impl true + def do_apply(%Project{} = project, _opts) do + project + |> edit_files() + |> copy_files() + end + + defp copy_files(%Project{web_module: web_module, web_path: web_path} = project) do + binding = [ + web_module: web_module + ] + + files = [ + {:eex, "lib/otp_app_web/controllers/api/fallback_controller.ex.eex", + "#{web_path}/controller/api/fallback_controller.ex"} + ] + + Generator.copy_file(files, binding) + + project + end + + defp edit_files(%Project{web_module: web_module, web_path: web_entry_point} = project) do + Generator.replace_content( + "#{web_entry_point}.ex", + """ + def controller do + quote do + use Phoenix.Controller, namespace: #{web_module} + + import Plug.Conn + import #{web_module}.Gettext + + alias #{web_module}.ParamsValidator + alias #{web_module}.Router.Helpers, as: Routes + """, + """ + def controller do + quote do + use Phoenix.Controller, namespace: #{web_module} + + import Plug.Conn + import #{web_module}.Gettext + + alias #{web_module}.ParamsValidator + alias #{web_module}.Router.Helpers, as: Routes + + action_fallback #{web_module}.Api.FallbackController + """ + ) + + project + end +end diff --git a/lib/nimble_template/addons/variants/phoenix/api/json_api.ex b/lib/nimble_template/addons/variants/phoenix/api/json_api.ex new file mode 100644 index 00000000..0fd09586 --- /dev/null +++ b/lib/nimble_template/addons/variants/phoenix/api/json_api.ex @@ -0,0 +1,40 @@ +defmodule NimbleTemplate.Addons.Phoenix.Api.JsonApi do + @moduledoc false + + use NimbleTemplate.Addons.Addon + + @impl true + def do_apply(%Project{} = project, _opts) do + edit_files(project) + end + + defp edit_files(%Project{} = project) do + project + |> inject_mix_dependency() + |> edit_config() + end + + def inject_mix_dependency(project) do + Generator.inject_mix_dependency({:jsonapi, latest_package_version(:jsonapi)}) + + project + end + + def edit_config(project) do + Generator.replace_content( + "config/config.exs", + """ + # Import environment specific config. This must remain at the bottom + # of this file so it overrides the configuration defined above. + """, + """ + config :jsonapi, remove_links: true + + # Import environment specific config. This must remain at the bottom + # of this file so it overrides the configuration defined above. + """ + ) + + project + end +end diff --git a/lib/nimble_template/addons/variants/phoenix/api/params_validation.ex b/lib/nimble_template/addons/variants/phoenix/api/params_validation.ex new file mode 100644 index 00000000..bc1a41fc --- /dev/null +++ b/lib/nimble_template/addons/variants/phoenix/api/params_validation.ex @@ -0,0 +1,69 @@ +defmodule NimbleTemplate.Addons.Phoenix.Api.ParamsValidation do + @moduledoc false + + use NimbleTemplate.Addons.Addon + + def do_apply(%Project{} = project, _opts) do + project + |> copy_files() + |> edit_files() + end + + defp edit_files(project) do + edit_web_entry_point(project) + end + + defp edit_web_entry_point(%Project{web_module: web_module, web_path: web_entry_point} = project) do + Generator.replace_content( + "#{web_entry_point}.ex", + """ + def controller do + quote do + use Phoenix.Controller, namespace: #{web_module} + + import Plug.Conn + import #{web_module}.Gettext + alias #{web_module}.Router.Helpers, as: Routes + """, + """ + def controller do + quote do + use Phoenix.Controller, namespace: #{web_module} + + import Plug.Conn + import #{web_module}.Gettext + + alias #{web_module}.ParamsValidator + alias #{web_module}.Router.Helpers, as: Routes + """ + ) + + project + end + + defp copy_files( + %Project{ + base_module: base_module, + web_module: web_module, + web_path: web_path, + web_test_path: web_test_path + } = project + ) do + binding = [ + web_module: web_module, + base_module: base_module + ] + + files = [ + {:eex, "lib/otp_app_web/params/params.ex.eex", "#{web_path}/params/params.ex"}, + {:eex, "lib/otp_app_web/params/params_validator.ex.eex", + "#{web_path}/params/params_validator.ex"}, + {:eex, "test/otp_app_web/params/params_validator_test.exs.eex", + "#{web_test_path}/params/params_validator_test.exs"} + ] + + Generator.copy_file(files, binding) + + project + end +end diff --git a/lib/nimble_template/addons/docker.ex b/lib/nimble_template/addons/variants/phoenix/docker.ex similarity index 91% rename from lib/nimble_template/addons/docker.ex rename to lib/nimble_template/addons/variants/phoenix/docker.ex index 8347650b..e39363f9 100644 --- a/lib/nimble_template/addons/docker.ex +++ b/lib/nimble_template/addons/variants/phoenix/docker.ex @@ -1,7 +1,7 @@ -defmodule NimbleTemplate.Addons.Docker do +defmodule NimbleTemplate.Addons.Phoenix.Docker do @moduledoc false - use NimbleTemplate.Addon + use NimbleTemplate.Addons.Addon @impl true def do_apply( diff --git a/lib/nimble_template/addons/variants/phoenix/ecto_data_migration.ex b/lib/nimble_template/addons/variants/phoenix/ecto_data_migration.ex new file mode 100644 index 00000000..939961ac --- /dev/null +++ b/lib/nimble_template/addons/variants/phoenix/ecto_data_migration.ex @@ -0,0 +1,73 @@ +defmodule NimbleTemplate.Addons.Phoenix.EctoDataMigration do + @moduledoc false + + use NimbleTemplate.Addons.Addon + + @impl true + def do_apply(%Project{} = project, _opts) do + project + |> copy_files() + |> edit_files() + end + + defp copy_files(%Project{} = project) do + Generator.copy_file([ + {:text, "priv/repo/data_migrations/.keep", "priv/repo/data_migrations/.keep"} + ]) + + project + end + + defp edit_files(%Project{} = project) do + edit_mix(project) + + project + end + + defp edit_mix(project) do + Generator.inject_content( + "mix.exs", + """ + defp aliases do + [ + """, + """ + "ecto.migrate_all": [ + "ecto.migrate --migrations-path=priv/repo/migrations --migrations-path=priv/repo/data_migrations" + ], + """ + ) + + Generator.replace_content( + "mix.exs", + """ + end + end + """, + """ + end + + defp migrate(_) do + if Mix.env() == :test do + Mix.Task.run("ecto.migrate", ["--quiet"]) + else + Mix.Task.run("ecto.migrate_all", []) + end + end + end + """ + ) + + Generator.replace_content( + "mix.exs", + """ + "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"], + """, + """ + "ecto.setup": ["ecto.create", &migrate/1, "run priv/repo/seeds.exs"], + """ + ) + + project + end +end diff --git a/lib/nimble_template/addons/ex_machina.ex b/lib/nimble_template/addons/variants/phoenix/ex_machina.ex similarity index 96% rename from lib/nimble_template/addons/ex_machina.ex rename to lib/nimble_template/addons/variants/phoenix/ex_machina.ex index a4aeca95..5582aad6 100644 --- a/lib/nimble_template/addons/ex_machina.ex +++ b/lib/nimble_template/addons/variants/phoenix/ex_machina.ex @@ -1,7 +1,7 @@ -defmodule NimbleTemplate.Addons.ExMachina do +defmodule NimbleTemplate.Addons.Phoenix.ExMachina do @moduledoc false - use NimbleTemplate.Addon + use NimbleTemplate.Addons.Addon @impl true def do_apply(%Project{} = project, _opts) do diff --git a/lib/nimble_template/addons/ex_vcr.ex b/lib/nimble_template/addons/variants/phoenix/ex_vcr.ex similarity index 77% rename from lib/nimble_template/addons/ex_vcr.ex rename to lib/nimble_template/addons/variants/phoenix/ex_vcr.ex index ed799752..11ac45a3 100644 --- a/lib/nimble_template/addons/ex_vcr.ex +++ b/lib/nimble_template/addons/variants/phoenix/ex_vcr.ex @@ -1,7 +1,7 @@ -defmodule NimbleTemplate.Addons.ExVCR do +defmodule NimbleTemplate.Addons.Phoenix.ExVCR do @moduledoc false - use NimbleTemplate.Addon + use NimbleTemplate.Addons.Addon @cassette_directory "test/support/fixtures/vcr_cassettes" @@ -34,7 +34,8 @@ defmodule NimbleTemplate.Addons.ExVCR do # Configurations for ExVCR config :exvcr, - vcr_cassette_library_dir: "#{@cassette_directory}" + vcr_cassette_library_dir: "#{@cassette_directory}", + ignore_localhost: true """ ) @@ -42,6 +43,17 @@ defmodule NimbleTemplate.Addons.ExVCR do end defp edit_case(project) do + Generator.inject_content( + "test/support/channel_case.ex", + """ + quote do + """, + """ + use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney + + """ + ) + Generator.inject_content( "test/support/conn_case.ex", """ diff --git a/lib/nimble_template/addons/variants/phoenix/gettext.ex b/lib/nimble_template/addons/variants/phoenix/gettext.ex new file mode 100644 index 00000000..6e6771a7 --- /dev/null +++ b/lib/nimble_template/addons/variants/phoenix/gettext.ex @@ -0,0 +1,25 @@ +defmodule NimbleTemplate.Addons.Phoenix.Gettext do + @moduledoc false + + use NimbleTemplate.Addons.Addon + + @impl true + def do_apply(%Project{} = project, _opts) do + edit_mix(project) + end + + defp edit_mix(%Project{} = project) do + Generator.inject_content( + "mix.exs", + """ + defp aliases do + [ + """, + """ + "gettext.extract-and-merge": ["gettext.extract --merge --no-fuzzy"], + """ + ) + + project + end +end diff --git a/lib/nimble_template/addons/variants/phoenix/health_plug.ex b/lib/nimble_template/addons/variants/phoenix/health_plug.ex new file mode 100644 index 00000000..668dcabf --- /dev/null +++ b/lib/nimble_template/addons/variants/phoenix/health_plug.ex @@ -0,0 +1,117 @@ +defmodule NimbleTemplate.Addons.Phoenix.HealthPlug do + @moduledoc false + + use NimbleTemplate.Addons.Addon + + @impl true + def do_apply(%Project{} = project, _opts) do + project + |> copy_files() + |> edit_files() + end + + defp copy_files( + %Project{ + web_module: web_module, + base_module: base_module, + web_path: web_path, + web_test_path: web_test_path, + otp_app: otp_app + } = project + ) do + binding = [ + web_module: web_module, + base_module: base_module, + otp_app: otp_app + ] + + files = [ + {:eex, "lib/otp_app_web/plugs/health_plug.ex.eex", "#{web_path}/plugs/health_plug.ex"}, + {:eex, "test/otp_app_web/plugs/health_plug_test.exs.eex", + "#{web_test_path}/plugs/health_plug_test.exs"}, + {:eex, "test/otp_app_web/requests/_health/liveness_request_test.exs.eex", + "#{web_test_path}/requests/_health/liveness_request_test.exs"}, + {:eex, "test/otp_app_web/requests/_health/rediness_request_test.exs.eex", + "#{web_test_path}/requests/_health/rediness_request_test.exs"} + ] + + Generator.copy_file(files, binding) + + project + end + + defp edit_files(project) do + project + |> edit_config() + |> edit_router() + |> edit_test_helper() + end + + defp edit_config(%Project{web_module: web_module, otp_app: otp_app} = project) do + Generator.replace_content( + "config/config.exs", + """ + config :#{otp_app}, #{web_module}.Endpoint, + """, + """ + config :#{otp_app}, #{web_module}.Endpoint, + health_path: "/_health", + """ + ) + + Generator.replace_content( + "config/runtime.exs", + """ + config :#{otp_app}, #{web_module}.Endpoint, + """, + """ + config :#{otp_app}, #{web_module}.Endpoint, + health_path: System.fetch_env!("HEALTH_PATH"), + """ + ) + + project + end + + defp edit_router(%Project{web_path: web_path, web_module: web_module, otp_app: otp_app} = project) do + Generator.replace_content( + "#{web_path}/router.ex", + """ + # coveralls-ignore-start + pipeline :api do + plug :accepts, ["json"] + end + + # coveralls-ignore-stop + """, + """ + # coveralls-ignore-start + pipeline :api do + plug :accepts, ["json"] + end + + # coveralls-ignore-stop + + forward Application.get_env(:#{otp_app}, #{web_module}.Endpoint)[:health_path], #{web_module}.HealthPlug + """ + ) + + project + end + + defp edit_test_helper(project) do + Generator.replace_content( + "test/test_helper.exs", + """ + ExUnit.start() + """, + """ + Mimic.copy(Ecto.Adapters.SQL) + + ExUnit.start() + """ + ) + + project + end +end diff --git a/lib/nimble_template/addons/makefile.ex b/lib/nimble_template/addons/variants/phoenix/makefile.ex similarity index 65% rename from lib/nimble_template/addons/makefile.ex rename to lib/nimble_template/addons/variants/phoenix/makefile.ex index b9c277f8..555e6404 100644 --- a/lib/nimble_template/addons/makefile.ex +++ b/lib/nimble_template/addons/variants/phoenix/makefile.ex @@ -1,7 +1,7 @@ -defmodule NimbleTemplate.Addons.Makefile do +defmodule NimbleTemplate.Addons.Phoenix.Makefile do @moduledoc false - use NimbleTemplate.Addon + use NimbleTemplate.Addons.Addon @impl true def do_apply(%Project{} = project, _opts) do diff --git a/lib/nimble_template/addons/variants/phoenix/mix_release.ex b/lib/nimble_template/addons/variants/phoenix/mix_release.ex new file mode 100644 index 00000000..63f37daf --- /dev/null +++ b/lib/nimble_template/addons/variants/phoenix/mix_release.ex @@ -0,0 +1,85 @@ +defmodule NimbleTemplate.Addons.Phoenix.MixRelease do + @moduledoc false + + use NimbleTemplate.Addons.Addon + + @impl true + def do_apply(%Project{} = project, _opts) do + project + |> copy_files() + |> edit_files() + + project + end + + defp copy_files( + %Project{otp_app: otp_app, base_module: base_module, base_path: base_path} = project + ) do + Generator.copy_file( + [ + {:eex, "lib/otp_app/release_tasks.ex.eex", base_path <> "/release_tasks.ex"} + ], + otp_app: otp_app, + base_module: base_module + ) + + project + end + + defp edit_files(%{otp_app: otp_app, web_module: web_module} = project) do + Generator.delete_content( + "config/runtime.exs", + """ + # Start the phoenix server if environment is set and running in a release + if System.get_env("PHX_SERVER") && System.get_env("RELEASE_NAME") do + config :#{otp_app}, #{web_module}.Endpoint, server: true + end + + """ + ) + + Generator.delete_content( + "config/runtime.exs", + """ + + # ## Using releases + # + # If you are doing OTP releases, you need to instruct Phoenix + # to start each relevant endpoint: + # + # config :#{otp_app}, #{web_module}.Endpoint, server: true + # + # Then you can assemble a release by calling `mix release`. + # See `mix help release` for more information. + """ + ) + + Generator.replace_content( + "config/runtime.exs", + """ + config :#{otp_app}, #{web_module}.Endpoint, + """, + """ + config :#{otp_app}, #{web_module}.Endpoint, + server: true, + """ + ) + + Generator.replace_content( + "config/runtime.exs", + """ + host = System.get_env("PHX_HOST") || "example.com" + """, + """ + host = + System.get_env("PHX_HOST") || + raise \"\"\" + Environment variable PHX_HOST is missing. + Set the Heroku endpoint to this variable. + \"\"\" + """ + ) + + project + end +end diff --git a/lib/nimble_template/addons/oban.ex b/lib/nimble_template/addons/variants/phoenix/oban.ex similarity index 93% rename from lib/nimble_template/addons/oban.ex rename to lib/nimble_template/addons/variants/phoenix/oban.ex index 3c8f06fc..9549de2b 100644 --- a/lib/nimble_template/addons/oban.ex +++ b/lib/nimble_template/addons/variants/phoenix/oban.ex @@ -1,7 +1,7 @@ -defmodule NimbleTemplate.Addons.Oban do +defmodule NimbleTemplate.Addons.Phoenix.Oban do @moduledoc false - use NimbleTemplate.Addon + use NimbleTemplate.Addons.Addon @impl true def do_apply(%Project{} = project, _opts) do @@ -36,7 +36,7 @@ defmodule NimbleTemplate.Addons.Oban do end defp create_folders(%Project{otp_app: otp_app} = project) do - File.mkdir("lib/" <> Atom.to_string(otp_app) <> "_worker") + Generator.make_directory("lib/" <> Atom.to_string(otp_app) <> "_worker") project end @@ -66,6 +66,7 @@ defmodule NimbleTemplate.Addons.Oban do """ # Tell Phoenix to update the endpoint configuration # whenever the application is updated. + @impl true def config_change(changed, _new, removed) do #{web_module}.Endpoint.config_change(changed, removed) :ok diff --git a/lib/nimble_template/addons/variants/phoenix/web/assets.ex b/lib/nimble_template/addons/variants/phoenix/web/assets.ex index 7f921c92..30e3a6d1 100644 --- a/lib/nimble_template/addons/variants/phoenix/web/assets.ex +++ b/lib/nimble_template/addons/variants/phoenix/web/assets.ex @@ -1,7 +1,7 @@ defmodule NimbleTemplate.Addons.Phoenix.Web.Assets do @moduledoc false - use NimbleTemplate.Addon + use NimbleTemplate.Addons.Addon @impl true def do_apply(%Project{} = project, _opts) do @@ -9,53 +9,19 @@ defmodule NimbleTemplate.Addons.Phoenix.Web.Assets do end defp edit_files(%Project{} = project) do - project - |> edit_mix() - |> edit_assets_package() - - project - end - - defp edit_mix(%Project{} = project) do - Generator.inject_content( - "mix.exs", - """ - defp aliases do - [ - """, - """ - "assets.compile": &compile_assets/1, - """ - ) - - Generator.replace_content( - "mix.exs", - """ - end - end - """, - """ - end - - defp compile_assets(_) do - Mix.shell().cmd("npm run --prefix assets build:dev", quiet: true) - end - end - """ - ) + enable_gzip_for_static_assets(project) project end - defp edit_assets_package(%Project{} = project) do + defp enable_gzip_for_static_assets(%Project{web_path: web_path} = project) do Generator.replace_content( - "assets/package.json", + "#{web_path}/endpoint.ex", """ - "watch": "webpack --mode development --watch" + gzip: false, """, """ - "watch": "webpack --mode development --watch", - "build:dev": "webpack --mode development" + gzip: true, """ ) diff --git a/lib/nimble_template/addons/variants/phoenix/web/bootstrap.ex b/lib/nimble_template/addons/variants/phoenix/web/bootstrap.ex new file mode 100644 index 00000000..ec87090f --- /dev/null +++ b/lib/nimble_template/addons/variants/phoenix/web/bootstrap.ex @@ -0,0 +1,154 @@ +defmodule NimbleTemplate.Addons.Phoenix.Web.Bootstrap do + @moduledoc false + + use NimbleTemplate.Addons.Addon + + @impl true + def do_apply(%Project{} = project, opts) do + project + |> edit_files(opts) + |> copy_files() + end + + defp edit_files(%Project{} = project, opts) do + project + |> edit_assets_package() + |> edit_app_js(opts) + |> edit_app_scss(opts) + |> edit_vendor_index(opts) + |> edit_css_variables(opts) + + project + end + + defp copy_files(%Project{} = project) do + copy_bootstrap_vendor(project) + + project + end + + defp edit_assets_package(%Project{} = project) do + Generator.replace_content( + "assets/package.json", + """ + "dependencies": { + """, + """ + "dependencies": { + "@popperjs/core": "^2.11.2", + "bootstrap": "5.1.3", + """ + ) + + project + end + + defp edit_app_js(project, %{with_nimble_js_addon: true}) do + Generator.replace_content( + "assets/js/app.js", + """ + import "phoenix_html" + """, + """ + // Bootstrap + import "bootstrap/dist/js/bootstrap"; + + import "phoenix_html" + """ + ) + + project + end + + defp edit_app_js(project, %{with_nimble_js_addon: false}) do + Generator.replace_content( + "assets/js/app.js", + """ + import "phoenix_html" + """, + """ + // Bootstrap + import "bootstrap/dist/js/bootstrap"; + + import "phoenix_html" + """ + ) + + project + end + + defp edit_app_scss(project, %{with_nimble_css_addon: true}), do: project + + defp edit_app_scss(project, %{with_nimble_css_addon: false}) do + Generator.replace_content( + "assets/css/app.scss", + """ + @import "./phoenix.css"; + """, + """ + @import "./phoenix.css"; + + @import './vendor/'; + """ + ) + + project + end + + defp edit_css_variables(project, %{with_nimble_css_addon: false}) do + Generator.create_file( + "assets/css/_variables.scss", + """ + //////////////////////////////// + // Shared variables // + //////////////////////////////// + + + //////////////////////////////// + // Custom Bootstrap variables // + //////////////////////////////// + """ + ) + + project + end + + defp edit_css_variables(project, %{with_nimble_css_addon: true}) do + Generator.append_content( + "assets/css/_variables.scss", + """ + //////////////////////////////// + // Shared variables // + //////////////////////////////// + + + //////////////////////////////// + // Custom Bootstrap variables // + //////////////////////////////// + """ + ) + + project + end + + defp edit_vendor_index(project, %{with_nimble_css_addon: true}) do + Generator.append_content("assets/css/vendor/_index.scss", "@import './bootstrap';") + + project + end + + defp edit_vendor_index(project, %{with_nimble_css_addon: false}) do + Generator.make_directory("assets/css/vendor/", false) + Generator.create_file("assets/css/vendor/_index.scss", "@import './bootstrap';") + + project + end + + defp copy_bootstrap_vendor(%Project{} = project) do + Generator.copy_file([ + {:text, "assets/bootstrap_css/vendor/_bootstrap.scss", "assets/css/vendor/_bootstrap.scss"} + ]) + + project + end +end diff --git a/lib/nimble_template/addons/variants/phoenix/web/core_js.ex b/lib/nimble_template/addons/variants/phoenix/web/core_js.ex index 54d044e5..f6a757c0 100644 --- a/lib/nimble_template/addons/variants/phoenix/web/core_js.ex +++ b/lib/nimble_template/addons/variants/phoenix/web/core_js.ex @@ -1,7 +1,7 @@ defmodule NimbleTemplate.Addons.Phoenix.Web.CoreJS do @moduledoc false - use NimbleTemplate.Addon + use NimbleTemplate.Addons.Addon @impl true def do_apply(%Project{} = project, _opts) do @@ -16,30 +16,17 @@ defmodule NimbleTemplate.Addons.Phoenix.Web.CoreJS do project end - def edit_package_json(%Project{live_project?: live_project?} = project) do - if live_project? do - Generator.replace_content( - "assets/package.json", - """ - "phoenix_html": "file:../deps/phoenix_html", - """, - """ - "phoenix_html": "file:../deps/phoenix_html", - "core-js": "^3.7.0", - """ - ) - else - Generator.replace_content( - "assets/package.json", - """ - "phoenix_html": "file:../deps/phoenix_html" - """, - """ - "phoenix_html": "file:../deps/phoenix_html", - "core-js": "^3.7.0" - """ - ) - end + def edit_package_json(%Project{} = project) do + Generator.replace_content( + "assets/package.json", + """ + "dependencies": { + """, + """ + "dependencies": { + "core-js": "^3.21.1" + """ + ) project end @@ -53,7 +40,6 @@ defmodule NimbleTemplate.Addons.Phoenix.Web.CoreJS do """ // CoreJS import "core-js/stable" - import "regenerator-runtime/runtime" import "phoenix_html" """ diff --git a/lib/nimble_template/addons/variants/phoenix/web/dart_sass.ex b/lib/nimble_template/addons/variants/phoenix/web/dart_sass.ex new file mode 100644 index 00000000..620977bc --- /dev/null +++ b/lib/nimble_template/addons/variants/phoenix/web/dart_sass.ex @@ -0,0 +1,113 @@ +defmodule NimbleTemplate.Addons.Phoenix.Web.DartSass do + @moduledoc false + + use NimbleTemplate.Addons.Addon + + @dart_sass_version "1.49.8" + + @impl true + def do_apply(%Project{} = project, _opts) do + project + |> inject_mix_dependency() + |> edit_config() + |> edit_mix() + |> edit_app_js() + |> rename_app_css() + end + + defp inject_mix_dependency(%Project{} = project) do + Generator.inject_mix_dependency( + {:dart_sass, latest_package_version(:dart_sass), runtime: "Mix.env() == :dev"} + ) + + Generator.replace_content( + "mix.exs", + "runtime: \"Mix.env() == :dev\"", + "runtime: Mix.env() == :dev" + ) + + project + end + + defp edit_config(%Project{} = project) do + Generator.replace_content( + "config/config.exs", + """ + # Configure esbuild (the version is required) + """, + """ + # Configure dart_sass (the version is required) + config :dart_sass, + version: "#{@dart_sass_version}", + default: [ + args: ~w( + --load-path=./node_modules + css/app.scss + ../priv/static/assets/app.css + ), + cd: Path.expand("../assets", __DIR__) + ] + + # Configure esbuild (the version is required) + """ + ) + + Generator.replace_content( + "config/dev.exs", + """ + watchers: [ + """, + """ + watchers: [ + sass: { + DartSass, + :install_and_run, + [:default, ~w(--embed-source-map --source-map-urls=absolute --watch)] + }, + """ + ) + + project + end + + defp edit_mix(project) do + Generator.replace_content( + "mix.exs", + """ + "assets.deploy": ["esbuild default --minify", "phx.digest"] + """, + """ + "assets.deploy": [ + "esbuild default --minify", + "sass default --no-source-map --style=compressed", + "phx.digest" + ] + """ + ) + + project + end + + defp edit_app_js(project) do + Generator.delete_content( + "assets/js/app.js", + """ + // We import the CSS which is extracted to its own file by esbuild. + // Remove this line if you add a your own CSS build pipeline (e.g postcss). + import "../css/app.css" + + """ + ) + + project + end + + defp rename_app_css(project) do + Generator.rename_file( + "assets/css/app.css", + "assets/css/app.scss" + ) + + project + end +end diff --git a/lib/nimble_template/addons/variants/phoenix/web/es_lint.ex b/lib/nimble_template/addons/variants/phoenix/web/es_lint.ex new file mode 100644 index 00000000..bf3446f9 --- /dev/null +++ b/lib/nimble_template/addons/variants/phoenix/web/es_lint.ex @@ -0,0 +1,100 @@ +defmodule NimbleTemplate.Addons.Phoenix.Web.EsLint do + @moduledoc false + + use NimbleTemplate.Addons.Addon + + @impl true + def do_apply(%Project{} = project, _opts) do + project + |> edit_files() + |> copy_files() + end + + defp edit_files(%Project{} = project) do + project + |> edit_assets_package() + |> edit_mix() + |> edit_app_js() + + project + end + + defp copy_files(%Project{} = project) do + Generator.copy_file([{:text, "assets/.eslintrc.json", "assets/.eslintrc.json"}]) + + project + end + + defp edit_assets_package(%Project{} = project) do + Generator.replace_content( + "assets/package.json", + """ + "scripts": { + """, + """ + "scripts": { + "eslint": "eslint --color ./", + "eslint.fix": "eslint --color --fix ./" + """ + ) + + Generator.replace_content( + "assets/package.json", + """ + "devDependencies": { + """, + """ + "devDependencies": { + "eslint": "^8.10.0", + "eslint-config-prettier": "^8.4.0", + "eslint-plugin-prettier": "^4.0.0", + """ + ) + + project + end + + defp edit_mix(%Project{} = project) do + Generator.replace_content( + "mix.exs", + """ + codebase: [ + """, + """ + codebase: [ + "cmd npm run eslint --prefix assets", + """ + ) + + Generator.replace_content( + "mix.exs", + """ + "codebase.fix": [ + """, + """ + "codebase.fix": [ + "cmd npm run eslint.fix --prefix assets", + """ + ) + + project + end + + def edit_app_js(%Project{live_project?: true} = project) do + Generator.replace_content( + "assets/js/app.js", + "window.addEventListener(\"phx:page-loading-start\", info => topbar.show())", + "window.addEventListener(\"phx:page-loading-start\", _info => topbar.show())" + ) + + Generator.replace_content( + "assets/js/app.js", + "window.addEventListener(\"phx:page-loading-stop\", info => topbar.hide())", + "window.addEventListener(\"phx:page-loading-stop\", _info => topbar.hide())" + ) + + project + end + + def edit_app_js(project), do: project +end diff --git a/lib/nimble_template/addons/variants/phoenix/web/nimble_css.ex b/lib/nimble_template/addons/variants/phoenix/web/nimble_css.ex new file mode 100644 index 00000000..e0d1a7fe --- /dev/null +++ b/lib/nimble_template/addons/variants/phoenix/web/nimble_css.ex @@ -0,0 +1,42 @@ +defmodule NimbleTemplate.Addons.Phoenix.Web.NimbleCSS do + @moduledoc false + + use NimbleTemplate.Addons.Addon + + @impl true + def do_apply(%Project{} = project, _opts) do + project + |> remove_default_phoenix_structure() + |> copy_nimble_structure() + |> edit_style_lint_rc() + end + + defp remove_default_phoenix_structure(project) do + File.rm_rf!("assets/css") + + project + end + + defp copy_nimble_structure(project) do + Generator.copy_directory("assets/nimble_css", "assets/css") + + project + end + + defp edit_style_lint_rc(project) do + Generator.replace_content( + "assets/.stylelintrc.json", + """ + "ignoreFiles": [ + "css/app.css", + "css/phoenix.css" + ], + """, + """ + "ignoreFiles": [], + """ + ) + + project + end +end diff --git a/lib/nimble_template/addons/variants/phoenix/web/nimble_js.ex b/lib/nimble_template/addons/variants/phoenix/web/nimble_js.ex new file mode 100644 index 00000000..2a3ccd34 --- /dev/null +++ b/lib/nimble_template/addons/variants/phoenix/web/nimble_js.ex @@ -0,0 +1,61 @@ +defmodule NimbleTemplate.Addons.Phoenix.Web.NimbleJS do + @moduledoc false + + use NimbleTemplate.Addons.Addon + + @impl true + def do_apply(%Project{} = project, _opts) do + project + |> copy_nimble_structure() + |> moving_js_vendor() + |> edit_app_js() + |> edit_es_lint_config() + end + + defp copy_nimble_structure(project) do + Generator.copy_directory("assets/nimble_js", "assets/js") + + project + end + + defp moving_js_vendor(project) do + Generator.rename_file("assets/vendor", "assets/js/vendor") + + project + end + + defp edit_app_js(project) do + Generator.append_content( + "assets/js/app.js", + """ + + // Application + import "./initializers/"; + + import "./screens/"; + """ + ) + + Generator.replace_content("assets/js/app.js", "assets/vendor", "assets/js/vendor") + + Generator.replace_content( + "assets/js/app.js", + "../vendor/some-package.js", + "./vendor/some-package.js" + ) + + Generator.replace_content("assets/js/app.js", "../vendor/topbar", "./vendor/topbar") + + project + end + + defp edit_es_lint_config(project) do + Generator.replace_content( + "assets/.eslintrc.json", + "/vendor/topbar.js", + "/js/vendor/topbar.js" + ) + + project + end +end diff --git a/lib/nimble_template/addons/variants/phoenix/web/node_package.ex b/lib/nimble_template/addons/variants/phoenix/web/node_package.ex new file mode 100644 index 00000000..771e704e --- /dev/null +++ b/lib/nimble_template/addons/variants/phoenix/web/node_package.ex @@ -0,0 +1,12 @@ +defmodule NimbleTemplate.Addons.Phoenix.Web.NodePackage do + @moduledoc false + + use NimbleTemplate.Addons.Addon + + @impl true + def do_apply(%Project{} = project, _opts) do + Generator.copy_file([{:text, "assets/package.json", "assets/package.json"}]) + + project + end +end diff --git a/lib/nimble_template/addons/variants/phoenix/web/prettier.ex b/lib/nimble_template/addons/variants/phoenix/web/prettier.ex new file mode 100644 index 00000000..5337a08c --- /dev/null +++ b/lib/nimble_template/addons/variants/phoenix/web/prettier.ex @@ -0,0 +1,96 @@ +defmodule NimbleTemplate.Addons.Phoenix.Web.Prettier do + @moduledoc false + + use NimbleTemplate.Addons.Addon + + @impl true + def do_apply(%Project{} = project, _opts) do + project + |> edit_files() + |> copy_files() + end + + defp edit_files(%Project{} = project) do + project + |> edit_npm_dev_dependencies() + |> edit_mix() + + project + end + + defp edit_npm_dev_dependencies(%Project{} = project) do + Generator.replace_content( + "assets/package.json", + """ + "devDependencies": { + """, + """ + "devDependencies": { + "prettier": "2.5.1", + "prettier-plugin-eex": "^0.5.0" + """ + ) + + project + end + + defp edit_mix(%Project{} = project) do + project + |> add_prettier_aliases() + |> add_prettier_into_codebase() + |> add_prettier_fix_into_codebase_fix() + end + + defp add_prettier_aliases(%Project{} = project) do + Generator.inject_content( + "mix.exs", + """ + defp aliases do + [ + """, + """ + prettier: "cmd ./assets/node_modules/.bin/prettier --check . --color", + "prettier.fix": "cmd ./assets/node_modules/.bin/prettier --write . --color", + """ + ) + + project + end + + defp add_prettier_into_codebase(%Project{} = project) do + Generator.replace_content( + "mix.exs", + """ + codebase: [ + """, + """ + codebase: [ + "prettier", + """ + ) + + project + end + + defp add_prettier_fix_into_codebase_fix(%Project{} = project) do + Generator.replace_content( + "mix.exs", + """ + "codebase.fix": [ + """, + """ + "codebase.fix": [ + "prettier.fix", + """ + ) + + project + end + + defp copy_files(%Project{} = project) do + Generator.copy_file([{:text, ".prettierignore", ".prettierignore"}]) + Generator.copy_file([{:text, ".prettierrc.yaml", ".prettierrc.yaml"}]) + + project + end +end diff --git a/lib/nimble_template/addons/variants/phoenix/web/sobelow.ex b/lib/nimble_template/addons/variants/phoenix/web/sobelow.ex index a597b610..3388123f 100644 --- a/lib/nimble_template/addons/variants/phoenix/web/sobelow.ex +++ b/lib/nimble_template/addons/variants/phoenix/web/sobelow.ex @@ -1,7 +1,7 @@ defmodule NimbleTemplate.Addons.Phoenix.Web.Sobelow do @moduledoc false - use NimbleTemplate.Addon + use NimbleTemplate.Addons.Addon @impl true def do_apply(%Project{} = project, _opts) do @@ -34,15 +34,11 @@ defmodule NimbleTemplate.Addons.Phoenix.Web.Sobelow do Generator.replace_content( "mix.exs", """ - codebase: ["deps.unlock --check-unused", "format --check-formatted", "credo --strict"], + codebase: [ """, """ codebase: [ - "deps.unlock --check-unused", - "format --check-formatted", - "credo --strict", - "sobelow --config" - ], + "sobelow --config", """ ) diff --git a/lib/nimble_template/addons/variants/phoenix/web/style_lint.ex b/lib/nimble_template/addons/variants/phoenix/web/style_lint.ex new file mode 100644 index 00000000..e5dcb700 --- /dev/null +++ b/lib/nimble_template/addons/variants/phoenix/web/style_lint.ex @@ -0,0 +1,81 @@ +defmodule NimbleTemplate.Addons.Phoenix.Web.StyleLint do + @moduledoc false + + use NimbleTemplate.Addons.Addon + + @impl true + def do_apply(%Project{} = project, _opts) do + project + |> edit_files() + |> copy_files() + end + + defp edit_files(%Project{} = project) do + project + |> edit_assets_package() + |> edit_mix() + + project + end + + defp copy_files(%Project{} = project) do + Generator.copy_file([{:text, "assets/.stylelintrc.json", "assets/.stylelintrc.json"}]) + + project + end + + defp edit_assets_package(%Project{} = project) do + Generator.replace_content( + "assets/package.json", + """ + "scripts": { + """, + """ + "scripts": { + "stylelint": "stylelint --color ./css", + "stylelint.fix": "stylelint --color --fix ./css", + """ + ) + + Generator.replace_content( + "assets/package.json", + """ + "devDependencies": { + """, + """ + "devDependencies": { + "stylelint": "^14.5.3", + "stylelint-config-property-sort-order-smacss": "^9.0.0", + "stylelint-config-sass-guidelines": "^9.0.1", + """ + ) + + project + end + + defp edit_mix(%Project{} = project) do + Generator.replace_content( + "mix.exs", + """ + codebase: [ + """, + """ + codebase: [ + "cmd npm run stylelint --prefix assets", + """ + ) + + Generator.replace_content( + "mix.exs", + """ + "codebase.fix": [ + """, + """ + "codebase.fix": [ + "cmd npm run stylelint.fix --prefix assets", + """ + ) + + project + end +end diff --git a/lib/nimble_template/addons/variants/phoenix/web/svg_sprite.ex b/lib/nimble_template/addons/variants/phoenix/web/svg_sprite.ex new file mode 100644 index 00000000..ed6e86f2 --- /dev/null +++ b/lib/nimble_template/addons/variants/phoenix/web/svg_sprite.ex @@ -0,0 +1,117 @@ +defmodule NimbleTemplate.Addons.Phoenix.Web.SvgSprite do + @moduledoc false + + use NimbleTemplate.Addons.Addon + + @impl true + def do_apply(%Project{} = project, _opts) do + project + |> edit_files() + |> copy_files() + end + + defp edit_files(%Project{} = project) do + project + |> edit_assets_package() + |> edit_web_entry_point() + |> edit_wiki_sidebar() + + project + end + + defp copy_files(%Project{} = project) do + project + |> copy_icon_helper() + |> copy_wiki_documentation() + + project + end + + defp edit_assets_package(%Project{} = project) do + Generator.replace_content( + "assets/package.json", + """ + "scripts": { + """, + """ + "scripts": { + "svg-sprite.generate-icon": "svg-sprite --shape-id-generator \\"icon-%s\\" --symbol --symbol-dest static/images --symbol-sprite icon-sprite.svg static/images/icons/*.svg", + """ + ) + + Generator.replace_content( + "assets/package.json", + """ + "devDependencies": { + """, + """ + "devDependencies": { + "svg-sprite": "^1.5.4", + """ + ) + + project + end + + defp edit_web_entry_point(%Project{web_module: web_module, web_path: web_path} = project) do + Generator.replace_content( + "#{web_path}.ex", + """ + # Include shared imports and aliases for views + unquote(view_helpers()) + """, + """ + import #{web_module}.IconHelper + + # Include shared imports and aliases for views + unquote(view_helpers()) + """ + ) + + project + end + + defp edit_wiki_sidebar(project) do + if has_github_wiki_directory?() do + Generator.replace_content( + ".github/wiki/_Sidebar.md", + """ + - [[Getting Started]] + """, + """ + - [[Getting Started]] + + ## Operations + + - [[Icon Sprite]] + """ + ) + end + + project + end + + defp copy_icon_helper( + %Project{web_module: web_module, web_path: web_path, web_test_path: web_test_path} = + project + ) do + Generator.copy_file( + [ + {:eex, "lib/otp_app_web/helpers/icon_helper.ex.eex", "#{web_path}/helpers/icon_helper.ex"}, + {:eex, "test/otp_app_web/helpers/icon_helper_test.exs.eex", + "#{web_test_path}/helpers/icon_helper_test.exs"} + ], + web_module: web_module + ) + + project + end + + defp copy_wiki_documentation(%Project{} = project) do + if has_github_wiki_directory?() do + Generator.copy_file([{:text, ".github/wiki/Icon-Sprite.md", ".github/wiki/Icon-Sprite.md"}]) + end + + project + end +end diff --git a/lib/nimble_template/addons/variants/phoenix/web/wallaby.ex b/lib/nimble_template/addons/variants/phoenix/web/wallaby.ex index 2d489759..43234638 100644 --- a/lib/nimble_template/addons/variants/phoenix/web/wallaby.ex +++ b/lib/nimble_template/addons/variants/phoenix/web/wallaby.ex @@ -1,7 +1,7 @@ defmodule NimbleTemplate.Addons.Phoenix.Web.Wallaby do @moduledoc false - use NimbleTemplate.Addon + use NimbleTemplate.Addons.Addon @impl true def do_apply(%Project{} = project, _opts) do diff --git a/lib/nimble_template/helpers/addon.ex b/lib/nimble_template/helpers/addon.ex new file mode 100644 index 00000000..be699552 --- /dev/null +++ b/lib/nimble_template/helpers/addon.ex @@ -0,0 +1,4 @@ +defmodule NimbleTemplate.AddonHelper do + def install_addon_prompt?(addon), + do: Mix.shell().yes?("\nWould you like to add the #{addon} addon?") +end diff --git a/lib/nimble_template/helpers/dependency.ex b/lib/nimble_template/helpers/dependency.ex new file mode 100644 index 00000000..eb6ad62b --- /dev/null +++ b/lib/nimble_template/helpers/dependency.ex @@ -0,0 +1,29 @@ +defmodule NimbleTemplate.DependencyHelper do + alias NimbleTemplate.Generator + + def order_dependencies! do + file_content = File.read!("mix.exs") + + dependencies = extract_dependencies(file_content) + + ordered_dependencies = + dependencies + |> String.split(",\n") + |> Enum.sort() + |> Enum.join(",\n") + + Generator.replace_content("mix.exs", dependencies, ordered_dependencies) + end + + defp extract_dependencies(contents) do + [_, deps_with_file_footer] = + String.split(contents, """ + defp deps do + [ + """) + + [deps | _footer] = String.split(deps_with_file_footer, "\n ]\n end") + + deps + end +end diff --git a/lib/nimble_template/generator.ex b/lib/nimble_template/helpers/generator.ex similarity index 81% rename from lib/nimble_template/generator.ex rename to lib/nimble_template/helpers/generator.ex index 376ba90e..5787be83 100644 --- a/lib/nimble_template/generator.ex +++ b/lib/nimble_template/helpers/generator.ex @@ -3,6 +3,16 @@ defmodule NimbleTemplate.Generator do @template_resource "priv/templates/nimble_template" + def copy_directory(source_path, target_path, binding \\ []) do + root = Application.app_dir(:nimble_template, @template_resource) + + "#{root}/#{source_path}/**/*" + |> Path.wildcard(match_dot: true) + |> Enum.reject(&File.dir?/1) + |> Enum.map(&String.replace(&1, "#{root}/", "")) + |> Enum.each(©_file([{:text, &1, String.replace(&1, source_path, target_path)}], binding)) + end + def copy_file(files, binding \\ []) do Mix.Phoenix.copy_from( [:nimble_template], @@ -12,6 +22,8 @@ defmodule NimbleTemplate.Generator do ) end + def rename_file(old_path, new_path), do: File.rename(old_path, new_path) + def replace_content(file_path, anchor, content) do file = Path.join([file_path]) @@ -105,6 +117,16 @@ defmodule NimbleTemplate.Generator do create_keep_file(path, touch_directory) end + def create_file(path, content) do + case File.write(path, content) do + :ok -> + :ok + + {:error, reason} -> + Mix.raise(~s[Failed to create file at #{path}, reason: #{Atom.to_string(reason)}]) + end + end + def print_log(prefix, content \\ ""), do: Mix.shell().info([:green, prefix, :reset, content]) defp split_with_self(contents, text) do diff --git a/lib/nimble_template/helpers/github.ex b/lib/nimble_template/helpers/github.ex new file mode 100644 index 00000000..d5e3835a --- /dev/null +++ b/lib/nimble_template/helpers/github.ex @@ -0,0 +1,29 @@ +defmodule NimbleTemplate.GithubHelper do + def host_on_github?(), do: Mix.shell().yes?("\nWill you host this project on Github?") + + def generate_github_template?(), + do: + Mix.shell().yes?( + "\nDo you want to generate the .github/ISSUE_TEMPLATE and .github/PULL_REQUEST_TEMPLATE?" + ) + + def generate_github_action?(), + do: Mix.shell().yes?("\nDo you want to generate the Github Action workflow?") + + def generate_github_wiki?(), + do: + Mix.shell().yes?( + "\nDo you want to publish a Github Wiki for this project? You'd need to manually create the first Github Wiki Page and set the GH_TOKEN and GH_EMAIL secret for this to properly function." + ) + + def generate_github_workflows_readme?(), + do: Mix.shell().yes?("\nDo you want to generate the .github/workflows/README file?") + + def generate_github_action_test?(), + do: Mix.shell().yes?("\nDo you want to generate the Github Action workflows: Test?") + + def generate_github_action_deploy_heroku?(), + do: Mix.shell().yes?("\nDo you want to generate the Github Action workflows: Deploy to Heroku?") + + def has_github_wiki_directory?(), do: File.dir?(".github/wiki/") +end diff --git a/lib/nimble_template/project.ex b/lib/nimble_template/projects/project.ex similarity index 91% rename from lib/nimble_template/project.ex rename to lib/nimble_template/projects/project.ex index 87844838..557245cc 100644 --- a/lib/nimble_template/project.ex +++ b/lib/nimble_template/projects/project.ex @@ -1,10 +1,10 @@ -defmodule NimbleTemplate.Project do +defmodule NimbleTemplate.Projects.Project do @moduledoc false - @alpine_version "3.13.2" - @elixir_version "1.11.4" - @erlang_version "23.3" - @node_version "14" + @alpine_version "3.15.0" + @elixir_version "1.13.3" + @erlang_version "24.2.2" + @node_version "16" defstruct base_module: nil, base_path: nil, diff --git a/lib/nimble_template/template.ex b/lib/nimble_template/template.ex deleted file mode 100644 index 0db74daa..00000000 --- a/lib/nimble_template/template.ex +++ /dev/null @@ -1,37 +0,0 @@ -defmodule NimbleTemplate.Template do - @moduledoc false - - alias NimbleTemplate.Mix.Template, as: MixTemplate - alias NimbleTemplate.Phoenix.Template, as: PhoenixTemplate - alias NimbleTemplate.Project - - def apply(%Project{mix_project?: true} = project) do - MixTemplate.apply(project) - - fetch_and_install_dependencies() - end - - def apply(%Project{} = project) do - PhoenixTemplate.apply(project) - - fetch_and_install_dependencies() - end - - def host_on_github?(), do: Mix.shell().yes?("\nWill you host this project on Github?") - - def generate_github_template?(), - do: - Mix.shell().yes?( - "\nDo you want to generate the .github/ISSUE_TEMPLATE and .github/PULL_REQUEST_TEMPLATE?" - ) - - def generate_github_action?(), - do: Mix.shell().yes?("\nDo you want to generate the Github Action workflow?") - - def install_addon_prompt?(addon), - do: Mix.shell().yes?("\nWould you like to add the #{addon} addon?") - - defp fetch_and_install_dependencies(), - do: - if(Mix.shell().yes?("\nFetch and install dependencies?"), do: Mix.shell().cmd("mix deps.get")) -end diff --git a/lib/nimble_template/templates/template.ex b/lib/nimble_template/templates/template.ex new file mode 100644 index 00000000..1880eabe --- /dev/null +++ b/lib/nimble_template/templates/template.ex @@ -0,0 +1,58 @@ +defmodule NimbleTemplate.Templates.Template do + @moduledoc false + + import NimbleTemplate.DependencyHelper + + alias NimbleTemplate.Addons.ExUnit + alias NimbleTemplate.Projects.Project + alias NimbleTemplate.Templates.Mix.Template, as: MixTemplate + alias NimbleTemplate.Templates.Phoenix.Template, as: PhoenixTemplate + + def apply(%Project{mix_project?: true} = project) do + MixTemplate.apply(project) + + ExUnit.apply(project) + + post_apply(project) + end + + def apply(%Project{mix_project?: false} = project) do + PhoenixTemplate.apply(project) + + ExUnit.apply(project) + + post_apply(project) + end + + defp post_apply(%Project{mix_project?: true}) do + order_dependencies!() + fetch_and_install_elixir_dependencies() + format_codebase() + end + + defp post_apply(%Project{api_project?: true}) do + order_dependencies!() + fetch_and_install_elixir_dependencies() + format_codebase() + end + + defp post_apply(%Project{web_project?: true}) do + order_dependencies!() + fetch_and_install_elixir_dependencies() + fetch_and_install_node_dependencies() + format_codebase() + end + + defp fetch_and_install_elixir_dependencies() do + Mix.shell().cmd("MIX_ENV=develop mix do deps.get, deps.compile") + Mix.shell().cmd("MIX_ENV=test mix do deps.get, deps.compile") + end + + defp fetch_and_install_node_dependencies() do + Mix.shell().cmd("npm install --prefix assets") + end + + defp format_codebase() do + Mix.shell().cmd("mix codebase.fix") + end +end diff --git a/lib/nimble_template/templates/variants/mix/template.ex b/lib/nimble_template/templates/variants/mix/template.ex new file mode 100644 index 00000000..e34a9178 --- /dev/null +++ b/lib/nimble_template/templates/variants/mix/template.ex @@ -0,0 +1,52 @@ +defmodule NimbleTemplate.Templates.Mix.Template do + @moduledoc false + + import NimbleTemplate.{AddonHelper, GithubHelper} + + alias NimbleTemplate.Addons + alias NimbleTemplate.Projects.Project + + def apply(%Project{} = project) do + project + |> apply_default_mix_addons() + |> apply_optional_mix_addons() + end + + defp apply_default_mix_addons(project) do + project + |> Addons.ElixirVersion.apply() + |> Addons.Readme.apply() + |> Addons.TestEnv.apply() + |> Addons.Credo.apply() + |> Addons.Dialyxir.apply() + |> Addons.ExCoveralls.apply() + |> Addons.Faker.apply() + end + + defp apply_optional_mix_addons(project) do + if host_on_github?() do + if generate_github_template?(), + do: Addons.Github.apply(project, %{github_template: true}) + + generate_github_action_test? = generate_github_action_test?() + + if generate_github_action_test?, + do: Addons.Github.apply(project, %{github_action_test: true}) + + if generate_github_workflows_readme?(), + do: + Addons.Github.apply(project, %{ + github_workflows_readme: true, + with_test_workflow?: generate_github_action_test?, + with_deploy_to_heroku_workflow?: false + }) + + if generate_github_wiki?(), + do: Addons.Github.apply(project, %{github_wiki: true}) + end + + if install_addon_prompt?("Mimic"), do: Addons.Mimic.apply(project) + + project + end +end diff --git a/lib/nimble_template/templates/variants/phoenix/api/template.ex b/lib/nimble_template/templates/variants/phoenix/api/template.ex new file mode 100644 index 00000000..4924c5e7 --- /dev/null +++ b/lib/nimble_template/templates/variants/phoenix/api/template.ex @@ -0,0 +1,20 @@ +defmodule NimbleTemplate.Templates.Phoenix.Api.Template do + @moduledoc false + + alias NimbleTemplate.Addons.Phoenix.Api + alias NimbleTemplate.Projects.Project + + def apply(%Project{} = project) do + apply_default_api_addons(project) + end + + defp apply_default_api_addons(project) do + project + |> Api.Config.apply() + |> Api.EmptyBodyParamsPlug.apply() + |> Api.ParamsValidation.apply() + |> Api.ErrorView.apply() + |> Api.JsonApi.apply() + |> Api.FallbackController.apply() + end +end diff --git a/lib/nimble_template/templates/variants/phoenix/live/template.ex b/lib/nimble_template/templates/variants/phoenix/live/template.ex new file mode 100644 index 00000000..d226357a --- /dev/null +++ b/lib/nimble_template/templates/variants/phoenix/live/template.ex @@ -0,0 +1,14 @@ +defmodule NimbleTemplate.Templates.Phoenix.Live.Template do + @moduledoc false + + alias NimbleTemplate.Projects.Project + alias NimbleTemplate.Templates.Phoenix.Web.Template, as: WebTemplate + + def apply(%Project{} = project) do + apply_web_addons(project) + end + + defp apply_web_addons(project) do + WebTemplate.apply(project) + end +end diff --git a/lib/nimble_template/templates/variants/phoenix/template.ex b/lib/nimble_template/templates/variants/phoenix/template.ex new file mode 100644 index 00000000..bf85479a --- /dev/null +++ b/lib/nimble_template/templates/variants/phoenix/template.ex @@ -0,0 +1,108 @@ +defmodule NimbleTemplate.Templates.Phoenix.Template do + @moduledoc false + + import NimbleTemplate.{AddonHelper, GithubHelper} + + alias NimbleTemplate.Addons + alias NimbleTemplate.Addons.Phoenix, as: PhoenixAddons + alias NimbleTemplate.Projects.Project + alias NimbleTemplate.Templates.Phoenix.Api.Template, as: ApiTemplate + alias NimbleTemplate.Templates.Phoenix.Live.Template, as: LiveTemplate + alias NimbleTemplate.Templates.Phoenix.Web.Template, as: WebTemplate + + def apply(%Project{} = project) do + project + |> apply_phoenix_common_setup() + |> apply_phoenix_variant_setup() + end + + # credo:disable-for-next-line Credo.Check.Refactor.ABCSize + defp apply_phoenix_common_setup(%Project{} = project) do + project + |> apply_default_common_phoenix_addons() + |> apply_optional_common_phoenix_addons() + end + + defp apply_default_common_phoenix_addons(project) do + project + |> apply_default_common_addons() + |> apply_default_phoenix_addons() + end + + defp apply_default_common_addons(project) do + project + |> Addons.ElixirVersion.apply() + |> Addons.Readme.apply() + |> Addons.TestEnv.apply() + |> Addons.Credo.apply() + |> Addons.Dialyxir.apply() + |> Addons.ExCoveralls.apply() + |> Addons.Mimic.apply() + |> Addons.Faker.apply() + end + + defp apply_default_phoenix_addons(project) do + project + |> PhoenixAddons.ExMachina.apply() + |> PhoenixAddons.Makefile.apply() + |> PhoenixAddons.Docker.apply() + |> PhoenixAddons.EctoDataMigration.apply() + |> PhoenixAddons.MixRelease.apply() + |> PhoenixAddons.HealthPlug.apply() + |> PhoenixAddons.Gettext.apply(project) + end + + defp apply_optional_common_phoenix_addons(project) do + project + |> apply_optional_common_addons() + |> apply_optional_phoenix_addons() + end + + defp apply_optional_common_addons(project) do + if host_on_github?(), do: github_addons_setup(project) + + project + end + + defp apply_optional_phoenix_addons(project) do + if install_addon_prompt?("Oban"), do: PhoenixAddons.Oban.apply(project) + if install_addon_prompt?("ExVCR"), do: PhoenixAddons.ExVCR.apply(project) + + project + end + + defp github_addons_setup(%Project{} = project) do + if generate_github_template?(), + do: Addons.Github.apply(project, %{github_template: true}) + + generate_github_action_test? = generate_github_action_test?() + + if generate_github_action_test?, + do: Addons.Github.apply(project, %{github_action_test: true}) + + generate_github_action_deploy_heroku? = generate_github_action_deploy_heroku?() + + if generate_github_action_deploy_heroku?, + do: Addons.Github.apply(project, %{github_action_deploy_heroku: true}) + + if generate_github_workflows_readme?(), + do: + Addons.Github.apply(project, %{ + github_workflows_readme: true, + with_test_workflow?: generate_github_action_test?, + with_deploy_to_heroku_workflow?: generate_github_action_deploy_heroku? + }) + + if generate_github_wiki?(), + do: Addons.Github.apply(project, %{github_wiki: true}) + end + + defp apply_phoenix_variant_setup(%Project{api_project?: true} = project), + do: ApiTemplate.apply(project) + + defp apply_phoenix_variant_setup(%Project{web_project?: true, live_project?: false} = project), + do: WebTemplate.apply(project) + + defp apply_phoenix_variant_setup(%Project{web_project?: true, live_project?: true} = project), + do: LiveTemplate.apply(project) +end diff --git a/lib/nimble_template/templates/variants/phoenix/web/template.ex b/lib/nimble_template/templates/variants/phoenix/web/template.ex new file mode 100644 index 00000000..f1c03260 --- /dev/null +++ b/lib/nimble_template/templates/variants/phoenix/web/template.ex @@ -0,0 +1,68 @@ +defmodule NimbleTemplate.Templates.Phoenix.Web.Template do + @moduledoc false + + import NimbleTemplate.AddonHelper + + alias NimbleTemplate.Addons.Phoenix.Web + alias NimbleTemplate.Projects.Project + + def apply(%Project{} = project) do + project + |> apply_default_web_addons() + |> apply_optional_web_addons() + end + + defp apply_default_web_addons(project) do + project + |> Web.NodePackage.apply() + |> Web.Assets.apply() + |> Web.CoreJS.apply() + |> Web.Prettier.apply() + |> Web.Sobelow.apply() + |> Web.Wallaby.apply() + |> Web.EsLint.apply() + |> Web.StyleLint.apply() + end + + defp apply_optional_web_addons(project) do + if install_addon_prompt?("SVG Sprite"), do: Web.SvgSprite.apply(project) + + if install_addon_prompt?("Dart Sass") do + Web.DartSass.apply(project) + + apply_dart_sass_requires_addons(project) + else + if install_addon_prompt?("Nimble JS"), do: Web.NimbleJS.apply(project) + end + + project + end + + # These addons depend on the DartSass + defp apply_dart_sass_requires_addons(project) do + with_nimble_css_addon = + if install_addon_prompt?("Nimble CSS") do + Web.NimbleCSS.apply(project) + + true + else + false + end + + with_nimble_js_addon = + if install_addon_prompt?("Nimble JS") do + Web.NimbleJS.apply(project) + + true + else + false + end + + if install_addon_prompt?("Bootstrap"), + do: + Web.Bootstrap.apply(project, %{ + with_nimble_css_addon: with_nimble_css_addon, + with_nimble_js_addon: with_nimble_js_addon + }) + end +end diff --git a/lib/nimble_template/variants/mix/template.ex b/lib/nimble_template/variants/mix/template.ex deleted file mode 100644 index 50067dd9..00000000 --- a/lib/nimble_template/variants/mix/template.ex +++ /dev/null @@ -1,37 +0,0 @@ -defmodule NimbleTemplate.Mix.Template do - @moduledoc false - - import NimbleTemplate.Template, - only: [ - host_on_github?: 0, - generate_github_template?: 0, - generate_github_action?: 0, - install_addon_prompt?: 1 - ] - - alias NimbleTemplate.{Addons, Project} - - def apply(%Project{} = project) do - project - |> Addons.ElixirVersion.apply() - |> Addons.Readme.apply() - |> Addons.TestEnv.apply() - |> Addons.Credo.apply() - |> Addons.Dialyxir.apply() - |> Addons.ExCoveralls.apply() - - if host_on_github?() do - if generate_github_template?(), - do: Addons.Github.apply(project, %{github_template: true}) - - if generate_github_action?(), - do: Addons.Github.apply(project, %{github_action: true}) - end - - if install_addon_prompt?("Mimic"), do: Addons.Mimic.apply(project) - - Addons.ExUnit.apply(project) - - project - end -end diff --git a/lib/nimble_template/variants/phoenix/api/template.ex b/lib/nimble_template/variants/phoenix/api/template.ex deleted file mode 100644 index 443bd77b..00000000 --- a/lib/nimble_template/variants/phoenix/api/template.ex +++ /dev/null @@ -1,9 +0,0 @@ -defmodule NimbleTemplate.Phoenix.Api.Template do - @moduledoc false - - alias NimbleTemplate.Project - - def apply(%Project{} = project) do - project - end -end diff --git a/lib/nimble_template/variants/phoenix/live/template.ex b/lib/nimble_template/variants/phoenix/live/template.ex deleted file mode 100644 index 4175bbbe..00000000 --- a/lib/nimble_template/variants/phoenix/live/template.ex +++ /dev/null @@ -1,12 +0,0 @@ -defmodule NimbleTemplate.Phoenix.Live.Template do - @moduledoc false - - alias NimbleTemplate.Phoenix.Web.Template, as: WebTemplate - alias NimbleTemplate.Project - - def apply(%Project{} = project) do - WebTemplate.apply(project) - - project - end -end diff --git a/lib/nimble_template/variants/phoenix/template.ex b/lib/nimble_template/variants/phoenix/template.ex deleted file mode 100644 index 36b2fecf..00000000 --- a/lib/nimble_template/variants/phoenix/template.ex +++ /dev/null @@ -1,62 +0,0 @@ -defmodule NimbleTemplate.Phoenix.Template do - @moduledoc false - - import NimbleTemplate.Template, - only: [ - host_on_github?: 0, - generate_github_template?: 0, - generate_github_action?: 0, - install_addon_prompt?: 1 - ] - - alias NimbleTemplate.Phoenix.Api.Template, as: ApiTemplate - alias NimbleTemplate.Phoenix.Live.Template, as: LiveTemplate - alias NimbleTemplate.Phoenix.Web.Template, as: WebTemplate - alias NimbleTemplate.{Addons, Project} - - def apply(%Project{} = project) do - project - |> common_setup() - |> variant_setup() - - Addons.ExUnit.apply(project) - - project - end - - defp common_setup(%Project{} = project) do - project - |> Addons.ElixirVersion.apply() - |> Addons.Readme.apply() - |> Addons.Makefile.apply() - |> Addons.Docker.apply() - |> Addons.MixRelease.apply() - |> Addons.TestEnv.apply() - |> Addons.Credo.apply() - |> Addons.Dialyxir.apply() - |> Addons.ExCoveralls.apply() - |> Addons.ExMachina.apply() - |> Addons.Mimic.apply() - - if host_on_github?() do - if generate_github_template?(), - do: Addons.Github.apply(project, %{github_template: true}) - - if generate_github_action?(), - do: Addons.Github.apply(project, %{github_action: true}) - end - - if install_addon_prompt?("Oban"), do: Addons.Oban.apply(project) - if install_addon_prompt?("ExVCR"), do: Addons.ExVCR.apply(project) - - project - end - - defp variant_setup(%Project{api_project?: true} = project), do: ApiTemplate.apply(project) - - defp variant_setup(%Project{web_project?: true, live_project?: false} = project), - do: WebTemplate.apply(project) - - defp variant_setup(%Project{web_project?: true, live_project?: true} = project), - do: LiveTemplate.apply(project) -end diff --git a/lib/nimble_template/variants/phoenix/web/template.ex b/lib/nimble_template/variants/phoenix/web/template.ex deleted file mode 100644 index 434d2319..00000000 --- a/lib/nimble_template/variants/phoenix/web/template.ex +++ /dev/null @@ -1,16 +0,0 @@ -defmodule NimbleTemplate.Phoenix.Web.Template do - @moduledoc false - - alias NimbleTemplate.Addons.Phoenix.Web - alias NimbleTemplate.Project - - def apply(%Project{} = project) do - project - |> Web.Assets.apply() - |> Web.CoreJS.apply() - |> Web.Sobelow.apply() - |> Web.Wallaby.apply() - - project - end -end diff --git a/mix.exs b/mix.exs index a89d3519..29016cfa 100644 --- a/mix.exs +++ b/mix.exs @@ -4,9 +4,9 @@ defmodule NimbleTemplate.MixProject do def project do [ app: :nimble_template, - version: "3.0.0", + version: "4.0.0", description: "Phoenix/Mix template for projects at [Nimble](https://nimblehq.co/).", - elixir: "~> 1.11.4", + elixir: "~> 1.13.3", elixirc_paths: elixirc_paths(Mix.env()), start_permanent: Mix.env() == :prod, deps: deps(), @@ -35,13 +35,14 @@ defmodule NimbleTemplate.MixProject do {:httpoison, "~> 1.7"}, {:jason, "~> 1.2"}, {:mimic, "~> 1.3", only: :test}, - {:phoenix, "~> 1.5.7"} + {:phoenix, "~> 1.6.6"} ] end defp aliases do [ - codebase: ["deps.unlock --check-unused", "format --check-formatted", "credo --strict"] + codebase: ["deps.unlock --check-unused", "format --check-formatted", "credo --strict"], + "codebase.fix": ["deps.clean --unlock --unused", "format"] ] end diff --git a/mix.lock b/mix.lock index c3d1ab37..0e7b5eac 100644 --- a/mix.lock +++ b/mix.lock @@ -1,28 +1,29 @@ %{ "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, - "certifi": {:hex, :certifi, "2.6.1", "dbab8e5e155a0763eea978c913ca280a6b544bfa115633fa20249c3d396d9493", [:rebar3], [], "hexpm", "524c97b4991b3849dd5c17a631223896272c6b0af446778ba4675a1dff53bb7e"}, - "credo": {:hex, :credo, "1.5.5", "e8f422026f553bc3bebb81c8e8bf1932f498ca03339856c7fec63d3faac8424b", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "dd8623ab7091956a855dc9f3062486add9c52d310dfd62748779c4315d8247de"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.12", "b245e875ec0a311a342320da0551da407d9d2b65d98f7a9597ae078615af3449", [:mix], [], "hexpm", "711e2cc4d64abb7d566d43f54b78f7dc129308a63bc103fbd88550d2174b3160"}, - "ex_doc": {:hex, :ex_doc, "0.24.2", "e4c26603830c1a2286dae45f4412a4d1980e1e89dc779fcd0181ed1d5a05c8d9", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "e134e1d9e821b8d9e4244687fb2ace58d479b67b282de5158333b0d57c6fb7da"}, + "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, + "credo": {:hex, :credo, "1.6.3", "0a9f8925dbc8f940031b789f4623fc9a0eea99d3eed600fe831e403eb96c6a83", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "1167cde00e6661d740fc54da2ee268e35d3982f027399b64d3e2e83af57a1180"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.19", "de0d033d5ff9fc396a24eadc2fcf2afa3d120841eb3f1004d138cbf9273210e8", [:mix], [], "hexpm", "527ab6630b5c75c3a3960b75844c314ec305c76d9899bb30f71cb85952a9dc45"}, + "ex_doc": {:hex, :ex_doc, "0.28.0", "7eaf526dd8c80ae8c04d52ac8801594426ae322b52a6156cd038f30bafa8226f", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "e55cdadf69a5d1f4cfd8477122ebac5e1fadd433a8c1022dafc5025e48db0131"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, - "hackney": {:hex, :hackney, "1.17.4", "99da4674592504d3fb0cfef0db84c3ba02b4508bae2dff8c0108baa0d6e0977c", [:rebar3], [{:certifi, "~>2.6.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "de16ff4996556c8548d512f4dbe22dd58a587bf3332e7fd362430a7ef3986b16"}, + "hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~>2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"}, "httpoison": {:hex, :httpoison, "1.8.0", "6b85dea15820b7804ef607ff78406ab449dd78bed923a49c7160e1886e987a3d", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "28089eaa98cf90c66265b6b5ad87c59a3729bea2e74e9d08f9b51eb9729b3c3a"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, - "jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"}, - "makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.15.1", "b5888c880d17d1cc3e598f05cdb5b5a91b7b17ac4eaf5f297cb697663a1094dd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "db68c173234b07ab2a07f645a5acdc117b9f99d69ebf521821d89690ae6c6ec8"}, + "jason": {:hex, :jason, "1.3.0", "fa6b82a934feb176263ad2df0dbd91bf633d4a46ebfdffea0c8ae82953714946", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "53fc1f51255390e0ec7e50f9cb41e751c260d065dcba2bf0d08dc51a4002c2ac"}, + "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.15.2", "dc72dfe17eb240552857465cc00cce390960d9a0c055c4ccd38b70629227e97c", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "fd23ae48d09b32eff49d4ced2b43c9f086d402ee4fd4fcb2d7fad97fa8823e75"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, - "mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"}, + "mime": {:hex, :mime, "2.0.2", "0b9e1a4c840eafb68d820b0e2158ef5c49385d17fb36855ac6e7e087d4b1dcc5", [:mix], [], "hexpm", "e6a3f76b4c277739e36c2e21a2c640778ba4c3846189d5ab19f97f126df5f9b7"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, - "mimic": {:hex, :mimic, "1.5.0", "ad33740f414412639006afa4092f7bf09f5ddeb51999f94181ff8ad9e5d5d675", [:mix], [], "hexpm", "0f4620b88623c3869a11bd7e5691cccc712290c4bcbf73bf07bfb37c3c6932a4"}, - "nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"}, + "mimic": {:hex, :mimic, "1.5.1", "085f7ebfeb5b579a13a167aec3c712d71fecfc6cb8b298c0dd3056f97ea2c2a0", [:mix], [], "hexpm", "33a50ef9ff38f8f24b2586d52e529981a3ba2b8d061c872084aff0e993bf4bd5"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.2.2", "b99ca56bbce410e9d5ee4f9155a212e942e224e259c7ebbf8f2c86ac21d4fa3c", [:mix], [], "hexpm", "98d51bd64d5f6a2a9c6bb7586ee8129e27dfaab1140b5a4753f24dac0ba27d2f"}, "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, - "phoenix": {:hex, :phoenix, "1.5.8", "71cfa7a9bb9a37af4df98939790642f210e35f696b935ca6d9d9c55a884621a4", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "35ded0a32f4836168c7ab6c33b88822eccd201bcd9492125a9bea4c54332d955"}, + "phoenix": {:hex, :phoenix, "1.6.6", "281c8ce8dccc9f60607346b72cdfc597c3dde134dd9df28dff08282f0b751754", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "807bd646e64cd9dc83db016199715faba72758e6db1de0707eef0a2da4924364"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"}, - "plug": {:hex, :plug, "1.11.1", "f2992bac66fdae679453c9e86134a4201f6f43a687d8ff1cd1b2862d53c80259", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "23524e4fefbb587c11f0833b3910bfb414bf2e2534d61928e920f54e3a1b881f"}, + "phoenix_view": {:hex, :phoenix_view, "1.1.2", "1b82764a065fb41051637872c7bd07ed2fdb6f5c3bd89684d4dca6e10115c95a", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "7ae90ad27b09091266f6adbb61e1d2516a7c3d7062c6789d46a7554ec40f3a56"}, + "plug": {:hex, :plug, "1.13.3", "93b299039c21a8b82cc904d13812bce4ced45cf69153e8d35ca16ffb3e8c5d98", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "98c8003e4faf7b74a9ac41bee99e328b08f069bf932747d4a7532e97ae837a17"}, "plug_crypto": {:hex, :plug_crypto, "1.2.2", "05654514ac717ff3a1843204b424477d9e60c143406aa94daf2274fdd280794d", [:mix], [], "hexpm", "87631c7ad914a5a445f0a3809f99b079113ae4ed4b867348dd9eec288cecb6db"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, - "telemetry": {:hex, :telemetry, "0.4.3", "a06428a514bdbc63293cd9a6263aad00ddeb66f608163bdec7c8995784080818", [:rebar3], [], "hexpm", "eb72b8365ffda5bed68a620d1da88525e326cb82a75ee61354fc24b844768041"}, + "telemetry": {:hex, :telemetry, "1.0.0", "0f453a102cdf13d506b7c0ab158324c337c41f1cc7548f0bc0e130bbf0ae9452", [:rebar3], [], "hexpm", "73bc09fa59b4a0284efb4624335583c528e07ec9ae76aca96ea0673850aec57a"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, } diff --git a/priv/templates/nimble_template/.credo.exs b/priv/templates/nimble_template/.credo.exs index b1df08b1..5dd6dc1c 100644 --- a/priv/templates/nimble_template/.credo.exs +++ b/priv/templates/nimble_template/.credo.exs @@ -87,7 +87,7 @@ # If you don't want TODO comments to cause `mix credo` to fail, just # set this value to 0 (zero). # - {Credo.Check.Design.TagTODO, [exit_status: 2]}, + {Credo.Check.Design.TagTODO, [exit_status: 0]}, {Credo.Check.Design.TagFIXME, []}, # @@ -163,7 +163,7 @@ ]}, {Credo.Check.Consistency.MultiAliasImportRequireUse, []}, {Credo.Check.Consistency.UnusedVariableNames, false}, - {Credo.Check.Design.DuplicatedCode, []}, + {Credo.Check.Design.DuplicatedCode, files: %{excluded: ["**/*_test.exs"]}}, {Credo.Check.Readability.AliasAs, false}, {Credo.Check.Readability.MultiAlias, false}, {Credo.Check.Readability.Specs, false}, diff --git a/priv/templates/nimble_template/.dockerignore b/priv/templates/nimble_template/.dockerignore index b042b8b5..865baac3 100644 --- a/priv/templates/nimble_template/.dockerignore +++ b/priv/templates/nimble_template/.dockerignore @@ -12,4 +12,3 @@ README.md cover **/tmp/ doc -/priv/static/ diff --git a/priv/templates/nimble_template/.github/PULL_REQUEST_TEMPLATE.md b/priv/templates/nimble_template/.github/PULL_REQUEST_TEMPLATE.md index 2f2e4cbc..84d066cb 100644 --- a/priv/templates/nimble_template/.github/PULL_REQUEST_TEMPLATE.md +++ b/priv/templates/nimble_template/.github/PULL_REQUEST_TEMPLATE.md @@ -1,4 +1,4 @@ -Issue +Add the story URL here. Prefer the short link format, e.g. https://app.clubhouse.io/acme/story/1234/ ## What happened diff --git a/priv/templates/nimble_template/.github/PULL_REQUEST_TEMPLATE/RELEASE_TEMPLATE.md b/priv/templates/nimble_template/.github/PULL_REQUEST_TEMPLATE/RELEASE_TEMPLATE.md new file mode 100644 index 00000000..819fd591 --- /dev/null +++ b/priv/templates/nimble_template/.github/PULL_REQUEST_TEMPLATE/RELEASE_TEMPLATE.md @@ -0,0 +1,17 @@ +Link to the milestone on Github e.g. https://github.com/nimblehq/git-templates/milestone/41?closed=1 +or +Link to the project management tool for the release + +## Features + +Provide the ID and title of the issue in the section for each type (feature, chore and bug). The link is optional. + +- [ch1234] As a user, I can log in + or +- [[ch1234](https://github.com/nimblehq/git-templates/issues/1234)] As a user, I can log in + +## Chores +- Same structure as in ## Feature + +## Bugs +- Same structure as in ## Feature diff --git a/priv/templates/nimble_template/.github/wiki/Application-Status.md b/priv/templates/nimble_template/.github/wiki/Application-Status.md new file mode 100644 index 00000000..96001b23 --- /dev/null +++ b/priv/templates/nimble_template/.github/wiki/Application-Status.md @@ -0,0 +1,11 @@ +1. Liveness + - URL: `/_health/liveness` + - Response: + - HTTP status: 200 + - Body: Alive + +2. Readiness + - URL: `/_health/readiness` + - Response: + - HTTP status: 200 + - Body: Ready diff --git a/priv/templates/nimble_template/.github/wiki/Environment-Variables.md.eex b/priv/templates/nimble_template/.github/wiki/Environment-Variables.md.eex new file mode 100644 index 00000000..86d149b2 --- /dev/null +++ b/priv/templates/nimble_template/.github/wiki/Environment-Variables.md.eex @@ -0,0 +1,12 @@ +## List of Environment Variables + +### Database + +- `DB_URL`: URL to database +- `DB_POOL_SIZE`: Number of connections to the database + +### Endpoint configuration + +- `HEALTH_PATH`: Health path (eg: "/_health") +- `PHX_HOST`: The application endpoint. +- `SECRET_KEY_BASE`: Secret key base diff --git a/priv/templates/nimble_template/.github/wiki/Getting-Started.md.eex b/priv/templates/nimble_template/.github/wiki/Getting-Started.md.eex new file mode 100644 index 00000000..3e7c84a9 --- /dev/null +++ b/priv/templates/nimble_template/.github/wiki/Getting-Started.md.eex @@ -0,0 +1,77 @@ +## Erlang & Elixir + +- Erlang <%= erlang_version %> + +- Elixir <%= elixir_version %> + +- Recommended version manager. + + - [asdf](https://github.com/asdf-vm/asdf) + - [asdf-erlang](https://github.com/asdf-vm/asdf-erlang) + - [asdf-elixir](https://github.com/asdf-vm/asdf-elixir) + +## Development + +- Install [Docker for Mac](https://docs.docker.com/docker-for-mac/install/) + +- Setup and boot the Docker containers: + + ```sh + make docker_setup + ``` + +- Install Elixir dependencies: + + ```sh + mix do deps.get, deps.compile + ``` +<%= if web_project? do %> +- Install Node dependencies: + + ```sh + npm install --prefix assets + ``` +<% end %> +- Setup the databases: + + ```sh + mix ecto.setup + ``` + +- Start the Phoenix app + + ```sh + iex -S mix phx.server + ``` + +- Run all tests: + + ```sh + mix test + ``` + +- Run all lint: + + ```sh + mix codebase + ``` + +- Fix all lint: + + ```sh + mix codebase.fix + ``` + +- Test coverage: + + ```sh + mix coverage + ``` + +## Production + +- Build Docker image + + ```sh + docker-compose build + ``` diff --git a/priv/templates/nimble_template/.github/wiki/Getting-Started.md.mix.eex b/priv/templates/nimble_template/.github/wiki/Getting-Started.md.mix.eex new file mode 100644 index 00000000..c5700229 --- /dev/null +++ b/priv/templates/nimble_template/.github/wiki/Getting-Started.md.mix.eex @@ -0,0 +1,43 @@ +### Erlang & Elixir + +- Erlang <%= erlang_version %> + +- Elixir <%= elixir_version %> + +- Recommended version manager. + + - [asdf](https://github.com/asdf-vm/asdf) + - [asdf-erlang](https://github.com/asdf-vm/asdf-erlang) + - [asdf-elixir](https://github.com/asdf-vm/asdf-elixir) + +### Development + +- Install Elixir dependencies: + + ```sh + mix deps.get + ``` + +- Run all tests: + + ```sh + mix test + ``` + +- Run all lint: + + ```sh + mix codebase + ``` + +- Fix all lint: + + ```sh + mix codebase.fix + ``` + +- Test coverage: + + ```sh + mix coverage + ``` diff --git a/priv/templates/nimble_template/.github/wiki/Home.md b/priv/templates/nimble_template/.github/wiki/Home.md new file mode 100644 index 00000000..f5d995ea --- /dev/null +++ b/priv/templates/nimble_template/.github/wiki/Home.md @@ -0,0 +1 @@ +> *Insert information about your project here!* diff --git a/priv/templates/nimble_template/.github/wiki/Icon-Sprite.md b/priv/templates/nimble_template/.github/wiki/Icon-Sprite.md new file mode 100644 index 00000000..72c2e10a --- /dev/null +++ b/priv/templates/nimble_template/.github/wiki/Icon-Sprite.md @@ -0,0 +1,18 @@ +## Generate/update the `icon-sprite.svg` file + +1. Export SVG icon from Figma. + +2. Add icon file to `assets/static/images/icons/` (without the `icon-` prefix). + +3. Generate/Update the `icon-sprite.svg` by running this command: + + ```sh + npm run svg-sprite.generate-icon --prefix assets + ``` + +## Using Icon Sprite in Template + +```sh +<%= icon_tag(@conn, "icon-active", class: "something") %> +# +``` diff --git a/priv/templates/nimble_template/.github/wiki/_Sidebar.md b/priv/templates/nimble_template/.github/wiki/_Sidebar.md new file mode 100644 index 00000000..7cdcf6f9 --- /dev/null +++ b/priv/templates/nimble_template/.github/wiki/_Sidebar.md @@ -0,0 +1,9 @@ +## Table of Contents + +- [[Home]] +- [[Getting Started]] + +## Infrastructure + +- [[Application Status]] +- [[Environment Variables]] diff --git a/priv/templates/nimble_template/.github/wiki/_Sidebar.md.mix b/priv/templates/nimble_template/.github/wiki/_Sidebar.md.mix new file mode 100644 index 00000000..7499a11c --- /dev/null +++ b/priv/templates/nimble_template/.github/wiki/_Sidebar.md.mix @@ -0,0 +1,4 @@ +## Table of Contents + +- [[Home]] +- [[Getting Started]] diff --git a/priv/templates/nimble_template/.github/workflows/README.md.eex b/priv/templates/nimble_template/.github/workflows/README.md.eex new file mode 100644 index 00000000..c259f396 --- /dev/null +++ b/priv/templates/nimble_template/.github/workflows/README.md.eex @@ -0,0 +1,38 @@ +# Github Actions + +The following workflows are supported. +<%= if with_test_workflow? do %> +- Test +<% end %> +<%= if with_deploy_to_heroku_workflow? do %> +- [Deploy to Heroku](#deploy-to-heroku-workflow-usage-instruction) +<% end %> +<%= if with_deploy_to_heroku_workflow? do %> +## Deploy to Heroku Workflow usage instruction + +### Requirements + +- A pre-generated [Heroku App](https://devcenter.heroku.com/articles/creating-apps) +- A Heroku API key. It can be generated under your [Account Settings](https://dashboard.heroku.com/account#api-key) +- Three Heroku config vars: + - **DATABASE_URL**: It will be created automatically when the [PostgreSQL add-on](https://elements.heroku.com/addons/heroku-postgresql) is added. + - **PHX_HOST**: if your app name is `acme`, the value of this var is: `acme.herokuapp.com` + - **HEALTH_PATH**: Health path (eg: "/_health") + - **SECRET_KEY_BASE**: use the `mix phx.gen.secret` to get a new secret and set it as the value of this var. + +### How to use + +- Defining two [Github secrets](https://docs.github.com/en/actions/reference/encrypted-secrets) to hold the value of Heroku API key and Heroku app name: + - HEROKU_API_KEY + - HEROKU_APP_NAME +- If you plan on using WebSockets, the timeout for the WebSocket transport needs to be decreased in `lib/hello_web/endpoint.ex`. + + ```elixir + socket "/socket", HelloWeb.UserSocket, + websocket: [timeout: 45_000], + longpoll: false + ... + ``` + + otherwise, leaving it set to `false` as default. +<% end %> diff --git a/priv/templates/nimble_template/.github/workflows/deploy_heroku.yml b/priv/templates/nimble_template/.github/workflows/deploy_heroku.yml new file mode 100644 index 00000000..4b9915b2 --- /dev/null +++ b/priv/templates/nimble_template/.github/workflows/deploy_heroku.yml @@ -0,0 +1,39 @@ +name: Deploy to Heroku + +on: + workflow_run: + workflows: + - Test + branches: + - develop + types: + - completed + workflow_dispatch: + +env: + # To API Key is used by Heroku CLI to authenticate + HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }} + HEROKU_APP_NAME: ${{ secrets.HEROKU_APP_NAME }} + +jobs: + deploy: + name: Deploy to Heroku + runs-on: ubuntu-latest + + steps: + - name: Cancel previous runs + uses: styfle/cancel-workflow-action@0.9.0 + with: + access_token: ${{ github.token }} + + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Login to Heroku Container registry + run: heroku container:login + + - name: Build and push + run: heroku container:push -a ${{ env.HEROKU_APP_NAME }} web + + - name: Release + run: heroku container:release -a ${{ env.HEROKU_APP_NAME }} web diff --git a/priv/templates/nimble_template/.github/workflows/publish_wiki.yml b/priv/templates/nimble_template/.github/workflows/publish_wiki.yml new file mode 100644 index 00000000..59f4b29c --- /dev/null +++ b/priv/templates/nimble_template/.github/workflows/publish_wiki.yml @@ -0,0 +1,18 @@ +name: Publish Wiki + +on: + push: + paths: + - .github/wiki/** + branches: + - develop + +jobs: + publish: + name: Publish Wiki + uses: nimblehq/github-actions-workflows/.github/workflows/publish_wiki.yml@0.1.0 + with: + USER_NAME: github-wiki-workflow + USER_EMAIL: ${{ secrets.GH_EMAIL }} + secrets: + USER_TOKEN: ${{ secrets.GH_TOKEN }} diff --git a/priv/templates/nimble_template/.github/workflows/test.yml.eex b/priv/templates/nimble_template/.github/workflows/test.yml.eex index a67a05e0..6723132c 100644 --- a/priv/templates/nimble_template/.github/workflows/test.yml.eex +++ b/priv/templates/nimble_template/.github/workflows/test.yml.eex @@ -1,4 +1,4 @@ -name: "Tests" +name: Test on: pull_request @@ -10,7 +10,68 @@ env: DB_HOST: localhost jobs: - test: + install_and_compile_dependencies: + name: Install and Compile Dependencies + + runs-on: ubuntu-latest + + steps: + - name: Cancel Previous Runs + uses: styfle/cancel-workflow-action@0.8.0 + with: + access_token: ${{ github.token }} + + - name: Checkout repository + uses: actions/checkout@v2 + with: + ref: ${{ github.head_ref }} + + - name: Set up Elixir + uses: erlef/setup-elixir@v1 + with: + otp-version: ${{ env.OTP_VERSION }} + elixir-version: ${{ env.ELIXIR_VERSION }} + <%= if web_project? do %> + - name: Set up Node + uses: actions/setup-node@v2.1.5 + with: + node-version: ${{ env.NODE_VERSION }} + <% end %> + - name: Cache Elixir build + uses: actions/cache@v2 + with: + path: | + _build + deps + key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} + restore-keys: | + ${{ runner.os }}-mix- + <%= if web_project? do %> + - name: Cache Node modules + uses: actions/cache@v2 + with: + path: assets/node_modules + key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node- + <% end %> + - name: Install Elixir dependencies + run: mix deps.get + + - name: Compile dependencies + run: mix compile + <%= if web_project? do %> + - name: Install Node dependencies + run: npm --prefix assets install + + - name: Compile assets + run: mix assets.deploy + <% end %> + lint_codebase: + name: Linting + + needs: install_and_compile_dependencies + runs-on: ubuntu-latest services: @@ -26,21 +87,19 @@ jobs: --health-retries 5 steps: - - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.8.0 - with: - access_token: ${{ github.token }} - - - uses: actions/checkout@v2 + - name: Checkout repository + uses: actions/checkout@v2 with: ref: ${{ github.head_ref }} - - uses: erlef/setup-elixir@v1 + - name: Set up Elixir + uses: erlef/setup-elixir@v1 with: otp-version: ${{ env.OTP_VERSION }} elixir-version: ${{ env.ELIXIR_VERSION }} <%= if web_project? do %> - - uses: actions/setup-node@v2.1.5 + - name: Set up Node + uses: actions/setup-node@v2.1.5 with: node-version: ${{ env.NODE_VERSION }} <% end %> @@ -54,7 +113,7 @@ jobs: restore-keys: | ${{ runner.os }}-mix- <%= if web_project? do %> - - name: Cache Node npm + - name: Cache Node modules uses: actions/cache@v2 with: path: assets/node_modules @@ -62,18 +121,17 @@ jobs: restore-keys: | ${{ runner.os }}-node- <% end %> - - name: Install Dependencies + - name: Install Elixir dependencies run: mix deps.get - + - name: Compile dependencies run: mix compile --warnings-as-errors --all-warnings - <%= if web_project? do %> - - name: Install node module + - name: Install Node dependencies run: npm --prefix assets install - name: Compile assets - run: npm run --prefix assets build:dev + run: mix assets.deploy <% end %> - name: Create database run: mix ecto.create @@ -81,15 +139,267 @@ jobs: - name: Migrate database run: mix ecto.migrate + - name: Ensure that localization files (POs, POTs) are up-to-date. + run: + mix gettext.extract-and-merge; + + if [ -n "$(git diff --exit-code priv/gettext/)" ]; then + echo "The localization files (POs, POTs) are NOT up-to-date, run \"mix gettext.extract-and-merge\" on your local and push again."; + exit 1; + fi + - name: Run codebase check run: mix codebase + + test_database_seeds: + name: Test database seeds + + needs: lint_codebase + + runs-on: ubuntu-latest - - name: Run Tests - run: mix coverage + services: + db: + image: postgres:12.3 + ports: ['5432:5432'] + env: + POSTGRES_PASSWORD: postgres + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + env: + MIX_ENV: dev + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + ref: ${{ github.head_ref }} + + - name: Set up Elixir + uses: erlef/setup-elixir@v1 + with: + otp-version: ${{ env.OTP_VERSION }} + elixir-version: ${{ env.ELIXIR_VERSION }} <%= if web_project? do %> - - uses: actions/upload-artifact@v2 + - name: Set up Node + uses: actions/setup-node@v2.1.5 + with: + node-version: ${{ env.NODE_VERSION }} + <% end %> + - name: Cache Elixir build + uses: actions/cache@v2 + with: + path: | + _build + deps + key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} + restore-keys: | + ${{ runner.os }}-mix- + <%= if web_project? do %> + - name: Cache Node modules + uses: actions/cache@v2 + with: + path: assets/node_modules + key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node- + <% end %> + - name: Install Elixir dependencies + run: mix deps.get + + - name: Compile dependencies + run: mix compile + <%= if web_project? do %> + - name: Install Node dependencies + run: npm --prefix assets install + + - name: Compile assets + run: mix assets.deploy + <% end %> + - name: Create database + run: mix ecto.create + + - name: Migrate database + run: mix ecto.migrate + + - name: Seed database + run: mix run priv/repo/seeds.exs + + unit_test: + name: Unit test + + needs: lint_codebase + + runs-on: ubuntu-latest + + services: + db: + image: postgres:12.3 + ports: ['5432:5432'] + env: + POSTGRES_PASSWORD: postgres + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + ref: ${{ github.head_ref }} + + - name: Set up Elixir + uses: erlef/setup-elixir@v1 + with: + otp-version: ${{ env.OTP_VERSION }} + elixir-version: ${{ env.ELIXIR_VERSION }} + <%= if web_project? do %> + - name: Set up Node + uses: actions/setup-node@v2.1.5 + with: + node-version: ${{ env.NODE_VERSION }} + <% end %> + - name: Cache Elixir build + uses: actions/cache@v2 + with: + path: | + _build + deps + key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} + restore-keys: | + ${{ runner.os }}-mix- + <%= if web_project? do %> + - name: Cache Node modules + uses: actions/cache@v2 + with: + path: assets/node_modules + key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node- + <% end %> + - name: Install Elixir dependencies + run: mix deps.get + + - name: Compile dependencies + run: mix compile + <%= if web_project? do %> + - name: Install Node dependencies + run: npm --prefix assets install + + - name: Compile assets + run: mix assets.deploy + <% end %> + - name: Create database + run: mix ecto.create + + - name: Migrate database + run: mix ecto.migrate + + - name: Run Tests + run: mix coverage --exclude feature_test + + - name: Upload Code Coverage Artifact + uses: actions/upload-artifact@v2 + if: ${{ failure() }} + with: + name: code_coverage + path: cover/ +<%= if web_project? do %> + feature_test: + name: Feature test + + needs: lint_codebase + + runs-on: ubuntu-latest + + services: + db: + image: postgres:12.3 + ports: ['5432:5432'] + env: + POSTGRES_PASSWORD: postgres + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + # To have parallel feature test jobs, adjust the `mix_test_partition` here and the `--partitions option` in the `Run test` step. + # Example: + # - mix_test_partition: [1, 2, 3, 4] + # - mix test --only feature_test --partitions 4 + strategy: + matrix: + mix_test_partition: [1] + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + ref: ${{ github.head_ref }} + + - name: Set up Elixir + uses: erlef/setup-elixir@v1 + with: + otp-version: ${{ env.OTP_VERSION }} + elixir-version: ${{ env.ELIXIR_VERSION }} + + - name: Set up Node + uses: actions/setup-node@v2.1.5 + with: + node-version: ${{ env.NODE_VERSION }} + + - name: Cache Elixir build + uses: actions/cache@v2 + with: + path: | + _build + deps + key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} + restore-keys: | + ${{ runner.os }}-mix- + + - name: Cache Node modules + uses: actions/cache@v2 + with: + path: assets/node_modules + key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node- + + - name: Install Elixir dependencies + run: mix deps.get + + - name: Compile dependencies + run: mix compile + + - name: Install Node dependencies + run: npm --prefix assets install + + - name: Compile assets + run: mix assets.deploy + + - name: Create database + run: mix ecto.create + + - name: Migrate database + run: mix ecto.migrate + + - name: Run Tests + run: mix test --only feature_test --partitions 1 + env: + MIX_TEST_PARTITION: ${{ matrix.mix_test_partition }} + + - name: Upload Wallaby Screenshots Artifact + uses: actions/upload-artifact@v2 if: ${{ failure() }} with: name: wallaby_screenshots path: tmp/wallaby_screenshots/ - <% end %> +<% end %> diff --git a/priv/templates/nimble_template/.github/workflows/test.yml.mix.eex b/priv/templates/nimble_template/.github/workflows/test.yml.mix.eex index 7b466bf1..f58582f3 100644 --- a/priv/templates/nimble_template/.github/workflows/test.yml.mix.eex +++ b/priv/templates/nimble_template/.github/workflows/test.yml.mix.eex @@ -1,4 +1,4 @@ -name: "Tests" +name: Test on: pull_request @@ -8,7 +8,9 @@ env: MIX_ENV: test jobs: - test: + install_and_compile_dependencies: + name: Install and Compile Dependencies + runs-on: ubuntu-latest steps: @@ -17,11 +19,13 @@ jobs: with: access_token: ${{ github.token }} - - uses: actions/checkout@v2 + - name: Checkout repository + uses: actions/checkout@v2 with: ref: ${{ github.head_ref }} - - uses: erlef/setup-elixir@v1 + - name: Set up Elixir + uses: erlef/setup-elixir@v1 with: otp-version: ${{ env.OTP_VERSION }} elixir-version: ${{ env.ELIXIR_VERSION }} @@ -36,14 +40,91 @@ jobs: restore-keys: | ${{ runner.os }}-mix- - - name: Install Dependencies + - name: Install dependencies run: mix deps.get - + + - name: Compile dependencies + run: mix compile + + lint_codebase: + name: Linting + + needs: install_and_compile_dependencies + + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + ref: ${{ github.head_ref }} + + - name: Set up Elixir + uses: erlef/setup-elixir@v1 + with: + otp-version: ${{ env.OTP_VERSION }} + elixir-version: ${{ env.ELIXIR_VERSION }} + + - name: Cache Elixir build + uses: actions/cache@v2 + with: + path: | + _build + deps + key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} + restore-keys: | + ${{ runner.os }}-mix- + + - name: Install dependencies + run: mix deps.get + - name: Compile dependencies run: mix compile --warnings-as-errors --all-warnings - + - name: Run codebase check run: mix codebase + + test: + name: Unit Test + + needs: lint_codebase + + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + ref: ${{ github.head_ref }} + + - name: Set up Elixir + uses: erlef/setup-elixir@v1 + with: + otp-version: ${{ env.OTP_VERSION }} + elixir-version: ${{ env.ELIXIR_VERSION }} + + - name: Cache Elixir build + uses: actions/cache@v2 + with: + path: | + _build + deps + key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} + restore-keys: | + ${{ runner.os }}-mix- + + - name: Install dependencies + run: mix deps.get + + - name: Compile dependencies + run: mix compile - name: Run Tests run: mix coverage + + - name: Upload Code Coverage Artifact + uses: actions/upload-artifact@v2 + if: ${{ failure() }} + with: + name: code_coverage + path: cover/ diff --git a/priv/templates/nimble_template/.prettierignore b/priv/templates/nimble_template/.prettierignore new file mode 100644 index 00000000..63c8d9aa --- /dev/null +++ b/priv/templates/nimble_template/.prettierignore @@ -0,0 +1,8 @@ +deps/ +_build/ +.elixir_ls +assets +priv +cover/ +.github/ +README.md diff --git a/priv/templates/nimble_template/.prettierrc.yaml b/priv/templates/nimble_template/.prettierrc.yaml new file mode 100644 index 00000000..3b330243 --- /dev/null +++ b/priv/templates/nimble_template/.prettierrc.yaml @@ -0,0 +1,3 @@ +printWidth: 120 +trailingComma: "none" +semi: false diff --git a/priv/templates/nimble_template/Dockerfile.eex b/priv/templates/nimble_template/Dockerfile.eex index 1afbd53e..46583638 100644 --- a/priv/templates/nimble_template/Dockerfile.eex +++ b/priv/templates/nimble_template/Dockerfile.eex @@ -13,7 +13,11 @@ RUN apk update && \ build-base && \ mix local.rebar --force && \ mix local.hex --force - +<%= if web_project? do %> +RUN wget -q -O /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub +RUN wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.34-r0/glibc-2.34-r0.apk +RUN apk add glibc-2.34-r0.apk +<% end %> WORKDIR /app COPY . . @@ -24,11 +28,9 @@ RUN mix do deps.get, deps.compile, compile <%= if web_project? do %> RUN cd assets && \ npm ci --progress=false --no-audit --loglevel=error && \ - npm run deploy && \ cd - && \ - mix phx.digest + mix assets.deploy <% end %> - RUN mix release # @@ -38,13 +40,15 @@ FROM alpine:${RELEASE_IMAGE_VERSION} AS app RUN apk update && \ apk add --no-cache \ + libstdc++ \ + libgcc \ bash \ openssl-dev WORKDIR /opt/app EXPOSE 4000 -RUN addgroup -g 1000 appuser && \ +RUN addgroup -g 1000 appuser && \ adduser -u 1000 -G appuser -g appuser -s /bin/sh -D appuser && \ chown 1000:1000 /opt/app diff --git a/priv/templates/nimble_template/README.md.eex b/priv/templates/nimble_template/README.md.eex index c56769e6..e1e5de67 100644 --- a/priv/templates/nimble_template/README.md.eex +++ b/priv/templates/nimble_template/README.md.eex @@ -8,11 +8,11 @@ ### Erlang & Elixir -* Erlang <%= erlang_version %> +- Erlang <%= erlang_version %> -* Elixir <%= elixir_version %> +- Elixir <%= elixir_version %> -* Recommended version manager. +- Recommended version manager. - [asdf](https://github.com/asdf-vm/asdf) - [asdf-erlang](https://github.com/asdf-vm/asdf-erlang) @@ -20,51 +20,57 @@ ### Development -* Install [Docker for Mac](https://docs.docker.com/docker-for-mac/install/) +- Install [Docker for Mac](https://docs.docker.com/docker-for-mac/install/) -* Setup and boot the Docker containers: +- Setup and boot the Docker containers: ```sh make docker_setup ``` -* Install Elixir dependencies: +- Install Elixir dependencies: ```sh mix deps.get ``` <%= if web_project? do %> -* Install Node dependencies: +- Install Node dependencies: ```sh npm install --prefix assets ``` <% end %> -* Setup the databases: +- Setup the databases: ```sh mix ecto.setup ``` -* Start the Phoenix app +- Start the Phoenix app ```sh iex -S mix phx.server ``` -* Run all tests: +- Run all tests: ```sh mix test ``` -* Run all lint: +- Run all lint: ```sh mix codebase ``` -* Test coverage: +- Fix all lint: + + ```sh + mix codebase.fix + ``` + +- Test coverage: ```sh mix coverage @@ -72,8 +78,12 @@ ### Production -* Buidl Docker image +- Build Docker image ```sh docker-compose build ``` + +### CI/CD +The project relies entirely on [Github Actions](https://github.com/features/actions) for CI/CD via multiple workflows located under the [`.github/workflows/`](.github/workflows) directory. +Please check out the [`.github/workflows/README.md`](.github/workflows/README.md) file for further instructions. diff --git a/priv/templates/nimble_template/README.md.mix.eex b/priv/templates/nimble_template/README.md.mix.eex index 1cb7f107..4c55854b 100644 --- a/priv/templates/nimble_template/README.md.mix.eex +++ b/priv/templates/nimble_template/README.md.mix.eex @@ -8,11 +8,11 @@ ### Erlang & Elixir -* Erlang <%= erlang_version %> +- Erlang <%= erlang_version %> -* Elixir <%= elixir_version %> +- Elixir <%= elixir_version %> -* Recommended version manager. +- Recommended version manager. - [asdf](https://github.com/asdf-vm/asdf) - [asdf-erlang](https://github.com/asdf-vm/asdf-erlang) @@ -20,26 +20,36 @@ ### Development -* Install Elixir dependencies: +- Install Elixir dependencies: ```sh mix deps.get ``` -* Run all tests: +- Run all tests: ```sh mix test ``` -* Run all lint: +- Run all lint: ```sh mix codebase ``` -* Test coverage: +- Fix all lint: + + ```sh + mix codebase.fix + ``` + +- Test coverage: ```sh mix coverage ``` + +### CI/CD +The project relies entirely on [Github Actions](https://github.com/features/actions) for CI/CD via multiple workflows located under the [`.github/workflows/`](.github/workflows) directory. +Please check out the [`.github/workflows/README.md`](.github/workflows/README.md) file for further instructions. diff --git a/priv/templates/nimble_template/assets/.eslintrc.json b/priv/templates/nimble_template/assets/.eslintrc.json new file mode 100644 index 00000000..76a367a4 --- /dev/null +++ b/priv/templates/nimble_template/assets/.eslintrc.json @@ -0,0 +1,29 @@ +{ + "env": { + "browser": true, + "node": true, + "es2021": true + }, + "extends": [ + "eslint:recommended", + "plugin:prettier/recommended" + ], + "parserOptions": { + "ecmaVersion": 12, + "sourceType": "module" + }, + "plugins": [ + "prettier" + ], + "rules": { + "prettier/prettier": "error", + "no-unused-vars": [ + "error", { + "argsIgnorePattern": "^_" + } + ] + }, + "ignorePatterns": [ + "/vendor/topbar.js" + ] +} diff --git a/priv/templates/nimble_template/assets/.stylelintrc.json b/priv/templates/nimble_template/assets/.stylelintrc.json new file mode 100644 index 00000000..e4257297 --- /dev/null +++ b/priv/templates/nimble_template/assets/.stylelintrc.json @@ -0,0 +1,17 @@ +{ + "extends": [ + "stylelint-config-sass-guidelines", + "stylelint-config-property-sort-order-smacss" + ], + "ignoreFiles": [ + "css/app.css", + "css/phoenix.css" + ], + "rules": { + "selector-class-pattern": null, + "no-eol-whitespace": true, + "max-nesting-depth": 3, + "scss/at-extend-no-missing-placeholder": null, + "order/properties-alphabetical-order": null + } +} diff --git a/priv/templates/nimble_template/assets/bootstrap_css/vendor/_bootstrap.scss b/priv/templates/nimble_template/assets/bootstrap_css/vendor/_bootstrap.scss new file mode 100644 index 00000000..885a7a26 --- /dev/null +++ b/priv/templates/nimble_template/assets/bootstrap_css/vendor/_bootstrap.scss @@ -0,0 +1,46 @@ +// Configuration +@import 'bootstrap/scss/functions'; +@import 'bootstrap/scss/variables'; +@import 'bootstrap/scss/mixins'; +@import 'bootstrap/scss/utilities'; + +// Re-enable these if needed. +// Do not forget to check the order of import components before re-enable. + +// Layout & components +@import 'bootstrap/scss/root'; +@import 'bootstrap/scss/reboot'; +@import 'bootstrap/scss/type'; +// @import 'bootstrap/scss/images'; +@import 'bootstrap/scss/containers'; +@import 'bootstrap/scss/grid'; +// @import 'bootstrap/scss/tables'; +// @import 'bootstrap/scss/forms'; +// @import 'bootstrap/scss/buttons'; +// @import 'bootstrap/scss/transitions'; +// @import 'bootstrap/scss/dropdown'; +// @import 'bootstrap/scss/button-group'; +// @import 'bootstrap/scss/nav'; +// @import 'bootstrap/scss/navbar'; +// @import 'bootstrap/scss/card'; +// @import 'bootstrap/scss/accordion'; +// @import 'bootstrap/scss/breadcrumb'; +// @import 'bootstrap/scss/pagination'; +// @import 'bootstrap/scss/badge'; +// @import 'bootstrap/scss/alert'; +// @import 'bootstrap/scss/progress'; +// @import 'bootstrap/scss/list-group'; +// @import 'bootstrap/scss/close'; +// @import 'bootstrap/scss/toasts'; +// @import 'bootstrap/scss/modal'; +// @import 'bootstrap/scss/tooltip'; +// @import 'bootstrap/scss/popover'; +// @import 'bootstrap/scss/carousel'; +// @import 'bootstrap/scss/spinners'; +// @import 'bootstrap/scss/offcanvas'; + +// Helpers +// @import 'bootstrap/scss/helpers'; + +// Utilities +// @import 'bootstrap/scss/utilities/api'; diff --git a/test/nimble_template/addons/variants/phoenix/api/.keep b/priv/templates/nimble_template/assets/nimble_css/_variables.scss similarity index 100% rename from test/nimble_template/addons/variants/phoenix/api/.keep rename to priv/templates/nimble_template/assets/nimble_css/_variables.scss diff --git a/priv/templates/nimble_template/assets/nimble_css/app.scss b/priv/templates/nimble_template/assets/nimble_css/app.scss new file mode 100644 index 00000000..ede91728 --- /dev/null +++ b/priv/templates/nimble_template/assets/nimble_css/app.scss @@ -0,0 +1,15 @@ +@import './variables'; + +@import './functions'; + +@import './vendor'; + +@import './mixins'; + +@import './base'; + +@import './components'; + +@import './layouts'; + +@import './screens'; diff --git a/priv/templates/nimble_template/assets/nimble_css/base/_index.scss b/priv/templates/nimble_template/assets/nimble_css/base/_index.scss new file mode 100644 index 00000000..e69de29b diff --git a/priv/templates/nimble_template/assets/nimble_css/components/_index.scss b/priv/templates/nimble_template/assets/nimble_css/components/_index.scss new file mode 100644 index 00000000..e69de29b diff --git a/priv/templates/nimble_template/assets/nimble_css/functions/_index.scss b/priv/templates/nimble_template/assets/nimble_css/functions/_index.scss new file mode 100644 index 00000000..9642f39c --- /dev/null +++ b/priv/templates/nimble_template/assets/nimble_css/functions/_index.scss @@ -0,0 +1 @@ +@import './sizing'; diff --git a/priv/templates/nimble_template/assets/nimble_css/functions/_sizing.scss b/priv/templates/nimble_template/assets/nimble_css/functions/_sizing.scss new file mode 100644 index 00000000..e7b39d6d --- /dev/null +++ b/priv/templates/nimble_template/assets/nimble_css/functions/_sizing.scss @@ -0,0 +1,14 @@ +@function rem($values, $base-font-size: 16px) { + $list: (); + + @each $value in $values { + @if ($value == 0 or $value == auto) { + $list: append($list, $value); + } @else { + $rem-value: ($value / $base-font-size) + rem; + $list: append($list, $rem-value); + } + } + + @return $list; +} diff --git a/priv/templates/nimble_template/assets/nimble_css/layouts/_default.scss b/priv/templates/nimble_template/assets/nimble_css/layouts/_default.scss new file mode 100644 index 00000000..a53b133e --- /dev/null +++ b/priv/templates/nimble_template/assets/nimble_css/layouts/_default.scss @@ -0,0 +1,8 @@ +.layout-default { + > body { + display: grid; + grid-template-columns: auto; + grid-template-rows: auto 1fr auto; + min-height: 100vh; + } +} diff --git a/priv/templates/nimble_template/assets/nimble_css/layouts/_index.scss b/priv/templates/nimble_template/assets/nimble_css/layouts/_index.scss new file mode 100644 index 00000000..14328d9d --- /dev/null +++ b/priv/templates/nimble_template/assets/nimble_css/layouts/_index.scss @@ -0,0 +1 @@ +@import './default'; diff --git a/priv/templates/nimble_template/assets/nimble_css/mixins/_index.scss b/priv/templates/nimble_template/assets/nimble_css/mixins/_index.scss new file mode 100644 index 00000000..59ea504d --- /dev/null +++ b/priv/templates/nimble_template/assets/nimble_css/mixins/_index.scss @@ -0,0 +1 @@ +@import './text-ellipsis'; diff --git a/priv/templates/nimble_template/assets/nimble_css/mixins/_text-ellipsis.scss b/priv/templates/nimble_template/assets/nimble_css/mixins/_text-ellipsis.scss new file mode 100644 index 00000000..07e71012 --- /dev/null +++ b/priv/templates/nimble_template/assets/nimble_css/mixins/_text-ellipsis.scss @@ -0,0 +1,23 @@ +$font-size-base: 1rem !default; +$line-height-base: 1.5 !default; + +@mixin text-ellipsis( + $lines: 2, + $font-size: $font-size-base, + $line-height: $line-height-base +) { + /* stylelint-disable property-no-vendor-prefix */ + /* stylelint-disable value-no-vendor-prefix */ + -webkit-box-orient: vertical; + display: -webkit-box; + -webkit-line-clamp: $lines; + max-height: calc(#{$line-height} * #{$font-size} * #{$lines}); + overflow: hidden; + word-break: break-word; + /* stylelint-enable property-no-vendor-prefix */ + /* stylelint-enable value-no-vendor-prefix */ + + @include media-breakpoint-up(md) { + max-height: calc(#{$line-height} * #{$font-size} * #{$lines}); + } +} diff --git a/priv/templates/nimble_template/assets/nimble_css/screens/_index.scss b/priv/templates/nimble_template/assets/nimble_css/screens/_index.scss new file mode 100644 index 00000000..e69de29b diff --git a/priv/templates/nimble_template/assets/nimble_css/vendor/_index.scss b/priv/templates/nimble_template/assets/nimble_css/vendor/_index.scss new file mode 100644 index 00000000..e69de29b diff --git a/priv/templates/nimble_template/assets/nimble_js/adapters/.keep b/priv/templates/nimble_template/assets/nimble_js/adapters/.keep new file mode 100644 index 00000000..e69de29b diff --git a/priv/templates/nimble_template/assets/nimble_js/components/.keep b/priv/templates/nimble_template/assets/nimble_js/components/.keep new file mode 100644 index 00000000..e69de29b diff --git a/priv/templates/nimble_template/assets/nimble_js/config/.keep b/priv/templates/nimble_template/assets/nimble_js/config/.keep new file mode 100644 index 00000000..e69de29b diff --git a/priv/templates/nimble_template/assets/nimble_js/helpers/event.js b/priv/templates/nimble_template/assets/nimble_js/helpers/event.js new file mode 100644 index 00000000..f61fc1a3 --- /dev/null +++ b/priv/templates/nimble_template/assets/nimble_js/helpers/event.js @@ -0,0 +1,12 @@ +export const dispatchEventFromElement = ( + element, + eventName, + eventDetail = {} +) => { + const event = new CustomEvent(eventName, { + detail: eventDetail, + cancelable: true, + }); + + element.dispatchEvent(event); +}; diff --git a/priv/templates/nimble_template/assets/nimble_js/helpers/screen.js b/priv/templates/nimble_template/assets/nimble_js/helpers/screen.js new file mode 100644 index 00000000..111e1ee4 --- /dev/null +++ b/priv/templates/nimble_template/assets/nimble_js/helpers/screen.js @@ -0,0 +1,5 @@ +"use strict"; + +export const isMobileScreenSize = () => { + return window.matchMedia("(max-width: 767px)").matches; +}; diff --git a/priv/templates/nimble_template/assets/nimble_js/initializers/index.js b/priv/templates/nimble_template/assets/nimble_js/initializers/index.js new file mode 100644 index 00000000..e69de29b diff --git a/priv/templates/nimble_template/assets/nimble_js/lib/.keep b/priv/templates/nimble_template/assets/nimble_js/lib/.keep new file mode 100644 index 00000000..e69de29b diff --git a/priv/templates/nimble_template/assets/nimble_js/screens/index.js b/priv/templates/nimble_template/assets/nimble_js/screens/index.js new file mode 100644 index 00000000..e69de29b diff --git a/priv/templates/nimble_template/assets/package.json b/priv/templates/nimble_template/assets/package.json new file mode 100644 index 00000000..ab72065e --- /dev/null +++ b/priv/templates/nimble_template/assets/package.json @@ -0,0 +1,11 @@ +{ + "repository": {}, + "description": " ", + "license": "MIT", + "scripts": { + }, + "dependencies": { + }, + "devDependencies": { + } +} diff --git a/priv/templates/nimble_template/coveralls.json.eex b/priv/templates/nimble_template/coveralls.json.eex index 7dd4aec6..0f032f3e 100644 --- a/priv/templates/nimble_template/coveralls.json.eex +++ b/priv/templates/nimble_template/coveralls.json.eex @@ -6,7 +6,10 @@ "lib/<%= otp_app %>_web.ex", "lib/<%= otp_app %>_mail.ex", "lib/<%= otp_app %>_web/endpoint.ex", + "lib/<%= otp_app %>_web/telemetry.ex", + "lib/<%= otp_app %>_web/channels/user_socket.ex", "lib/<%= otp_app %>_web/views/error_helpers.ex", + "lib/<%= otp_app %>_web/controller/api/fallback_controller.ex", "test/support" ], "coverage_options": { diff --git a/priv/templates/nimble_template/lib/otp_app/release_tasks.ex.eex b/priv/templates/nimble_template/lib/otp_app/release_tasks.ex.eex index 70470bfd..afc62cda 100644 --- a/priv/templates/nimble_template/lib/otp_app/release_tasks.ex.eex +++ b/priv/templates/nimble_template/lib/otp_app/release_tasks.ex.eex @@ -5,7 +5,14 @@ defmodule <%= base_module %>.ReleaseTasks do load_app() for repo <- repos() do - {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true)) + schema_migrations = Ecto.Migrator.migrations_path(repo, "migrations") + data_migrations = Ecto.Migrator.migrations_path(repo, "data_migrations") + + {:ok, _, _} = + Ecto.Migrator.with_repo( + repo, + &Ecto.Migrator.run(&1, [schema_migrations, data_migrations], :up, all: true) + ) end end diff --git a/priv/templates/nimble_template/lib/otp_app_web/controllers/api/fallback_controller.ex.eex b/priv/templates/nimble_template/lib/otp_app_web/controllers/api/fallback_controller.ex.eex new file mode 100644 index 00000000..18aba0d8 --- /dev/null +++ b/priv/templates/nimble_template/lib/otp_app_web/controllers/api/fallback_controller.ex.eex @@ -0,0 +1,13 @@ +defmodule <%= web_module %>.Api.FallbackController do + use Phoenix.Controller + + alias Ecto.Changeset + alias <%= web_module %>.Api.ErrorView + + def call(conn, {:error, :invalid_params, %Changeset{valid?: false} = changeset}) do + conn + |> put_status(:unprocessable_entity) + |> put_view(ErrorView) + |> render("error.json", %{code: :validation_error, changeset: changeset}) + end +end diff --git a/priv/templates/nimble_template/lib/otp_app_web/helpers/icon_helper.ex.eex b/priv/templates/nimble_template/lib/otp_app_web/helpers/icon_helper.ex.eex new file mode 100644 index 00000000..904c1da3 --- /dev/null +++ b/priv/templates/nimble_template/lib/otp_app_web/helpers/icon_helper.ex.eex @@ -0,0 +1,17 @@ +defmodule <%= web_module %>.IconHelper do + @moduledoc """ + Generate the SVG icon tag + """ + + use Phoenix.HTML + + alias <%= web_module %>.Router.Helpers, as: Routes + + def icon_tag(conn, name, opts \\ []) do + classes = "icon " <> Keyword.get(opts, :class, "") + + content_tag(:svg, class: classes) do + tag(:use, "xlink:href": Routes.static_path(conn, "/images/icon-sprite.svg#" <> name)) + end + end +end diff --git a/priv/templates/nimble_template/lib/otp_app_web/params/params.ex.eex b/priv/templates/nimble_template/lib/otp_app_web/params/params.ex.eex new file mode 100644 index 00000000..74d8be75 --- /dev/null +++ b/priv/templates/nimble_template/lib/otp_app_web/params/params.ex.eex @@ -0,0 +1,23 @@ +defmodule <%= web_module %>.Params do + @moduledoc """ + Apply to the params module to define params schema with validation. + """ + + @callback changeset(map(), map()) :: Ecto.Changeset.t() + + @optional_callbacks [changeset: 2] + + @type t :: module + + defmacro __using__(_) do + quote do + use Ecto.Schema + + import Ecto.Changeset + + @primary_key false + + @behaviour <%= web_module %>.Params + end + end +end diff --git a/priv/templates/nimble_template/lib/otp_app_web/params/params_validator.ex.eex b/priv/templates/nimble_template/lib/otp_app_web/params/params_validator.ex.eex new file mode 100644 index 00000000..360ea024 --- /dev/null +++ b/priv/templates/nimble_template/lib/otp_app_web/params/params_validator.ex.eex @@ -0,0 +1,34 @@ +defmodule <%= web_module %>.ParamsValidator do + @moduledoc """ + Helper module for validating given request params with params module + """ + + alias Ecto.Changeset + alias <%= web_module %>.Params + + @spec validate(map(), atom(), [{:for, Params.t()}]) :: + {:ok, map()} | {:error, :invalid_params, Ecto.Changeset.t()} + def validate(params, changeset_method \\ :changeset, for: params_module) do + params_module + |> Kernel.apply(changeset_method, [params]) + |> handle_changeset() + end + + defp handle_changeset(%Changeset{valid?: true} = changeset), + do: {:ok, extract_changes(changeset)} + + defp handle_changeset(changeset), do: {:error, :invalid_params, put_action(changeset)} + + defp extract_changes(%Changeset{} = changeset) do + Enum.reduce(changeset.changes, %{}, fn {key, value}, params -> + Map.put(params, key, extract_changes(value)) + end) + end + + defp extract_changes([%Changeset{} | _] = changesets), + do: Enum.map(changesets, &extract_changes/1) + + defp extract_changes(value), do: value + + defp put_action(%Changeset{} = changeset), do: Map.put(changeset, :action, :validate) +end diff --git a/priv/templates/nimble_template/lib/otp_app_web/plugs/check_empty_body_params_plug.ex.eex b/priv/templates/nimble_template/lib/otp_app_web/plugs/check_empty_body_params_plug.ex.eex new file mode 100644 index 00000000..45765bd1 --- /dev/null +++ b/priv/templates/nimble_template/lib/otp_app_web/plugs/check_empty_body_params_plug.ex.eex @@ -0,0 +1,21 @@ +defmodule <%= web_module %>.CheckEmptyBodyParamsPlug do + @behaviour Plug + + import Phoenix.Controller, only: [put_view: 2, render: 3] + import Plug.Conn + + alias <%= web_module %>.Api.ErrorView + + def init(opts), do: opts + + def call(%{body_params: body_params, method: "POST"} = conn, _opts) + when body_params == %{} do + conn + |> put_status(:bad_request) + |> put_view(ErrorView) + |> render("400.json", %{message: "Missing body params"}) + |> halt() + end + + def call(conn, _opts), do: conn +end diff --git a/priv/templates/nimble_template/lib/otp_app_web/plugs/health_plug.ex.eex b/priv/templates/nimble_template/lib/otp_app_web/plugs/health_plug.ex.eex new file mode 100644 index 00000000..06d721a3 --- /dev/null +++ b/priv/templates/nimble_template/lib/otp_app_web/plugs/health_plug.ex.eex @@ -0,0 +1,57 @@ +defmodule <%= web_module %>.HealthPlug do + @behaviour Plug + + import Plug.Conn + + alias Ecto.Adapters.SQL + alias <%= base_module %>.Repo + + require Logger + + @liveness_path ["liveness"] + @readiness_path ["readiness"] + + @impl true + def init(opts), do: opts + + @impl true + def call(%{path_info: @liveness_path} = conn, _opts) do + conn + |> resp(:ok, "alive") + |> halt() + end + + @impl true + def call(%{path_info: @readiness_path} = conn, _opts) do + case readiness_check() do + :ok -> + conn + |> resp(:ok, "ready") + |> halt() + + :error -> + conn + |> resp(:internal_server_error, "") + |> halt() + end + end + + @impl true + def call(conn, _opts) do + conn + |> resp(:not_found, "") + |> halt() + end + + defp readiness_check do + case SQL.query(Repo, "SELECT 1") do + {:ok, _result} -> + :ok + + {:error, exception} -> + Logger.error(inspect(exception)) + + :error + end + end +end diff --git a/priv/templates/nimble_template/lib/otp_app_web/views/api/error_view.ex.eex b/priv/templates/nimble_template/lib/otp_app_web/views/api/error_view.ex.eex new file mode 100644 index 00000000..5d61f744 --- /dev/null +++ b/priv/templates/nimble_template/lib/otp_app_web/views/api/error_view.ex.eex @@ -0,0 +1,55 @@ +defmodule <%= web_module %>.Api.ErrorView do + use <%= web_module %>, :view + + alias Ecto.Changeset + + def template_not_found(template, assigns) do + build_error_response( + code: status_code_from_template(template), + detail: %{}, + message: assigns[:message] || Phoenix.Controller.status_message_from_template(template) + ) + end + + def render("error.json", %{code: code, changeset: %Changeset{} = changeset}) do + build_error_response( + code: code, + detail: translate_errors(changeset), + message: build_changeset_error_message(changeset) + ) + end + + defp build_changeset_error_message(%Changeset{} = changeset) do + changeset + |> translate_errors() + |> Enum.flat_map(fn {key, messages} -> + Enum.map(messages, &"#{Phoenix.Naming.humanize(key)} #{&1}") + end) + |> to_sentence() + end + + defp to_sentence([message]), do: message + + defp to_sentence(messages) do + sentence = + messages + |> Enum.slice(0..(length(messages) - 2)) + |> Enum.join(", ") + + "#{sentence} and #{List.last(messages)}" + end + + defp build_error_response(code: code, detail: detail, message: message) do + %{ + errors: [ + %{ + code: code, + detail: detail, + message: message + } + ] + } + end + + defp translate_errors(changeset), do: Changeset.traverse_errors(changeset, &translate_error/1) +end diff --git a/priv/templates/nimble_template/lib/otp_app_web/views/error_helpers.ex.eex b/priv/templates/nimble_template/lib/otp_app_web/views/error_helpers.ex.eex new file mode 100644 index 00000000..78dc4631 --- /dev/null +++ b/priv/templates/nimble_template/lib/otp_app_web/views/error_helpers.ex.eex @@ -0,0 +1,59 @@ +defmodule <%= web_module %>.ErrorHelpers do + @moduledoc """ + Conveniences for translating and building error messages. + """ + + use Phoenix.HTML + + alias Plug.Conn.Status + + @doc """ + Generates tag for inlined form input errors. + """ + def error_tag(form, field) do + Enum.map(Keyword.get_values(form.errors, field), fn error -> + content_tag(:span, translate_error(error), + class: "invalid-feedback", + phx_feedback_for: input_id(form, field) + ) + end) + end + + @doc """ + Translates an error message using gettext. + """ + def translate_error({msg, opts}) do + # When using gettext, we typically pass the strings we want + # to translate as a static argument: + # + # # Translate "is invalid" in the "errors" domain + # dgettext("errors", "is invalid") + # + # # Translate the number of files with plural rules + # dngettext("errors", "1 file", "%{count} files", count) + # + # Because the error messages we show in our forms and APIs + # are defined inside Ecto, we need to translate them dynamically. + # This requires us to call the Gettext module passing our gettext + # backend as first argument. + # + # Note we use the "errors" domain, which means translations + # should be written to the errors.po file. The :count option is + # set by Ecto and indicates we should also apply plural rules. + if count = opts[:count] do + Gettext.dngettext(<%= web_module %>.Gettext, "errors", msg, msg, count, opts) + else + Gettext.dgettext(<%= web_module %>.Gettext, "errors", msg, opts) + end + end + + def status_code_from_template(template) do + template + |> String.split(".") + |> hd() + |> String.to_integer() + |> Status.reason_atom() + rescue + _ -> :internal_server_error + end +end diff --git a/priv/templates/nimble_template/priv/repo/data_migrations/.keep b/priv/templates/nimble_template/priv/repo/data_migrations/.keep new file mode 100644 index 00000000..e69de29b diff --git a/priv/templates/nimble_template/test/otp_app_web/helpers/icon_helper_test.exs.eex b/priv/templates/nimble_template/test/otp_app_web/helpers/icon_helper_test.exs.eex new file mode 100644 index 00000000..d68f89c8 --- /dev/null +++ b/priv/templates/nimble_template/test/otp_app_web/helpers/icon_helper_test.exs.eex @@ -0,0 +1,19 @@ +defmodule <%= web_module %>.IconHelperTest do + use <%= web_module %>.ConnCase, async: true + + import Phoenix.HTML, only: [safe_to_string: 1] + + alias <%= web_module %>.IconHelper + + describe "icon_tag/3" do + test "renders a svg icon" do + image = + <%= web_module %>.Endpoint + |> IconHelper.icon_tag("active", class: "customize-icon-class") + |> safe_to_string() + + assert image == + "" + end + end +end diff --git a/priv/templates/nimble_template/test/otp_app_web/params/params_validator_test.exs.eex b/priv/templates/nimble_template/test/otp_app_web/params/params_validator_test.exs.eex new file mode 100644 index 00000000..2076e717 --- /dev/null +++ b/priv/templates/nimble_template/test/otp_app_web/params/params_validator_test.exs.eex @@ -0,0 +1,104 @@ +defmodule <%= web_module %>.ParamsValidatorTest do + use <%= base_module %>.DataCase, async: true + + alias <%= web_module %>.ParamsValidator + + defmodule CreateDeviceParams do + use <%= web_module %>.Params + + embedded_schema do + field(:device_id, :string) + field(:device_name, :string) + end + + def changeset(data \\ %__MODULE__{}, params) do + data + |> cast(params, [:device_id, :device_name]) + |> validate_required([:device_id, :device_name]) + end + + def custom_changeset(data \\ %__MODULE__{}, params) do + data + |> cast(params, [:device_id, :device_name]) + |> validate_required([:device_id]) + end + end + + defmodule CreateUserParams do + use <%= web_module %>.Params + + embedded_schema do + field(:name, :string) + embeds_many(:devices, CreateDeviceParams) + end + + def changeset(data \\ %__MODULE__{}, params) do + data + |> cast(params, [:name]) + |> cast_embed(:devices, required: true) + |> validate_required([:name]) + end + end + + describe "validate/2" do + test "given valid params, returns {:ok, validated_params}" do + params = %{ + "name" => "John Doe", + "devices" => [ + %{ + "device_id" => "Android", + "device_name" => "John Doe Devices" + } + ] + } + + assert {:ok, validated_params} = ParamsValidator.validate(params, for: CreateUserParams) + + assert validated_params == %{ + devices: [%{device_id: "Android", device_name: "John Doe Devices"}], + name: "John Doe" + } + end + + test "given valid params for the custom_changeset, returns {:ok, validated_params} " do + params = %{ + "device_id" => "Android", + "device_name" => "John Doe Devices" + } + + assert {:ok, validated_params} = + ParamsValidator.validate(params, :custom_changeset, for: CreateDeviceParams) + + assert validated_params == %{ + device_id: "Android", + device_name: "John Doe Devices" + } + end + + test "given invalid params, returns {:error, :invalid_params, changeset}" do + params = %{"device_id" => ""} + + assert {:error, :invalid_params, changeset} = + ParamsValidator.validate(params, for: CreateUserParams) + + assert changeset.valid? == false + assert changeset.action == :validate + + assert errors_on(changeset) == %{devices: ["can't be blank"], name: ["can't be blank"]} + end + + test "given invalid params for the custom_changeset, returns {:error, :invalid_params, changeset}" do + params = %{"device_id" => ""} + + assert {:error, :invalid_params, changeset} = + ParamsValidator.validate(params, :custom_changeset, for: CreateDeviceParams) + + assert changeset.valid? == false + assert changeset.action == :validate + + assert errors_on(changeset) == %{ + device_id: ["can't be blank"] + } + end + end +end diff --git a/priv/templates/nimble_template/test/otp_app_web/plugs/check_empty_body_params_plug_test.exs.eex b/priv/templates/nimble_template/test/otp_app_web/plugs/check_empty_body_params_plug_test.exs.eex new file mode 100644 index 00000000..550bafc4 --- /dev/null +++ b/priv/templates/nimble_template/test/otp_app_web/plugs/check_empty_body_params_plug_test.exs.eex @@ -0,0 +1,70 @@ +defmodule <%= web_module %>.CheckEmptyBodyParamsPlugTest do + use <%= web_module %>.ConnCase, async: true + + alias <%= web_module %>.CheckEmptyBodyParamsPlug + + describe "init/1" do + test "returns given options" do + assert CheckEmptyBodyParamsPlug.init([]) == [] + end + end + + describe "call/2" do + test "given body params are empty on POST request, halts the conn and returns 400 status", + %{ + conn: conn + } do + conn = + conn + |> Map.put(:body_params, %{}) + |> Map.put(:method, "POST") + |> CheckEmptyBodyParamsPlug.call([]) + + assert conn.halted == true + + assert json_response(conn, 400) == %{ + "errors" => [ + %{ + "code" => "bad_request", + "detail" => %{}, + "message" => "Missing body params" + } + ] + } + end + + test "given body params are NOT empty on POST request, does NOT halt the conn", %{conn: conn} do + conn = + conn + |> Map.put(:body_params, %{name: "Android Phone"}) + |> Map.put(:method, "POST") + |> CheckEmptyBodyParamsPlug.call([]) + + assert conn.halted == false + end + + test "given body params are empty on GET request, does NOT halt the conn", %{ + conn: conn + } do + conn = + conn + |> Map.put(:body_params, %{}) + |> Map.put(:method, "GET") + |> CheckEmptyBodyParamsPlug.call([]) + + assert conn.halted == false + end + + test "given body params are empty on PATCH request, does NOT halt the conn", %{ + conn: conn + } do + conn = + conn + |> Map.put(:body_params, %{}) + |> Map.put(:method, "PATCH") + |> CheckEmptyBodyParamsPlug.call([]) + + assert conn.halted == false + end + end +end diff --git a/priv/templates/nimble_template/test/otp_app_web/plugs/health_plug_test.exs.eex b/priv/templates/nimble_template/test/otp_app_web/plugs/health_plug_test.exs.eex new file mode 100644 index 00000000..8f66bfc1 --- /dev/null +++ b/priv/templates/nimble_template/test/otp_app_web/plugs/health_plug_test.exs.eex @@ -0,0 +1,64 @@ +defmodule <%= web_module %>.HealthPlugTest do + use <%= web_module %>.ConnCase, async: true + + import ExUnit.CaptureLog + + alias <%= web_module %>.HealthPlug + + describe "init/1" do + test "returns given options" do + assert HealthPlug.init([]) == [] + end + end + + describe "call/2" do + test "given the liveness path, returns 200 status " do + conn = + :get + |> build_conn("/liveness") + |> HealthPlug.call([]) + + assert conn.halted == true + assert response(conn, :ok) =~ "alive" + end + + test "given the readiness path, returns 200 status " do + conn = + :get + |> build_conn("/readiness") + |> HealthPlug.call([]) + + assert conn.halted == true + assert response(conn, :ok) =~ "ready" + end + + test "given the database is not ready, returns 500 internal server error " do + expect(Ecto.Adapters.SQL, :query, fn <%= base_module %>.Repo, "SELECT 1" -> + {:error, DBConnection.ConnectionError.exception("Database connection error")} + end) + + error_message = + capture_log(fn -> + conn = + :get + |> build_conn("/readiness") + |> HealthPlug.call([]) + + assert conn.halted == true + assert response(conn, :internal_server_error) =~ "" + end) + + assert error_message =~ "Database connection error" + end + + test "given a path does NOT exist, returns 404 status " do + conn = + :get + |> build_conn("/something_else") + |> HealthPlug.call([]) + + assert conn.halted == true + assert response(conn, :not_found) =~ "" + end + end +end diff --git a/priv/templates/nimble_template/test/otp_app_web/requests/_health/liveness_request_test.exs.eex b/priv/templates/nimble_template/test/otp_app_web/requests/_health/liveness_request_test.exs.eex new file mode 100644 index 00000000..ed8d61a2 --- /dev/null +++ b/priv/templates/nimble_template/test/otp_app_web/requests/_health/liveness_request_test.exs.eex @@ -0,0 +1,13 @@ +defmodule <%= web_module %>.LivenessRequestTest do + use <%= web_module %>.ConnCase, async: true + + test "returns 200", %{conn: conn} do + conn = + get( + conn, + "#{Application.get_env(:<%= otp_app %>, <%= web_module %>.Endpoint)[:health_path]}/liveness" + ) + + assert response(conn, :ok) =~ "alive" + end +end diff --git a/priv/templates/nimble_template/test/otp_app_web/requests/_health/rediness_request_test.exs.eex b/priv/templates/nimble_template/test/otp_app_web/requests/_health/rediness_request_test.exs.eex new file mode 100644 index 00000000..f24047e6 --- /dev/null +++ b/priv/templates/nimble_template/test/otp_app_web/requests/_health/rediness_request_test.exs.eex @@ -0,0 +1,13 @@ +defmodule <%= web_module %>.ReadinessRequestTest do + use <%= web_module %>.ConnCase, async: true + + test "returns 200", %{conn: conn} do + conn = + get( + conn, + "#{Application.get_env(:<%= otp_app %>, <%= web_module %>.Endpoint)[:health_path]}/readiness" + ) + + assert response(conn, :ok) =~ "ready" + end +end diff --git a/priv/templates/nimble_template/test/otp_app_web/views/api/error_view_test.exs b/priv/templates/nimble_template/test/otp_app_web/views/api/error_view_test.exs new file mode 100644 index 00000000..30c1254d --- /dev/null +++ b/priv/templates/nimble_template/test/otp_app_web/views/api/error_view_test.exs @@ -0,0 +1,93 @@ +defmodule <%= web_module %>.Api.ErrorViewTest do + use <%= web_module %>.ViewCase, async: true + + alias <%= web_module %>.Api.ErrorView + + defmodule Device do + use Ecto.Schema + + import Ecto.Changeset + + schema "devices" do + field :device_id, :string + field :operating_system, :string + field :device_name, :string + + timestamps() + end + + def changeset(device \\ %__MODULE__{}, attrs) do + device + |> cast(attrs, [ + :device_id, + :operating_system, + :device_name + ]) + |> validate_required([ + :device_id, + :operating_system, + :device_name + ]) + end + end + + test "renders 404.json" do + assert render(ErrorView, "404.json", []) == %{ + errors: [%{code: :not_found, detail: %{}, message: "Not Found"}] + } + end + + test "renders 500.json" do + assert render(ErrorView, "500.json", []) == %{ + errors: [ + %{code: :internal_server_error, detail: %{}, message: "Internal Server Error"} + ] + } + end + + test "renders custom error message" do + assert render(ErrorView, "500.json", status: 500, message: "Something went wrong") == + %{ + errors: [ + %{code: :internal_server_error, detail: %{}, message: "Something went wrong"} + ] + } + end + + test "given error code and an invalid changeset with multiple errors fields, renders error.json" do + changeset = Device.changeset(%{}) + error = %{code: :validation_error, changeset: changeset} + + assert render(ErrorView, "error.json", error) == + %{ + errors: [ + %{ + code: :validation_error, + detail: %{ + device_id: ["can't be blank"], + device_name: ["can't be blank"], + operating_system: ["can't be blank"] + }, + message: + "Device can't be blank, Device name can't be blank and Operating system can't be blank" + } + ] + } + end + + test "given error code and an invalid changeset with single error field, renders error.json" do + changeset = Device.changeset(%{device_id: "12345678-9012", device_name: "Android"}) + error = %{code: :validation_error, changeset: changeset} + + assert render(ErrorView, "error.json", error) == + %{ + errors: [ + %{ + code: :validation_error, + detail: %{operating_system: ["can't be blank"]}, + message: "Operating system can't be blank" + } + ] + } + end +end diff --git a/priv/templates/nimble_template/test/otp_app_web/views/error_helpers_test.exs b/priv/templates/nimble_template/test/otp_app_web/views/error_helpers_test.exs new file mode 100644 index 00000000..4428b2ec --- /dev/null +++ b/priv/templates/nimble_template/test/otp_app_web/views/error_helpers_test.exs @@ -0,0 +1,19 @@ +defmodule <%= web_module %>.ErrorHelpersTest do + use <%= web_module %>.ViewCase, async: true + + alias <%= web_module %>.ErrorHelpers + + describe "status_code_from_template/1" do + test "given 404.json, returns :not_found error code" do + assert ErrorHelpers.status_code_from_template("404.json") == :not_found + end + + test "given 500.json, returns :internal_server_error error code" do + assert ErrorHelpers.status_code_from_template("500.json") == :internal_server_error + end + + test "given non-existing error number, returns :internal_server_error error code" do + assert ErrorHelpers.status_code_from_template("99999.json") == :internal_server_error + end + end +end diff --git a/priv/templates/nimble_template/test/support/feature_case.ex.eex b/priv/templates/nimble_template/test/support/feature_case.ex.eex index bd9db2d0..9cb1f5f3 100644 --- a/priv/templates/nimble_template/test/support/feature_case.ex.eex +++ b/priv/templates/nimble_template/test/support/feature_case.ex.eex @@ -3,11 +3,18 @@ defmodule <%= web_module %>.FeatureCase do using do quote do + use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney use Wallaby.Feature + use Mimic import <%= base_module %>.Factory + import <%= web_module %>.Gettext + alias <%= base_module %>.Repo + alias <%= web_module %>.Endpoint alias <%= web_module %>.Router.Helpers, as: Routes + + @moduletag :feature_test end end end diff --git a/priv/templates/nimble_template/test/support/view_case.ex.eex b/priv/templates/nimble_template/test/support/view_case.ex.eex new file mode 100644 index 00000000..6bfa9df6 --- /dev/null +++ b/priv/templates/nimble_template/test/support/view_case.ex.eex @@ -0,0 +1,22 @@ +defmodule <%= web_module %>.ViewCase do + use ExUnit.CaseTemplate + + using do + quote do + import Mimic + import Phoenix.View + import <%= base_module %>.Factory + import <%= web_module %>.ViewCase + import Phoenix.ConnTest, only: [get: 2, bypass_through: 1] + + alias <%= web_module %>.Router.Helpers, as: Routes + + # The default endpoint for testing + @endpoint <%= web_module %>.Endpoint + end + end + + setup do + {:ok, conn: Phoenix.ConnTest.build_conn()} + end +end diff --git a/test/nimble_template/addons/credo_test.exs b/test/nimble_template/addons/credo_test.exs index ea468150..0071f783 100644 --- a/test/nimble_template/addons/credo_test.exs +++ b/test/nimble_template/addons/credo_test.exs @@ -27,7 +27,7 @@ defmodule NimbleTemplate.Addons.CredoTest do assert file =~ """ defp deps do [ - {:credo, \"~> 1.4\", [only: [:dev, :test], runtime: false]}, + {:credo, "~> 1.4", [only: [:dev, :test], runtime: false]}, """ end) end) @@ -41,7 +41,8 @@ defmodule NimbleTemplate.Addons.CredoTest do assert file =~ """ defp aliases do [ - codebase: [\"deps.unlock --check-unused\", \"format --check-formatted\", \"credo --strict\"], + codebase: [ + "credo --strict", """ end) end) @@ -75,7 +76,7 @@ defmodule NimbleTemplate.Addons.CredoTest do assert file =~ """ defp deps do [ - {:credo, \"~> 1.4\", [only: [:dev, :test], runtime: false]}, + {:credo, "~> 1.4", [only: [:dev, :test], runtime: false]}, """ end) end) @@ -89,7 +90,8 @@ defmodule NimbleTemplate.Addons.CredoTest do assert file =~ """ defp aliases do [ - codebase: [\"deps.unlock --check-unused\", \"format --check-formatted\", \"credo --strict\"] + codebase: [ + "credo --strict", """ end) end) diff --git a/test/nimble_template/addons/dialyxir_test.exs b/test/nimble_template/addons/dialyxir_test.exs index 02ea4848..e6e0685e 100644 --- a/test/nimble_template/addons/dialyxir_test.exs +++ b/test/nimble_template/addons/dialyxir_test.exs @@ -15,7 +15,7 @@ defmodule NimbleTemplate.Addons.DialyxirTest do assert file =~ """ defp deps do [ - {:dialyxir, \"~> 1.0\", [only: [:dev], runtime: false]}, + {:dialyxir, "~> 1.0", [only: [:dev], runtime: false]}, """ end) end) diff --git a/test/nimble_template/addons/elixir_version_test.exs b/test/nimble_template/addons/elixir_version_test.exs index 5b833b1d..df3f74a5 100644 --- a/test/nimble_template/addons/elixir_version_test.exs +++ b/test/nimble_template/addons/elixir_version_test.exs @@ -11,8 +11,8 @@ defmodule NimbleTemplate.Addons.ElixirVersionTest do assert_file(".tool-versions", fn file -> assert file =~ """ - erlang 23.3 - elixir 1.11.4-otp-23 + erlang 24.2.2 + elixir 1.13.3-otp-24 """ end) end) diff --git a/test/nimble_template/addons/ex_coveralls_test.exs b/test/nimble_template/addons/ex_coveralls_test.exs index 0da13400..70a6596b 100644 --- a/test/nimble_template/addons/ex_coveralls_test.exs +++ b/test/nimble_template/addons/ex_coveralls_test.exs @@ -26,7 +26,7 @@ defmodule NimbleTemplate.Addons.ExCoverallsTest do assert file =~ """ defp deps do [ - {:excoveralls, \"~> 0.12.2\", [only: :test]}, + {:excoveralls, "~> 0.12.2", [only: :test]}, """ end) end) @@ -59,7 +59,7 @@ defmodule NimbleTemplate.Addons.ExCoverallsTest do assert file =~ """ defp aliases do [ - coverage: [\"coveralls.html --raise\"], + coverage: ["coveralls.html --raise"], """ end) end) @@ -93,7 +93,7 @@ defmodule NimbleTemplate.Addons.ExCoverallsTest do assert file =~ """ defp deps do [ - {:excoveralls, \"~> 0.12.2\", [only: :test]}, + {:excoveralls, "~> 0.12.2", [only: :test]}, """ end) end) @@ -126,7 +126,7 @@ defmodule NimbleTemplate.Addons.ExCoverallsTest do assert file =~ """ defp aliases do [ - coverage: [\"coveralls.html --raise\"], + coverage: ["coveralls.html --raise"], """ end) end) diff --git a/test/nimble_template/addons/faker_test.exs b/test/nimble_template/addons/faker_test.exs new file mode 100644 index 00000000..93ae0646 --- /dev/null +++ b/test/nimble_template/addons/faker_test.exs @@ -0,0 +1,24 @@ +defmodule NimbleTemplate.Addons.FakerTest do + use NimbleTemplate.AddonCase, async: false + + describe "#apply/2" do + @describetag mock_latest_package_versions: [{:faker, "0.17.0"}] + + test "injects faker to mix dependency", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + Addons.Faker.apply(project) + + assert_file("mix.exs", fn file -> + assert file =~ """ + defp deps do + [ + {:faker, "~> 0.17.0", [only: [:dev, :test], runtime: false]}, + """ + end) + end) + end + end +end diff --git a/test/nimble_template/addons/github_test.exs b/test/nimble_template/addons/github_test.exs index 43157cfb..8adb9569 100644 --- a/test/nimble_template/addons/github_test.exs +++ b/test/nimble_template/addons/github_test.exs @@ -23,6 +23,17 @@ defmodule NimbleTemplate.Addons.GithubTest do assert_file(".github/PULL_REQUEST_TEMPLATE.md") end) end + + test "copies the .github/PULL_REQUEST_TEMPLATE/RELEASE_TEMPLATE.md", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + Addons.Github.apply(project, %{github_template: true}) + + assert_file(".github/PULL_REQUEST_TEMPLATE/RELEASE_TEMPLATE.md") + end) + end end describe "#apply/2 with mix_project and github_template option" do @@ -49,9 +60,148 @@ defmodule NimbleTemplate.Addons.GithubTest do assert_file(".github/PULL_REQUEST_TEMPLATE.md") end) end + + test "copies the .github/PULL_REQUEST_TEMPLATE/RELEASE_TEMPLATE.md", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + Addons.Github.apply(project, %{github_template: true}) + + assert_file(".github/PULL_REQUEST_TEMPLATE/RELEASE_TEMPLATE.md") + end) + end + end + + describe "#apply/2 with github_workflows_readme option" do + test "copies the .github/workflows/README.md", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + Addons.Github.apply(project, %{ + github_workflows_readme: true, + with_test_workflow?: true, + with_deploy_to_heroku_workflow?: true + }) + + assert_file(".github/workflows/README.md", fn file -> + assert file =~ "- Test" + + assert file =~ "- [Deploy to Heroku](#deploy-to-heroku-workflow-usage-instruction)" + assert file =~ "## Deploy to Heroku Workflow usage instruction" + end) + end) + end + + test "does NOT generate the Test section given with_test_workflow? is false", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + Addons.Github.apply(project, %{ + github_workflows_readme: true, + with_test_workflow?: false, + with_deploy_to_heroku_workflow?: true + }) + + assert_file(".github/workflows/README.md", fn file -> + refute file =~ "- Test" + + assert file =~ "- [Deploy to Heroku](#deploy-to-heroku-workflow-usage-instruction)" + assert file =~ "## Deploy to Heroku Workflow usage instruction" + end) + end) + end + + test "does NOT generate the Deploy to Heroku section given with_deploy_to_heroku_workflow? is false", + %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + Addons.Github.apply(project, %{ + github_workflows_readme: true, + with_test_workflow?: true, + with_deploy_to_heroku_workflow?: false + }) + + assert_file(".github/workflows/README.md", fn file -> + assert file =~ "- Test" + + refute file =~ "- [Deploy to Heroku](#deploy-to-heroku-workflow-usage-instruction)" + refute file =~ "## Deploy to Heroku Workflow usage instruction" + end) + end) + end + + test "does NOT generate the Test and Deploy to Heroku sections given with_test_workflow? and with_deploy_to_heroku_workflow? are false", + %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + Addons.Github.apply(project, %{ + github_workflows_readme: true, + with_test_workflow?: false, + with_deploy_to_heroku_workflow?: false + }) + + assert_file(".github/workflows/README.md", fn file -> + refute file =~ "- Test" + + refute file =~ "- [Deploy to Heroku](#deploy-to-heroku-workflow-usage-instruction)" + refute file =~ "## Deploy to Heroku Workflow usage instruction" + end) + end) + end end - describe "#apply/2 with api_project and github_action option" do + describe "#apply/2 with mix_project and github_workflows_readme option" do + @describetag mix_project?: true + + test "copies the .github/workflows/README.md", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + Addons.Github.apply(project, %{ + github_workflows_readme: true, + with_test_workflow?: true, + with_deploy_to_heroku_workflow?: false + }) + + assert_file(".github/workflows/README.md", fn file -> + assert file =~ "- Test" + + refute file =~ "- [Deploy to Heroku](#deploy-to-heroku-workflow-usage-instruction)" + refute file =~ "## Deploy to Heroku Workflow usage instruction" + end) + end) + end + + test "does NOT generate the Test section given with_test_workflow? is false", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + Addons.Github.apply(project, %{ + github_workflows_readme: true, + with_test_workflow?: false, + with_deploy_to_heroku_workflow?: false + }) + + assert_file(".github/workflows/README.md", fn file -> + refute file =~ "- Test" + + refute file =~ "- [Deploy to Heroku](#deploy-to-heroku-workflow-usage-instruction)" + refute file =~ "## Deploy to Heroku Workflow usage instruction" + end) + end) + end + end + + describe "#apply/2 with api_project and github_action_test option" do test "does NOT include the npm setting", %{ project: project, test_project_path: test_project_path @@ -59,37 +209,37 @@ defmodule NimbleTemplate.Addons.GithubTest do project = %{project | api_project?: true, web_project?: false} in_test_project(test_project_path, fn -> - Addons.Github.apply(project, %{github_action: true}) + Addons.Github.apply(project, %{github_action_test: true}) assert_file(".github/workflows/test.yml", fn file -> refute file =~ "assets/node_modules" refute file =~ "npm --prefix assets install" - refute file =~ "npm run --prefix assets build:dev" + refute file =~ "mix assets.deploy" refute file =~ "wallaby_screenshots" end) end) end end - describe "#apply/2 with web_project and github_action option" do + describe "#apply/2 with web_project and github_action_test option" do test "includes the npm setting", %{ project: project, test_project_path: test_project_path } do in_test_project(test_project_path, fn -> - Addons.Github.apply(project, %{github_action: true}) + Addons.Github.apply(project, %{github_action_test: true}) assert_file(".github/workflows/test.yml", fn file -> assert file =~ "assets/node_modules" assert file =~ "npm --prefix assets install" - assert file =~ "npm run --prefix assets build:dev" + assert file =~ "mix assets.deploy" assert file =~ "wallaby_screenshots" end) end) end end - describe "#apply/2 with mix_project and github_action option" do + describe "#apply/2 with mix_project and github_action_test option" do @describetag mix_project?: true test "does NOT include database setting", %{ @@ -97,7 +247,7 @@ defmodule NimbleTemplate.Addons.GithubTest do test_project_path: test_project_path } do in_test_project(test_project_path, fn -> - Addons.Github.apply(project, %{github_action: true}) + Addons.Github.apply(project, %{github_action_test: true}) assert_file(".github/workflows/test.yml", fn file -> refute file =~ "postgres" @@ -112,15 +262,280 @@ defmodule NimbleTemplate.Addons.GithubTest do test_project_path: test_project_path } do in_test_project(test_project_path, fn -> - Addons.Github.apply(project, %{github_action: true}) + Addons.Github.apply(project, %{github_action_test: true}) assert_file(".github/workflows/test.yml", fn file -> refute file =~ "assets/node_modules" refute file =~ "npm --prefix assets install" - refute file =~ "npm run --prefix assets build:dev" + refute file =~ "mix assets.deploy" refute file =~ "wallaby_screenshots" end) end) end end + + describe "#apply/2 with api_project and github_action_deploy_heroku option" do + test "copies the .github/workflows/deploy_heroku.yml file", %{ + project: project, + test_project_path: test_project_path + } do + project = %{project | api_project?: true, web_project?: false} + + in_test_project(test_project_path, fn -> + Addons.Github.apply(project, %{github_action_deploy_heroku: true}) + + assert_file(".github/workflows/deploy_heroku.yml") + end) + end + + test "adjusts config/runtime.exs ", %{ + project: project, + test_project_path: test_project_path + } do + project = %{project | api_project?: true, web_project?: false} + + in_test_project(test_project_path, fn -> + Addons.Github.apply(project, %{github_action_deploy_heroku: true}) + + assert_file("config/runtime.exs", fn file -> + assert file =~ "url: [scheme: \"https\", host: host," + + assert file =~ """ + config :nimble_template, NimbleTemplate.Repo, + ssl: true, + """ + end) + end) + end + + test "adds force_ssl config into config/prod.exs", %{ + project: project, + test_project_path: test_project_path + } do + project = %{project | api_project?: true, web_project?: false} + + in_test_project(test_project_path, fn -> + Addons.Github.apply(project, %{github_action_deploy_heroku: true}) + + assert_file("config/prod.exs", fn file -> + assert file =~ """ + config :nimble_template, NimbleTemplateWeb.Endpoint, + force_ssl: [rewrite_on: [:x_forwarded_proto]] + """ + end) + end) + end + end + + describe "#apply/2 with web_project and github_action_deploy_heroku option" do + test "copies the .github/workflows/deploy_heroku.yml file", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + Addons.Github.apply(project, %{github_action_deploy_heroku: true}) + + assert_file(".github/workflows/deploy_heroku.yml") + end) + end + end + + describe "#apply/2 with mix_project and github_action_deploy_heroku option" do + @describetag mix_project?: true + + test "raises FunctionClauseError exception", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + assert_raise FunctionClauseError, fn -> + Addons.Github.apply(project, %{github_action_deploy_heroku: true}) + end + end) + end + end + + describe "#apply/2 with github_wiki option" do + test "copies the .github/workflows/publish_wiki.yml and Github Wiki files", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + Addons.Github.apply(project, %{github_wiki: true}) + + assert_file(".github/workflows/publish_wiki.yml") + + assert_file(".github/wiki/Getting-Started.md", fn file -> + assert file =~ "Erlang 24.2.2" + assert file =~ "Elixir 1.13.3" + + assert file =~ """ + - Install Node dependencies: + + ```sh + npm install --prefix assets + ``` + """ + + assert file =~ """ + - Start the Phoenix app + + ```sh + iex -S mix phx.server + ``` + """ + end) + + assert_file(".github/wiki/Home.md", fn file -> + assert file =~ "Insert information about your project here!" + end) + + assert_file(".github/wiki/Application-Status.md") + + assert_file(".github/wiki/_Sidebar.md", fn file -> + assert file =~ """ + ## Table of Contents + + - [[Home]] + - [[Getting Started]] + + ## Infrastructure + + - [[Application Status]] + - [[Environment Variables]] + """ + end) + end) + end + + test "mentions Wiki in README.md", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + Addons.Github.apply(project, %{github_wiki: true}) + + assert_file("README.md", fn file -> + assert file =~ """ + ## Project documentation + + Most of the documentation is located in the `.github/wiki` directory, which is published to the [project's Github wiki](https://github.com/[REPO]/wiki). + """ + end) + end) + end + end + + describe "#apply/2 with api_project and github_wiki option" do + test "copies the .github/workflows/publish_wiki.yml and Github Wiki files", %{ + project: project, + test_project_path: test_project_path + } do + project = %{project | api_project?: true, web_project?: false} + + in_test_project(test_project_path, fn -> + Addons.Github.apply(project, %{github_wiki: true}) + + assert_file(".github/workflows/publish_wiki.yml") + + assert_file(".github/wiki/Getting-Started.md", fn file -> + assert file =~ "Erlang 24.2.2" + assert file =~ "Elixir 1.13.3" + + refute file =~ """ + - Install Node dependencies: + + ```sh + npm install --prefix assets + ``` + """ + + assert file =~ """ + - Start the Phoenix app + + ```sh + iex -S mix phx.server + ``` + """ + end) + + assert_file(".github/wiki/Home.md", fn file -> + assert file =~ "Insert information about your project here!" + end) + + assert_file(".github/wiki/Application-Status.md") + assert_file(".github/wiki/Environment-Variables.md") + + assert_file(".github/wiki/_Sidebar.md", fn file -> + assert file =~ """ + ## Table of Contents + + - [[Home]] + - [[Getting Started]] + + ## Infrastructure + + - [[Application Status]] + """ + end) + end) + end + end + + describe "#apply/2 with mix_project and github_wiki option" do + @describetag mix_project?: true + + test "copies the .github/workflows/publish_wiki.yml and Github Wiki files", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + Addons.Github.apply(project, %{github_wiki: true}) + + assert_file(".github/workflows/publish_wiki.yml") + + assert_file(".github/wiki/Getting-Started.md", fn file -> + assert file =~ "Erlang 24.2.2" + assert file =~ "Elixir 1.13.3" + + refute file =~ """ + - Install Node dependencies: + + ```sh + npm install --prefix assets + ``` + """ + + refute file =~ """ + - Start the Phoenix app + + ```sh + iex -S mix phx.server + ``` + """ + end) + + assert_file(".github/wiki/Home.md", fn file -> + assert file =~ "Insert information about your project here!" + end) + + refute_file(".github/wiki/Application-Status.md") + + assert_file(".github/wiki/_Sidebar.md", fn file -> + assert file =~ """ + ## Table of Contents + + - [[Home]] + - [[Getting Started]] + """ + + refute file =~ """ + ## Infrastructure + + - [[Application Status]] + """ + end) + end) + end + end end diff --git a/test/nimble_template/addons/mimic_test.exs b/test/nimble_template/addons/mimic_test.exs index 61455435..f9b3308b 100644 --- a/test/nimble_template/addons/mimic_test.exs +++ b/test/nimble_template/addons/mimic_test.exs @@ -15,13 +15,13 @@ defmodule NimbleTemplate.Addons.MimicTest do assert file =~ """ defp deps do [ - {:mimic, \"~> 1.3.1\", [only: :test]}, + {:mimic, "~> 1.3.1", [only: :test]}, """ end) end) end - test "stats the mimic in test/test_helper.exs", %{ + test "ensures mimic is started in test/test_helper.exs", %{ project: project, test_project_path: test_project_path } do @@ -37,5 +37,23 @@ defmodule NimbleTemplate.Addons.MimicTest do end) end) end + + test "updates test cases", %{project: project, test_project_path: test_project_path} do + in_test_project(test_project_path, fn -> + Addons.Mimic.apply(project) + + assert_file("test/support/channel_case.ex", fn file -> + assert file =~ "use Mimic" + end) + + assert_file("test/support/data_case.ex", fn file -> + assert file =~ "use Mimic" + end) + + assert_file("test/support/conn_case.ex", fn file -> + assert file =~ "use Mimic" + end) + end) + end end end diff --git a/test/nimble_template/addons/mix_release_test.exs b/test/nimble_template/addons/mix_release_test.exs deleted file mode 100644 index 4ca1ec59..00000000 --- a/test/nimble_template/addons/mix_release_test.exs +++ /dev/null @@ -1,68 +0,0 @@ -defmodule NimbleTemplate.Addons.MixReleaseTest do - use NimbleTemplate.AddonCase, async: false - - describe "#apply/2" do - test "deletes the import_config \"prod.secret.exs\" in config/prod.exs", %{ - project: project, - test_project_path: test_project_path - } do - in_test_project(test_project_path, fn -> - Addons.MixRelease.apply(project) - - assert_file("config/prod.exs", fn file -> - refute file =~ """ - # Finally import the config/prod.secret.exs which loads secrets - # and configuration from environment variables. - import_config "prod.secret.exs" - """ - end) - end) - end - - test "deletes the prod.secret.exs in config", %{ - project: project, - test_project_path: test_project_path - } do - in_test_project(test_project_path, fn -> - Addons.MixRelease.apply(project) - - refute_file("config/prod.secret.exs") - end) - end - - test "creates the runtime.exs in config", %{ - project: project, - test_project_path: test_project_path - } do - in_test_project(test_project_path, fn -> - Addons.MixRelease.apply(project) - - assert_file("config/runtime.exs", fn file -> - assert file =~ """ - import Config - - if config_env() == :prod do - """ - end) - end) - end - - test "creates the lib/nimble_template/release_tasks.ex", %{ - project: project, - test_project_path: test_project_path - } do - in_test_project(test_project_path, fn -> - Addons.MixRelease.apply(project) - - assert_file("lib/nimble_template/release_tasks.ex", fn file -> - assert file =~ """ - defmodule NimbleTemplate.ReleaseTasks do - @app :nimble_template - - def migrate do - """ - end) - end) - end - end -end diff --git a/test/nimble_template/addons/readme_test.exs b/test/nimble_template/addons/readme_test.exs index 36d415c6..3bb43db3 100644 --- a/test/nimble_template/addons/readme_test.exs +++ b/test/nimble_template/addons/readme_test.exs @@ -10,11 +10,11 @@ defmodule NimbleTemplate.Addons.ReadmeTest do Addons.Readme.apply(project) assert_file("README.md", fn file -> - assert file =~ "Erlang 23.3" - assert file =~ "Elixir 1.11.4" + assert file =~ "Erlang 24.2.2" + assert file =~ "Elixir 1.13.3" assert file =~ """ - * Install Node dependencies: + - Install Node dependencies: ```sh npm install --prefix assets @@ -22,7 +22,7 @@ defmodule NimbleTemplate.Addons.ReadmeTest do """ assert file =~ """ - * Start the Phoenix app + - Start the Phoenix app ```sh iex -S mix phx.server @@ -44,11 +44,11 @@ defmodule NimbleTemplate.Addons.ReadmeTest do Addons.Readme.apply(project) assert_file("README.md", fn file -> - assert file =~ "Erlang 23.3" - assert file =~ "Elixir 1.11.4" + assert file =~ "Erlang 24.2.2" + assert file =~ "Elixir 1.13.3" refute file =~ """ - * Install Node dependencies: + - Install Node dependencies: ```sh npm install --prefix assets @@ -56,7 +56,7 @@ defmodule NimbleTemplate.Addons.ReadmeTest do """ assert file =~ """ - * Start the Phoenix app + - Start the Phoenix app ```sh iex -S mix phx.server @@ -78,11 +78,11 @@ defmodule NimbleTemplate.Addons.ReadmeTest do Addons.Readme.apply(project) assert_file("README.md", fn file -> - assert file =~ "Erlang 23.3" - assert file =~ "Elixir 1.11.4" + assert file =~ "Erlang 24.2.2" + assert file =~ "Elixir 1.13.3" refute file =~ """ - * Install Node dependencies: + - Install Node dependencies: ```sh npm install --prefix assets @@ -90,7 +90,7 @@ defmodule NimbleTemplate.Addons.ReadmeTest do """ refute file =~ """ - * Start the Phoenix app + - Start the Phoenix app ```sh iex -S mix phx.server diff --git a/test/nimble_template/addons/test_env_test.exs b/test/nimble_template/addons/test_env_test.exs index 000b8550..df07a3ce 100644 --- a/test/nimble_template/addons/test_env_test.exs +++ b/test/nimble_template/addons/test_env_test.exs @@ -20,7 +20,25 @@ defmodule NimbleTemplate.Addons.TestEnvTest do assert file =~ """ defp aliases do [ - codebase: [\"deps.unlock --check-unused\", \"format --check-formatted\"], + codebase: [ + "deps.unlock --check-unused", + "format --check-formatted" + ], + """ + end) + end) + end + + test "adds codebase.fix alias", %{project: project, test_project_path: test_project_path} do + in_test_project(test_project_path, fn -> + Addons.TestEnv.apply(project) + + assert_file("mix.exs", fn file -> + assert file =~ """ + "codebase.fix": [ + "deps.clean --unlock --unused", + "format" + ], """ end) end) @@ -67,8 +85,8 @@ defmodule NimbleTemplate.Addons.TestEnvTest do Enum.each(["channel_case", "conn_case", "data_case"], fn support_case_name -> assert_file("test/support/" <> support_case_name <> ".ex", fn file -> assert file =~ "alias Ecto.Adapters.SQL.Sandbox" - assert file =~ "Sandbox.checkout(#{project.base_module}.Repo)" - assert file =~ "Sandbox.mode(#{project.base_module}.Repo, {:shared, self()})" + assert file =~ "Sandbox.start_owner!" + assert file =~ "Sandbox.stop_owner" end) end) end) @@ -78,7 +96,10 @@ defmodule NimbleTemplate.Addons.TestEnvTest do describe "#apply/2 with mix_project" do @describetag mix_project?: true - test "adds codebase alias", %{project: project, test_project_path: test_project_path} do + test "adds codebase and codebase.fix alias", %{ + project: project, + test_project_path: test_project_path + } do in_test_project(test_project_path, fn -> Addons.TestEnv.apply(project) @@ -88,7 +109,14 @@ defmodule NimbleTemplate.Addons.TestEnvTest do assert file =~ """ defp aliases do [ - codebase: [\"deps.unlock --check-unused\", \"format --check-formatted\"] + codebase: [ + "deps.unlock --check-unused", + "format --check-formatted" + ], + "codebase.fix": [ + "deps.clean --unlock --unused", + "format" + ] ] end """ diff --git a/test/nimble_template/addons/docker_test.exs b/test/nimble_template/addons/variants/docker_test.exs similarity index 72% rename from test/nimble_template/addons/docker_test.exs rename to test/nimble_template/addons/variants/docker_test.exs index 041b8d5a..f75a9b84 100644 --- a/test/nimble_template/addons/docker_test.exs +++ b/test/nimble_template/addons/variants/docker_test.exs @@ -1,4 +1,4 @@ -defmodule NimbleTemplate.Addons.DockerTest do +defmodule NimbleTemplate.Addons.Phoenix.DockerTest do use NimbleTemplate.AddonCase, async: false describe "#apply/2" do @@ -7,7 +7,7 @@ defmodule NimbleTemplate.Addons.DockerTest do test_project_path: test_project_path } do in_test_project(test_project_path, fn -> - Addons.Docker.apply(project) + PhoenixAddons.Docker.apply(project) assert_file("docker-compose.dev.yml", fn file -> assert file =~ """ @@ -32,7 +32,7 @@ defmodule NimbleTemplate.Addons.DockerTest do test_project_path: test_project_path } do in_test_project(test_project_path, fn -> - Addons.Docker.apply(project) + PhoenixAddons.Docker.apply(project) assert_file("docker-compose.yml") end) @@ -43,7 +43,7 @@ defmodule NimbleTemplate.Addons.DockerTest do test_project_path: test_project_path } do in_test_project(test_project_path, fn -> - Addons.Docker.apply(project) + PhoenixAddons.Docker.apply(project) assert_file(".dockerignore") end) @@ -54,25 +54,30 @@ defmodule NimbleTemplate.Addons.DockerTest do test_project_path: test_project_path } do in_test_project(test_project_path, fn -> - Addons.Docker.apply(project) + PhoenixAddons.Docker.apply(project) assert_file("Dockerfile", fn file -> assert file =~ """ - ARG ELIXIR_IMAGE_VERSION=1.11.4 - ARG ERLANG_IMAGE_VERSION=23.3 - ARG RELEASE_IMAGE_VERSION=3.13.2 + ARG ELIXIR_IMAGE_VERSION=1.13.3 + ARG ERLANG_IMAGE_VERSION=24.2.2 + ARG RELEASE_IMAGE_VERSION=3.15.0 FROM hexpm/elixir:${ELIXIR_IMAGE_VERSION}-erlang-${ERLANG_IMAGE_VERSION}-alpine-${RELEASE_IMAGE_VERSION} AS build """ assert file =~ "FROM alpine:${RELEASE_IMAGE_VERSION} AS app" + assert file =~ """ + RUN wget -q -O /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub + RUN wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.34-r0/glibc-2.34-r0.apk + RUN apk add glibc-2.34-r0.apk + """ + assert file =~ """ RUN cd assets && \\ \t\tnpm ci --progress=false --no-audit --loglevel=error && \\ - \t\tnpm run deploy && \\ \t\tcd - && \\ - \t\tmix phx.digest + \t\tmix assets.deploy """ assert file =~ "adduser -u 1000 -G appuser -g appuser -s /bin/sh -D appuser" @@ -89,7 +94,7 @@ defmodule NimbleTemplate.Addons.DockerTest do test_project_path: test_project_path } do in_test_project(test_project_path, fn -> - Addons.Docker.apply(project) + PhoenixAddons.Docker.apply(project) assert_file("bin/start.sh", fn file -> assert file =~ """ @@ -110,15 +115,20 @@ defmodule NimbleTemplate.Addons.DockerTest do project = %{project | api_project?: true, web_project?: false} in_test_project(test_project_path, fn -> - Addons.Docker.apply(project) + PhoenixAddons.Docker.apply(project) assert_file("Dockerfile", fn file -> + refute file =~ """ + RUN wget -q -O /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub + RUN wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.34-r0/glibc-2.34-r0.apk + RUN apk add glibc-2.34-r0.apk + """ + refute file =~ """ RUN cd assets && \\ \t\tnpm ci --progress=false --no-audit --loglevel=error && \\ - \t\tnpm run deploy && \\ \t\tcd - && \\ - \t\tmix phx.digest + \t\tmix assets.deploy """ end) end) diff --git a/test/nimble_template/addons/variants/ecto_data_migration_test.exs b/test/nimble_template/addons/variants/ecto_data_migration_test.exs new file mode 100644 index 00000000..e7e9e12d --- /dev/null +++ b/test/nimble_template/addons/variants/ecto_data_migration_test.exs @@ -0,0 +1,69 @@ +defmodule NimbleTemplate.Addons.Phoenix.EctoDataMigrationTest do + use NimbleTemplate.AddonCase, async: false + + describe "#apply/2" do + test "adds `ecto.migrate_all` into mix aliases", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + PhoenixAddons.EctoDataMigration.apply(project) + + assert_file("mix.exs", fn file -> + assert file =~ """ + "ecto.migrate_all": [ + "ecto.migrate --migrations-path=priv/repo/migrations --migrations-path=priv/repo/data_migrations" + ], + """ + end) + end) + end + + test "defines defp migrate", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + PhoenixAddons.EctoDataMigration.apply(project) + + assert_file("mix.exs", fn file -> + assert file =~ """ + defp migrate(_) do + if Mix.env() == :test do + Mix.Task.run("ecto.migrate", ["--quiet"]) + else + Mix.Task.run("ecto.migrate_all", []) + end + end + """ + end) + end) + end + + test "adjusts the `ecto.setup` alias by using `&migrate/1`", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + PhoenixAddons.EctoDataMigration.apply(project) + + assert_file("mix.exs", fn file -> + assert file =~ """ + "ecto.setup": ["ecto.create", &migrate/1, "run priv/repo/seeds.exs"], + """ + end) + end) + end + + test "creates the data_migrations directory", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + PhoenixAddons.EctoDataMigration.apply(project) + + assert_file("priv/repo/data_migrations/.keep") + end) + end + end +end diff --git a/test/nimble_template/addons/ex_machina_test.exs b/test/nimble_template/addons/variants/ex_machina_test.exs similarity index 85% rename from test/nimble_template/addons/ex_machina_test.exs rename to test/nimble_template/addons/variants/ex_machina_test.exs index 2da75a0f..fef60c92 100644 --- a/test/nimble_template/addons/ex_machina_test.exs +++ b/test/nimble_template/addons/variants/ex_machina_test.exs @@ -1,4 +1,4 @@ -defmodule NimbleTemplate.Addons.ExMachinaTest do +defmodule NimbleTemplate.Addons.Phoenix.ExMachinaTest do use NimbleTemplate.AddonCase, async: false describe "#apply/2" do @@ -9,7 +9,7 @@ defmodule NimbleTemplate.Addons.ExMachinaTest do test_project_path: test_project_path } do in_test_project(test_project_path, fn -> - Addons.ExMachina.apply(project) + PhoenixAddons.ExMachina.apply(project) assert_file("test/support/factory.ex") end) @@ -20,13 +20,13 @@ defmodule NimbleTemplate.Addons.ExMachinaTest do test_project_path: test_project_path } do in_test_project(test_project_path, fn -> - Addons.ExMachina.apply(project) + PhoenixAddons.ExMachina.apply(project) assert_file("mix.exs", fn file -> assert file =~ """ defp deps do [ - {:ex_machina, \"~> 2.4\", [only: :test]}, + {:ex_machina, "~> 2.4", [only: :test]}, """ end) end) @@ -37,7 +37,7 @@ defmodule NimbleTemplate.Addons.ExMachinaTest do test_project_path: test_project_path } do in_test_project(test_project_path, fn -> - Addons.ExMachina.apply(project) + PhoenixAddons.ExMachina.apply(project) assert_file("mix.exs", fn file -> assert file =~ """ @@ -49,7 +49,7 @@ defmodule NimbleTemplate.Addons.ExMachinaTest do test "updates test/test_helper.exs", %{project: project, test_project_path: test_project_path} do in_test_project(test_project_path, fn -> - Addons.ExMachina.apply(project) + PhoenixAddons.ExMachina.apply(project) assert_file("test/test_helper.exs", fn file -> assert file =~ """ @@ -63,7 +63,7 @@ defmodule NimbleTemplate.Addons.ExMachinaTest do test "adds Factory module", %{project: project, test_project_path: test_project_path} do in_test_project(test_project_path, fn -> - Addons.ExMachina.apply(project) + PhoenixAddons.ExMachina.apply(project) assert_file("test/support/data_case.ex", fn file -> assert file =~ "import NimbleTemplate.Factory" diff --git a/test/nimble_template/addons/ex_vcr_test.exs b/test/nimble_template/addons/variants/ex_vcr_test.exs similarity index 78% rename from test/nimble_template/addons/ex_vcr_test.exs rename to test/nimble_template/addons/variants/ex_vcr_test.exs index cfe7f8fe..bbce6362 100644 --- a/test/nimble_template/addons/ex_vcr_test.exs +++ b/test/nimble_template/addons/variants/ex_vcr_test.exs @@ -1,4 +1,4 @@ -defmodule NimbleTemplate.Addons.ExVCRTest do +defmodule NimbleTemplate.Addons.Phoenix.ExVCRTest do use NimbleTemplate.AddonCase describe "#apply/2" do @@ -9,7 +9,7 @@ defmodule NimbleTemplate.Addons.ExVCRTest do test_project_path: test_project_path } do in_test_project(test_project_path, fn -> - Addons.ExVCR.apply(project) + PhoenixAddons.ExVCR.apply(project) assert_file("mix.exs", fn file -> assert file =~ "{:exvcr, \"~> 0.12.2\", [only: :test]}" @@ -22,13 +22,14 @@ defmodule NimbleTemplate.Addons.ExVCRTest do test_project_path: test_project_path } do in_test_project(test_project_path, fn -> - Addons.ExVCR.apply(project) + PhoenixAddons.ExVCR.apply(project) assert_file("config/test.exs", fn file -> assert file =~ """ # Configurations for ExVCR config :exvcr, - vcr_cassette_library_dir: "test/support/fixtures/vcr_cassettes" + vcr_cassette_library_dir: "test/support/fixtures/vcr_cassettes", + ignore_localhost: true """ end) end) @@ -36,7 +37,11 @@ defmodule NimbleTemplate.Addons.ExVCRTest do test "updates test cases", %{project: project, test_project_path: test_project_path} do in_test_project(test_project_path, fn -> - Addons.ExVCR.apply(project) + PhoenixAddons.ExVCR.apply(project) + + assert_file("test/support/channel_case.ex", fn file -> + assert file =~ "use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney" + end) assert_file("test/support/data_case.ex", fn file -> assert file =~ "use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney" @@ -53,7 +58,7 @@ defmodule NimbleTemplate.Addons.ExVCRTest do test_project_path: test_project_path } do in_test_project(test_project_path, fn -> - Addons.ExVCR.apply(project) + PhoenixAddons.ExVCR.apply(project) assert(File.exists?("test/support/fixtures/vcr_cassettes/.keep")) == true end) diff --git a/test/nimble_template/addons/variants/gettext_test.exs b/test/nimble_template/addons/variants/gettext_test.exs new file mode 100644 index 00000000..e1743aa3 --- /dev/null +++ b/test/nimble_template/addons/variants/gettext_test.exs @@ -0,0 +1,22 @@ +defmodule NimbleTemplate.Addons.Phoenix.GettextTest do + use NimbleTemplate.AddonCase, async: false + + describe "#apply/2" do + @describetag required_addons: [:TestEnv] + + test "injects gettext.extract-and-merge command to mix aliases", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + PhoenixAddons.Gettext.apply(project) + + assert_file("mix.exs", fn file -> + assert file =~ """ + "gettext.extract-and-merge": ["gettext.extract --merge --no-fuzzy"], + """ + end) + end) + end + end +end diff --git a/test/nimble_template/addons/variants/health_plug_test.exs b/test/nimble_template/addons/variants/health_plug_test.exs new file mode 100644 index 00000000..97a219ef --- /dev/null +++ b/test/nimble_template/addons/variants/health_plug_test.exs @@ -0,0 +1,82 @@ +defmodule NimbleTemplate.Addons.Phoenix.HealthPlugTest do + use NimbleTemplate.AddonCase, async: false + + describe "#apply/2" do + @describetag required_addons: [:ExCoveralls, :"Phoenix.MixRelease"] + + test "copies the health plug file", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + PhoenixAddons.HealthPlug.apply(project) + + assert_file("lib/nimble_template_web/plugs/health_plug.ex") + end) + end + + test "copies the health plug test files", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + PhoenixAddons.HealthPlug.apply(project) + + assert_file("test/nimble_template_web/plugs/health_plug_test.exs") + assert_file("test/nimble_template_web/requests/_health/liveness_request_test.exs") + assert_file("test/nimble_template_web/requests/_health/rediness_request_test.exs") + end) + end + + test "adds health path configuration in config", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + PhoenixAddons.HealthPlug.apply(project) + + assert_file("config/config.exs", fn file -> + assert file =~ """ + config :nimble_template, NimbleTemplateWeb.Endpoint, + health_path: "/_health", + """ + end) + + assert_file("config/runtime.exs", fn file -> + assert file =~ """ + config :nimble_template, NimbleTemplateWeb.Endpoint, + health_path: System.fetch_env!("HEALTH_PATH"), + """ + end) + end) + end + + test "adds forward health path in router", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + PhoenixAddons.HealthPlug.apply(project) + + assert_file("lib/nimble_template_web/router.ex", fn file -> + assert file =~ """ + forward Application.get_env(:nimble_template, NimbleTemplateWeb.Endpoint)[:health_path], NimbleTemplateWeb.HealthPlug + """ + end) + end) + end + + test "adds `Mimic.copy(Ecto.Adapters.SQL)` to test_helper", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + PhoenixAddons.HealthPlug.apply(project) + + assert_file("test/test_helper.exs", fn file -> + assert file =~ "Mimic.copy(Ecto.Adapters.SQL)" + end) + end) + end + end +end diff --git a/test/nimble_template/addons/makefile_test.exs b/test/nimble_template/addons/variants/makefile_test.exs similarity index 72% rename from test/nimble_template/addons/makefile_test.exs rename to test/nimble_template/addons/variants/makefile_test.exs index 7ce8b7a0..10fe5749 100644 --- a/test/nimble_template/addons/makefile_test.exs +++ b/test/nimble_template/addons/variants/makefile_test.exs @@ -1,10 +1,10 @@ -defmodule NimbleTemplate.Addons.MakefileTest do +defmodule NimbleTemplate.Addons.Phoenix.MakefileTest do use NimbleTemplate.AddonCase, async: false describe "#apply/2" do test "copies the Makefile", %{project: project, test_project_path: test_project_path} do in_test_project(test_project_path, fn -> - Addons.Makefile.apply(project) + PhoenixAddons.Makefile.apply(project) assert_file("Makefile") end) diff --git a/test/nimble_template/addons/variants/mix_release_test.exs b/test/nimble_template/addons/variants/mix_release_test.exs new file mode 100644 index 00000000..6e0e6722 --- /dev/null +++ b/test/nimble_template/addons/variants/mix_release_test.exs @@ -0,0 +1,82 @@ +defmodule NimbleTemplate.Addons.Phoenix.MixReleaseTest do + use NimbleTemplate.AddonCase, async: false + + describe "#apply/2" do + test "creates the lib/nimble_template/release_tasks.ex", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + PhoenixAddons.MixRelease.apply(project) + + assert_file("lib/nimble_template/release_tasks.ex", fn file -> + assert file =~ """ + defmodule NimbleTemplate.ReleaseTasks do + @app :nimble_template + + def migrate do + load_app() + + for repo <- repos() do + schema_migrations = Ecto.Migrator.migrations_path(repo, "migrations") + data_migrations = Ecto.Migrator.migrations_path(repo, "data_migrations") + + {:ok, _, _} = + Ecto.Migrator.with_repo( + repo, + &Ecto.Migrator.run(&1, [schema_migrations, data_migrations], :up, all: true) + ) + end + end + """ + end) + end) + end + + test "adjusts the config/runtime.exs", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + PhoenixAddons.MixRelease.apply(project) + + assert_file("config/runtime.exs", fn file -> + assert file =~ """ + config :nimble_template, NimbleTemplateWeb.Endpoint, + server: true, + """ + + assert file =~ """ + host = + System.get_env("PHX_HOST") || + raise \"\"\" + Environment variable PHX_HOST is missing. + Set the Heroku endpoint to this variable. + \"\"\" + """ + + refute file =~ """ + + # ## Using releases + # + # If you are doing OTP releases, you need to instruct Phoenix + # to start each relevant endpoint: + # + # config :nimble_template, NimbleTemplateWeb.Endpoint, server: true + # + # Then you can assemble a release by calling `mix release`. + # See `mix help release` for more information. + """ + + refute file =~ """ + # Start the phoenix server if environment is set and running in a release + if System.get_env("PHX_SERVER") && System.get_env("RELEASE_NAME") do + config :nimble_template, NimbleTemplateWeb.Endpoint, server: true + end + + """ + end) + end) + end + end +end diff --git a/test/nimble_template/addons/oban_test.exs b/test/nimble_template/addons/variants/oban_test.exs similarity index 85% rename from test/nimble_template/addons/oban_test.exs rename to test/nimble_template/addons/variants/oban_test.exs index 10b091b0..493d4668 100644 --- a/test/nimble_template/addons/oban_test.exs +++ b/test/nimble_template/addons/variants/oban_test.exs @@ -1,22 +1,22 @@ -defmodule NimbleTemplate.Addons.ObanTest do +defmodule NimbleTemplate.Addons.Phoenix.ObanTest do use NimbleTemplate.AddonCase, async: false use Mimic describe "#apply/2" do @describetag mock_latest_package_versions: [{:oban, "2.3"}] - test "injects credo to mix dependency", %{ + test "injects oban to mix dependency", %{ project: project, test_project_path: test_project_path } do in_test_project(test_project_path, fn -> - Addons.Oban.apply(project) + PhoenixAddons.Oban.apply(project) assert_file("mix.exs", fn file -> assert file =~ """ defp deps do [ - {:oban, \"~> 2.3\"}, + {:oban, "~> 2.3"}, """ end) end) @@ -29,7 +29,7 @@ defmodule NimbleTemplate.Addons.ObanTest do in_test_project(test_project_path, fn -> expect(Calendar, :strftime, fn _datetime, _format -> "20201120074154" end) - Addons.Oban.apply(project) + PhoenixAddons.Oban.apply(project) assert_file("priv/repo/migrations/20201120074154_add_oban_jobs_table.exs", fn file -> assert file =~ """ @@ -56,7 +56,7 @@ defmodule NimbleTemplate.Addons.ObanTest do test_project_path: test_project_path } do in_test_project(test_project_path, fn -> - Addons.Oban.apply(project) + PhoenixAddons.Oban.apply(project) assert_file("lib/nimble_template/application.ex", fn file -> assert file =~ """ @@ -78,7 +78,7 @@ defmodule NimbleTemplate.Addons.ObanTest do test_project_path: test_project_path } do in_test_project(test_project_path, fn -> - Addons.Oban.apply(project) + PhoenixAddons.Oban.apply(project) assert_file("config/config.exs", fn file -> assert file =~ """ @@ -96,7 +96,7 @@ defmodule NimbleTemplate.Addons.ObanTest do test_project_path: test_project_path } do in_test_project(test_project_path, fn -> - Addons.Oban.apply(project) + PhoenixAddons.Oban.apply(project) assert_file("config/test.exs", fn file -> assert file =~ """ @@ -106,14 +106,14 @@ defmodule NimbleTemplate.Addons.ObanTest do end) end - test "creates the worker folder", %{ + test "creates the worker folder with .keep file", %{ project: project, test_project_path: test_project_path } do in_test_project(test_project_path, fn -> - Addons.Oban.apply(project) + PhoenixAddons.Oban.apply(project) - assert(File.dir?("lib/nimble_template_worker")) == true + assert(File.exists?("lib/nimble_template_worker/.keep")) == true end) end end diff --git a/test/nimble_template/addons/variants/phoenix/api/config_test.exs b/test/nimble_template/addons/variants/phoenix/api/config_test.exs new file mode 100644 index 00000000..9d0a1c81 --- /dev/null +++ b/test/nimble_template/addons/variants/phoenix/api/config_test.exs @@ -0,0 +1,18 @@ +defmodule NimbleTemplate.Addons.Phoenix.Api.ConfigTest do + use NimbleTemplate.AddonCase, async: false + + describe "#apply/2" do + test "removes cache_static_manifest setting in config/prod.exs", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + ApiAddons.Config.apply(project) + + assert_file("config/prod.exs", fn file -> + refute file =~ "cache_static_manifest: \"priv/static/cache_manifest.json\"" + end) + end) + end + end +end diff --git a/test/nimble_template/addons/variants/phoenix/api/empty_body_plug_test.exs b/test/nimble_template/addons/variants/phoenix/api/empty_body_plug_test.exs new file mode 100644 index 00000000..6e4cddb1 --- /dev/null +++ b/test/nimble_template/addons/variants/phoenix/api/empty_body_plug_test.exs @@ -0,0 +1,45 @@ +defmodule NimbleTemplate.Addons.Phoenix.Api.EmptyBodyParamsPlugTest do + use NimbleTemplate.AddonCase, async: false + + describe "#apply/2" do + test "copies the empty body plug file", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + ApiAddons.EmptyBodyParamsPlug.apply(project) + + assert_file("lib/nimble_template_web/plugs/check_empty_body_params_plug.ex") + end) + end + + test "copies the empty body plug test file", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + ApiAddons.EmptyBodyParamsPlug.apply(project) + + assert_file("test/nimble_template_web/plugs/check_empty_body_params_plug_test.exs") + end) + end + + test "adds CheckEmptyBodyParamsPlug into the api pipeline", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + ApiAddons.EmptyBodyParamsPlug.apply(project) + + assert_file("lib/nimble_template_web/router.ex", fn file -> + assert file =~ """ + pipeline :api do + plug :accepts, ["json"] + plug NimbleTemplateWeb.CheckEmptyBodyParamsPlug + end + """ + end) + end) + end + end +end diff --git a/test/nimble_template/addons/variants/phoenix/api/error_view_test.exs b/test/nimble_template/addons/variants/phoenix/api/error_view_test.exs new file mode 100644 index 00000000..c5f7b302 --- /dev/null +++ b/test/nimble_template/addons/variants/phoenix/api/error_view_test.exs @@ -0,0 +1,20 @@ +defmodule NimbleTemplate.Addons.Phoenix.Api.ErrorViewTest do + use NimbleTemplate.AddonCase, async: false + + describe "#apply/2" do + test "copies the error view files", %{ + project: project, + test_project_path: project_path + } do + in_test_project(project_path, fn -> + ApiAddons.ErrorView.apply(project) + + assert_file("lib/nimble_template_web/views/error_helpers.ex") + assert_file("lib/nimble_template_web/views/api/error_view.ex") + assert_file("test/nimble_template_web/views/error_helpers_test.exs") + assert_file("test/nimble_template_web/views/api/error_view_test.exs") + assert_file("test/support/view_case.ex") + end) + end + end +end diff --git a/test/nimble_template/addons/variants/phoenix/api/fallback_controller.exs b/test/nimble_template/addons/variants/phoenix/api/fallback_controller.exs new file mode 100644 index 00000000..a6afe5a0 --- /dev/null +++ b/test/nimble_template/addons/variants/phoenix/api/fallback_controller.exs @@ -0,0 +1,31 @@ +defmodule NimbleTemplate.Addons.Phoenix.Api.FallbackControllerTest do + use NimbleTemplate.AddonCase, async: false + + describe "#apply/2" do + @describetag required_addons: [:"Phoenix.Api.ParamsValidation"] + + test "copies the FallbackController module", %{ + project: project, + test_project_path: project_path + } do + in_test_project(project_path, fn -> + ApiAddons.FallbackController.apply(project) + + assert_file("lib/nimble_template_web/controller/api/fallback_controller.ex") + end) + end + + test "adds FallbackController alias into the web entry_point", %{ + project: project, + test_project_path: project_path + } do + in_test_project(project_path, fn -> + ApiAddons.FallbackController.apply(project) + + assert_file("lib/nimble_template_web.ex", fn file -> + assert file =~ "action_fallback NimbleTemplateWeb.Api.FallbackController" + end) + end) + end + end +end diff --git a/test/nimble_template/addons/variants/phoenix/api/json_api_test.exs b/test/nimble_template/addons/variants/phoenix/api/json_api_test.exs new file mode 100644 index 00000000..a0afd9c8 --- /dev/null +++ b/test/nimble_template/addons/variants/phoenix/api/json_api_test.exs @@ -0,0 +1,33 @@ +defmodule NimbleTemplate.Addons.Phoenix.Api.JsonApiTest do + use NimbleTemplate.AddonCase, async: false + + describe "#apply/2" do + @describetag mock_latest_package_versions: [{:jsonapi, "1.3.0"}] + + test "injects jsonapi to mix dependency", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + ApiAddons.JsonApi.apply(project) + + assert_file("mix.exs", fn file -> + assert file =~ "{:jsonapi, \"~> 1.3.0\"}" + end) + end) + end + + test "adds config for jsonapi in config/config.exs", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + ApiAddons.JsonApi.apply(project) + + assert_file("config/config.exs", fn file -> + assert file =~ "config :jsonapi, remove_links: true" + end) + end) + end + end +end diff --git a/test/nimble_template/addons/variants/phoenix/api/params_validation_test.exs b/test/nimble_template/addons/variants/phoenix/api/params_validation_test.exs new file mode 100644 index 00000000..6e45b1ad --- /dev/null +++ b/test/nimble_template/addons/variants/phoenix/api/params_validation_test.exs @@ -0,0 +1,31 @@ +defmodule NimbleTemplate.Addons.Phoenix.Api.ParamsValidationTest do + use NimbleTemplate.AddonCase, async: false + + describe "#apply/2" do + test "copies the params validation module and test files", %{ + project: project, + test_project_path: project_path + } do + in_test_project(project_path, fn -> + ApiAddons.ParamsValidation.apply(project) + + assert_file("lib/nimble_template_web/params/params.ex") + assert_file("lib/nimble_template_web/params/params_validator.ex") + assert_file("test/nimble_template_web/params/params_validator_test.exs") + end) + end + + test "adds ParamsValidator alias into the web entry_point", %{ + project: project, + test_project_path: project_path + } do + in_test_project(project_path, fn -> + ApiAddons.ParamsValidation.apply(project) + + assert_file("lib/nimble_template_web.ex", fn file -> + assert file =~ "alias NimbleTemplateWeb.ParamsValidator" + end) + end) + end + end +end diff --git a/test/nimble_template/addons/variants/phoenix/web/assets_test.exs b/test/nimble_template/addons/variants/phoenix/web/assets_test.exs index 96432b75..0bcb25a6 100644 --- a/test/nimble_template/addons/variants/phoenix/web/assets_test.exs +++ b/test/nimble_template/addons/variants/phoenix/web/assets_test.exs @@ -2,48 +2,15 @@ defmodule NimbleTemplate.Addons.Phoenix.Web.AssetsTest do use NimbleTemplate.AddonCase, async: false describe "#apply/2" do - test "adds assets.compile alias", %{project: project, test_project_path: test_project_path} do - in_test_project(test_project_path, fn -> - AddonsWeb.Assets.apply(project) - - assert_file("mix.exs", fn file -> - assert file =~ """ - defp aliases do - [ - \"assets.compile\": &compile_assets/1, - """ - end) - end) - end - - test "defines compile_assets method", %{ - project: project, - test_project_path: test_project_path - } do - in_test_project(test_project_path, fn -> - AddonsWeb.Assets.apply(project) - - assert_file("mix.exs", fn file -> - assert file =~ """ - defp compile_assets(_) do - Mix.shell().cmd("npm run --prefix assets build:dev", quiet: true) - end - """ - end) - end) - end - - test "adds build:dev script into package.json", %{ + test "enables gzip for static", %{ project: project, test_project_path: test_project_path } do in_test_project(test_project_path, fn -> - AddonsWeb.Assets.apply(project) + WebAddons.Assets.apply(project) - assert_file("assets/package.json", fn file -> - assert file =~ """ - "build:dev": "webpack --mode development" - """ + assert_file("lib/nimble_template_web/endpoint.ex", fn file -> + assert file =~ "gzip: true," end) end) end diff --git a/test/nimble_template/addons/variants/phoenix/web/bootstrap_test.exs b/test/nimble_template/addons/variants/phoenix/web/bootstrap_test.exs new file mode 100644 index 00000000..639bd98f --- /dev/null +++ b/test/nimble_template/addons/variants/phoenix/web/bootstrap_test.exs @@ -0,0 +1,243 @@ +defmodule NimbleTemplate.Addons.Phoenix.Web.BootstrapTest do + use NimbleTemplate.AddonCase, async: false + + describe "#apply/2 given no Nimble CSS and Nimble JS structure" do + @describetag required_addons: [ + :TestEnv, + :"Phoenix.Web.NodePackage", + :"Phoenix.Web.DartSass" + ] + + test "copies Bootstrap vendor file", %{project: project, test_project_path: test_project_path} do + in_test_project(test_project_path, fn -> + WebAddons.Bootstrap.apply(project, %{ + with_nimble_css_addon: false, + with_nimble_js_addon: false + }) + + assert_file("assets/css/vendor/_bootstrap.scss") + end) + end + + test "adds Bootstrap into package.json", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.Bootstrap.apply(project, %{ + with_nimble_css_addon: false, + with_nimble_js_addon: false + }) + + assert_file("assets/package.json", fn file -> + assert file =~ """ + "dependencies": { + "@popperjs/core": "^2.11.2", + "bootstrap": "5.1.3", + """ + end) + end) + end + + test "imports Bootstrap into app.js given", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.Bootstrap.apply(project, %{ + with_nimble_js_addon: false, + with_nimble_css_addon: false + }) + + assert_file("assets/js/app.js", fn file -> + assert file =~ """ + import "bootstrap/dist/js/bootstrap"; + """ + end) + end) + end + + test "imports Vendor into app.scss", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.Bootstrap.apply(project, %{ + with_nimble_css_addon: false, + with_nimble_js_addon: false + }) + + assert_file("assets/css/app.scss", fn file -> + assert file =~ """ + @import "./phoenix.css"; + + @import './vendor/'; + """ + end) + end) + end + + test "imports bootstrap vendor index file", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.Bootstrap.apply(project, %{ + with_nimble_css_addon: false, + with_nimble_js_addon: false + }) + + assert_file("assets/css/vendor/_index.scss", fn file -> + assert file =~ "@import './bootstrap';" + end) + end) + end + + test "creates the assets/css/_variables.scss", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.Bootstrap.apply(project, %{ + with_nimble_css_addon: false, + with_nimble_js_addon: false + }) + + assert_file("assets/css/_variables.scss", fn file -> + assert file =~ """ + //////////////////////////////// + // Shared variables // + //////////////////////////////// + + + //////////////////////////////// + // Custom Bootstrap variables // + //////////////////////////////// + """ + end) + end) + end + end + + describe "#apply/2 given Nimble CSS and Nimble JS structure" do + @describetag required_addons: [ + :TestEnv, + :"Phoenix.Web.NodePackage", + :"Phoenix.Web.StyleLint", + :"Phoenix.Web.EsLint", + :"Phoenix.Web.DartSass", + :"Phoenix.Web.NimbleCSS", + :"Phoenix.Web.NimbleJS" + ] + + test "copies Bootstrap vendor file", %{project: project, test_project_path: test_project_path} do + in_test_project(test_project_path, fn -> + WebAddons.Bootstrap.apply(project, %{ + with_nimble_css_addon: true, + with_nimble_js_addon: true + }) + + assert_file("assets/css/vendor/_bootstrap.scss") + end) + end + + test "adds Bootstrap into package.json", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.Bootstrap.apply(project, %{ + with_nimble_css_addon: true, + with_nimble_js_addon: true + }) + + assert_file("assets/package.json", fn file -> + assert file =~ """ + "dependencies": { + "@popperjs/core": "^2.11.2", + "bootstrap": "5.1.3", + """ + end) + end) + end + + test "imports Bootstrap into app.js", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.Bootstrap.apply(project, %{ + with_nimble_css_addon: true, + with_nimble_js_addon: true + }) + + assert_file("assets/js/app.js", fn file -> + assert file =~ """ + import "bootstrap/dist/js/bootstrap"; + """ + end) + end) + end + + test "does not import Vendor into app.scss", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.Bootstrap.apply(project, %{ + with_nimble_css_addon: true, + with_nimble_js_addon: true + }) + + assert_file("assets/css/app.scss", fn file -> + assert file =~ """ + @import './functions'; + + @import './vendor'; + """ + end) + end) + end + + test "imports bootstrap vendor index file", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.Bootstrap.apply(project, %{ + with_nimble_css_addon: true, + with_nimble_js_addon: true + }) + + assert_file("assets/css/vendor/_index.scss", fn file -> + assert file =~ "@import './bootstrap';" + end) + end) + end + + test "updates the assets/css/_variables.scss", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.Bootstrap.apply(project, %{ + with_nimble_css_addon: true, + with_nimble_js_addon: true + }) + + assert_file("assets/css/_variables.scss", fn file -> + assert file =~ """ + //////////////////////////////// + // Shared variables // + //////////////////////////////// + + + //////////////////////////////// + // Custom Bootstrap variables // + //////////////////////////////// + """ + end) + end) + end + end +end diff --git a/test/nimble_template/addons/variants/phoenix/web/core_js_test.exs b/test/nimble_template/addons/variants/phoenix/web/core_js_test.exs index c6e4b466..b46972a7 100644 --- a/test/nimble_template/addons/variants/phoenix/web/core_js_test.exs +++ b/test/nimble_template/addons/variants/phoenix/web/core_js_test.exs @@ -2,16 +2,19 @@ defmodule NimbleTemplate.Addons.Phoenix.Web.CoreJSTest do use NimbleTemplate.AddonCase, async: false describe "#apply/2" do + @describetag required_addons: [:"Phoenix.Web.NodePackage"] + test "adds core-js into package.json", %{ project: project, test_project_path: test_project_path } do in_test_project(test_project_path, fn -> - AddonsWeb.CoreJS.apply(project) + WebAddons.CoreJS.apply(project) assert_file("assets/package.json", fn file -> assert file =~ """ - "core-js": "^3.7.0" + "dependencies": { + "core-js": "^3.21.1" """ end) end) @@ -19,13 +22,33 @@ defmodule NimbleTemplate.Addons.Phoenix.Web.CoreJSTest do test "imports core-js into app.js", %{project: project, test_project_path: test_project_path} do in_test_project(test_project_path, fn -> - AddonsWeb.CoreJS.apply(project) + WebAddons.CoreJS.apply(project) assert_file("assets/js/app.js", fn file -> assert file =~ """ // CoreJS import "core-js/stable" - import "regenerator-runtime/runtime" + """ + end) + end) + end + end + + describe "#apply/2 to a Live project" do + @describetag live_project?: true + @describetag required_addons: [:"Phoenix.Web.NodePackage"] + + test "adds core-js into package.json", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.CoreJS.apply(project) + + assert_file("assets/package.json", fn file -> + assert file =~ """ + "dependencies": { + "core-js": "^3.21.1" """ end) end) diff --git a/test/nimble_template/addons/variants/phoenix/web/dart_sass_test.exs b/test/nimble_template/addons/variants/phoenix/web/dart_sass_test.exs new file mode 100644 index 00000000..4fc97ab4 --- /dev/null +++ b/test/nimble_template/addons/variants/phoenix/web/dart_sass_test.exs @@ -0,0 +1,118 @@ +defmodule NimbleTemplate.Addons.Phoenix.Web.DartSassTest do + use NimbleTemplate.AddonCase, async: false + + describe "#apply/2" do + @describetag mock_latest_package_versions: [{:dart_sass, "0.26.2"}] + + test "remove the import `css/app.css` in assets/js/app.js", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.DartSass.apply(project) + + assert_file("assets/js/app.js", fn file -> + refute file =~ """ + // We import the CSS which is extracted to its own file by esbuild. + // Remove this line if you add a your own CSS build pipeline (e.g postcss). + import "../css/app.css" + + """ + end) + end) + end + + test "adds sass step into the mix assets.deploy alias", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.DartSass.apply(project) + + assert_file("mix.exs", fn file -> + assert file =~ """ + "assets.deploy": [ + "esbuild default --minify", + "sass default --no-source-map --style=compressed", + "phx.digest" + ] + """ + end) + end) + end + + test "adds dart_sass configuration into the config.exs", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.DartSass.apply(project) + + assert_file("config/config.exs", fn file -> + assert file =~ """ + # Configure dart_sass (the version is required) + config :dart_sass, + version: "1.49.8", + default: [ + args: ~w( + --load-path=./node_modules + css/app.scss + ../priv/static/assets/app.css + ), + cd: Path.expand("../assets", __DIR__) + ] + """ + end) + end) + end + + test "adds sass into the development watcher", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.DartSass.apply(project) + + assert_file("config/dev.exs", fn file -> + assert file =~ """ + watchers: [ + sass: { + DartSass, + :install_and_run, + [:default, ~w(--embed-source-map --source-map-urls=absolute --watch)] + }, + """ + end) + end) + end + + test "injects dart_sass to mix dependency", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.DartSass.apply(project) + + assert_file("mix.exs", fn file -> + assert file =~ """ + defp deps do + [ + {:dart_sass, "~> 0.26.2", [runtime: Mix.env() == :dev]}, + """ + end) + end) + end + + test "rename app.css into app.scss", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.DartSass.apply(project) + + assert_file("assets/css/app.scss") + refute_file("assets/css/app.css") + end) + end + end +end diff --git a/test/nimble_template/addons/variants/phoenix/web/es_lint_test.exs b/test/nimble_template/addons/variants/phoenix/web/es_lint_test.exs new file mode 100644 index 00000000..080c6e47 --- /dev/null +++ b/test/nimble_template/addons/variants/phoenix/web/es_lint_test.exs @@ -0,0 +1,107 @@ +defmodule NimbleTemplate.Addons.Phoenix.Web.EsLintTest do + use NimbleTemplate.AddonCase, async: false + + describe "#apply/2" do + @describetag required_addons: [:TestEnv, :"Phoenix.Web.NodePackage"] + + test "adds eslint, eslint-config-prettier and eslint-plugin-prettier into package.json", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.EsLint.apply(project) + + assert_file("assets/package.json", fn file -> + assert file =~ """ + "devDependencies": { + "eslint": "^8.10.0", + "eslint-config-prettier": "^8.4.0", + "eslint-plugin-prettier": "^4.0.0", + """ + end) + end) + end + + test "adds eslint and eslint.fix into package.json", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.EsLint.apply(project) + + assert_file("assets/package.json", fn file -> + assert file =~ """ + "scripts": { + "eslint": "eslint --color ./", + "eslint.fix": "eslint --color --fix ./" + """ + end) + end) + end + + test "adds eslint into the codebase alias", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.EsLint.apply(project) + + assert_file("mix.exs", fn file -> + assert file =~ """ + codebase: [ + "cmd npm run eslint --prefix assets", + """ + end) + end) + end + + test "adds eslint.fix into the codebase.fix alias", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.EsLint.apply(project) + + assert_file("mix.exs", fn file -> + assert file =~ """ + "codebase.fix": [ + "cmd npm run eslint.fix --prefix assets", + """ + end) + end) + end + + test "copies the .eslintrc.json", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.EsLint.apply(project) + + assert_file("assets/.eslintrc.json") + end) + end + end + + describe "#apply/2 to a Live project" do + @describetag live_project?: true + @describetag required_addons: [:TestEnv, :"Phoenix.Web.NodePackage"] + + test "updates the assets/js/app.js", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.EsLint.apply(project) + + assert_file("assets/js/app.js", fn file -> + assert file =~ + "window.addEventListener(\"phx:page-loading-start\", _info => topbar.show())" + + assert file =~ + "window.addEventListener(\"phx:page-loading-stop\", _info => topbar.hide())" + end) + end) + end + end +end diff --git a/test/nimble_template/addons/variants/phoenix/web/nimble_css_test.exs b/test/nimble_template/addons/variants/phoenix/web/nimble_css_test.exs new file mode 100644 index 00000000..5658e0f9 --- /dev/null +++ b/test/nimble_template/addons/variants/phoenix/web/nimble_css_test.exs @@ -0,0 +1,53 @@ +defmodule NimbleTemplate.Addons.Phoenix.Web.NimbleCSSTest do + use NimbleTemplate.AddonCase, async: false + + describe "#apply/2" do + @describetag required_addons: [:TestEnv, :"Phoenix.Web.NodePackage", :"Phoenix.Web.StyleLint"] + + test "copies Nimble CSS structure", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.NimbleCSS.apply(project) + + assert_directory("assets/css/base") + assert_directory("assets/css/components") + assert_directory("assets/css/functions") + assert_directory("assets/css/layouts") + assert_directory("assets/css/mixins") + assert_directory("assets/css/screens") + assert_directory("assets/css/vendor") + + assert_file("assets/css/_variables.scss") + assert_file("assets/css/app.scss") + end) + end + + test "removes the default Phoenix styles", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.NimbleCSS.apply(project) + + refute_file("assets/css/app.css") + refute_file("assets/css/phoenix.css") + end) + end + + test "removes `css/app.css` and `css/phoenix.css` in assets/.stylelintrc.json", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.NimbleCSS.apply(project) + + assert_file("assets/.stylelintrc.json", fn file -> + refute file =~ "css/app.css" + refute file =~ "css/phoenix.css" + end) + end) + end + end +end diff --git a/test/nimble_template/addons/variants/phoenix/web/nimble_js_test.exs b/test/nimble_template/addons/variants/phoenix/web/nimble_js_test.exs new file mode 100644 index 00000000..9c72b201 --- /dev/null +++ b/test/nimble_template/addons/variants/phoenix/web/nimble_js_test.exs @@ -0,0 +1,82 @@ +defmodule NimbleTemplate.Addons.Phoenix.Web.NimbleJSTest do + use NimbleTemplate.AddonCase, async: false + + describe "#apply/2" do + @describetag required_addons: [:TestEnv, :"Phoenix.Web.NodePackage", :"Phoenix.Web.EsLint"] + + test "copies Nimble JS structure", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.NimbleJS.apply(project) + + assert_directory("assets/js/adapters") + assert_directory("assets/js/components") + assert_directory("assets/js/config") + assert_directory("assets/js/helpers") + assert_directory("assets/js/initializers") + assert_directory("assets/js/lib") + assert_directory("assets/js/screens") + + assert_file("assets/js/app.js") + end) + end + + test "updates assets/js/app.js", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.NimbleJS.apply(project) + + assert_file("assets/js/app.js", fn file -> + assert file =~ """ + + // Application + import "./initializers/"; + + import "./screens/"; + """ + + assert file =~ "\"./vendor/topbar\"" + assert file =~ "\"./vendor/some-package.js\"" + assert file =~ "assets/js/vendor" + + refute file =~ "\"../vendor/topbar\"" + refute file =~ "\"../vendor/some-package.js\"" + refute file =~ "assets/vendor" + end) + end) + end + + test "moves assets/vendor/topbar.js into assets/js/vendor/topbar.js", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.NimbleJS.apply(project) + + assert_file("assets/js/vendor/topbar.js") + refute_file("assets/vendor/topbar.js") + end) + end + + test "updates .eslintrc.json config", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.NimbleJS.apply(project) + + assert_file("assets/.eslintrc.json", fn file -> + assert file =~ """ + "ignorePatterns": [ + "/js/vendor/topbar.js" + ] + """ + end) + end) + end + end +end diff --git a/test/nimble_template/addons/variants/phoenix/web/node_package_test.exs b/test/nimble_template/addons/variants/phoenix/web/node_package_test.exs new file mode 100644 index 00000000..0c364908 --- /dev/null +++ b/test/nimble_template/addons/variants/phoenix/web/node_package_test.exs @@ -0,0 +1,16 @@ +defmodule NimbleTemplate.Addons.Phoenix.Web.NodePackageTest do + use NimbleTemplate.AddonCase, async: false + + describe "#apply/2" do + test "copies the package.json into assets", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.NodePackage.apply(project) + + assert_file("assets/package.json") + end) + end + end +end diff --git a/test/nimble_template/addons/variants/phoenix/web/prettier_test.exs b/test/nimble_template/addons/variants/phoenix/web/prettier_test.exs new file mode 100644 index 00000000..c98dafa3 --- /dev/null +++ b/test/nimble_template/addons/variants/phoenix/web/prettier_test.exs @@ -0,0 +1,86 @@ +defmodule NimbleTemplate.Addons.Phoenix.Web.PrettierTest do + use NimbleTemplate.AddonCase, async: false + + describe "#apply/2" do + @describetag required_addons: [:TestEnv, :"Phoenix.Web.NodePackage"] + + test "adds prettier and prettier-plugin-eex into package.json", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.Prettier.apply(project) + + assert_file("assets/package.json", fn file -> + assert file =~ """ + "devDependencies": { + "prettier": "2.5.1", + "prettier-plugin-eex": "^0.5.0" + """ + end) + end) + end + + test "injects prettier to mix aliases", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.Prettier.apply(project) + + assert_file("mix.exs", fn file -> + assert file =~ """ + defp aliases do + [ + prettier: "cmd ./assets/node_modules/.bin/prettier --check . --color", + "prettier.fix": "cmd ./assets/node_modules/.bin/prettier --write . --color", + """ + end) + end) + end + + test "adds prettier into the codebase alias", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.Prettier.apply(project) + + assert_file("mix.exs", fn file -> + assert file =~ """ + codebase: [ + "prettier", + """ + end) + end) + end + + test "adds prettier.fix into the codebase.fix alias", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.Prettier.apply(project) + + assert_file("mix.exs", fn file -> + assert file =~ """ + "codebase.fix": [ + "prettier.fix", + """ + end) + end) + end + + test "copies the .prettierignore and .prettierrc.yaml", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.Prettier.apply(project) + + assert_file(".prettierignore") + assert_file(".prettierrc.yaml") + end) + end + end +end diff --git a/test/nimble_template/addons/variants/phoenix/web/sobelow_test.exs b/test/nimble_template/addons/variants/phoenix/web/sobelow_test.exs index 91abc080..9681c76e 100644 --- a/test/nimble_template/addons/variants/phoenix/web/sobelow_test.exs +++ b/test/nimble_template/addons/variants/phoenix/web/sobelow_test.exs @@ -2,15 +2,15 @@ defmodule NimbleTemplate.Addons.Phoenix.Web.SobelowTest do use NimbleTemplate.AddonCase, async: false describe "#apply/2" do - @describetag mock_latest_package_versions: [{:credo, "0.26.2"}, {:sobelow, "0.8"}] - @describetag required_addons: [:TestEnv, :Credo] + @describetag mock_latest_package_versions: [{:sobelow, "0.8"}] + @describetag required_addons: [:TestEnv] test "copies the .sobelow-conf", %{ project: project, test_project_path: test_project_path } do in_test_project(test_project_path, fn -> - AddonsWeb.Sobelow.apply(project) + WebAddons.Sobelow.apply(project) assert_file(".sobelow-conf") end) @@ -21,13 +21,13 @@ defmodule NimbleTemplate.Addons.Phoenix.Web.SobelowTest do test_project_path: test_project_path } do in_test_project(test_project_path, fn -> - AddonsWeb.Sobelow.apply(project) + WebAddons.Sobelow.apply(project) assert_file("mix.exs", fn file -> assert file =~ """ defp deps do [ - {:sobelow, \"~> 0.8\", [only: [:dev, :test], runtime: false]}, + {:sobelow, "~> 0.8", [only: [:dev, :test], runtime: false]}, """ end) end) @@ -35,18 +35,12 @@ defmodule NimbleTemplate.Addons.Phoenix.Web.SobelowTest do test "adds sobelow codebase alias", %{project: project, test_project_path: test_project_path} do in_test_project(test_project_path, fn -> - AddonsWeb.Sobelow.apply(project) + WebAddons.Sobelow.apply(project) assert_file("mix.exs", fn file -> assert file =~ """ - defp aliases do - [ codebase: [ - \"deps.unlock --check-unused\", - \"format --check-formatted\", - \"credo --strict\", - \"sobelow --config\" - ], + "sobelow --config", """ end) end) diff --git a/test/nimble_template/addons/variants/phoenix/web/style_lint_test.exs b/test/nimble_template/addons/variants/phoenix/web/style_lint_test.exs new file mode 100644 index 00000000..9575aa31 --- /dev/null +++ b/test/nimble_template/addons/variants/phoenix/web/style_lint_test.exs @@ -0,0 +1,88 @@ +defmodule NimbleTemplate.Addons.Phoenix.Web.StyleLintTest do + use NimbleTemplate.AddonCase, async: false + + describe "#apply/2" do + @describetag required_addons: [:TestEnv, :"Phoenix.Web.NodePackage"] + + test "adds stylelint, + stylelint-config-property-sort-order-smacss + and stylelint-config-sass-guidelines into package.json", + %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.StyleLint.apply(project) + + assert_file("assets/package.json", fn file -> + assert file =~ """ + "devDependencies": { + "stylelint": "^14.5.3", + "stylelint-config-property-sort-order-smacss": "^9.0.0", + "stylelint-config-sass-guidelines": "^9.0.1", + """ + end) + end) + end + + test "adds stylelint and stylelint.fix into package.json", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.StyleLint.apply(project) + + assert_file("assets/package.json", fn file -> + assert file =~ """ + "scripts": { + "stylelint": "stylelint --color ./css", + "stylelint.fix": "stylelint --color --fix ./css", + """ + end) + end) + end + + test "adds stylelint into the codebase alias", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.StyleLint.apply(project) + + assert_file("mix.exs", fn file -> + assert file =~ """ + codebase: [ + "cmd npm run stylelint --prefix assets", + """ + end) + end) + end + + test "adds stylelint.fix into the codebase.fix alias", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.StyleLint.apply(project) + + assert_file("mix.exs", fn file -> + assert file =~ """ + "codebase.fix": [ + "cmd npm run stylelint.fix --prefix assets", + """ + end) + end) + end + + test "copies the .stylelintrc.json", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.StyleLint.apply(project) + + assert_file("assets/.stylelintrc.json") + end) + end + end +end diff --git a/test/nimble_template/addons/variants/phoenix/web/svg_sprite_test.exs b/test/nimble_template/addons/variants/phoenix/web/svg_sprite_test.exs new file mode 100644 index 00000000..4c0d83d9 --- /dev/null +++ b/test/nimble_template/addons/variants/phoenix/web/svg_sprite_test.exs @@ -0,0 +1,130 @@ +defmodule NimbleTemplate.Addons.Phoenix.Web.SvgSpriteTest do + use NimbleTemplate.AddonCase, async: false + + describe "#apply/2" do + @describetag required_addons: [:"Phoenix.Web.NodePackage"] + + test "adds svg-sprite into package.json", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.SvgSprite.apply(project) + + assert_file("assets/package.json", fn file -> + assert file =~ """ + "devDependencies": { + "svg-sprite": "^1.5.4", + """ + end) + end) + end + + test "adds svg-sprite.generate-icon into package.json", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.SvgSprite.apply(project) + + assert_file("assets/package.json", fn file -> + assert file =~ """ + "scripts": { + "svg-sprite.generate-icon": "svg-sprite --shape-id-generator \\"icon-%s\\" --symbol --symbol-dest static/images --symbol-sprite icon-sprite.svg static/images/icons/*.svg", + """ + end) + end) + end + + test "adds `import NimbleTemplateWeb.IconHelper` into lib/nimble_template_web.ex", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.SvgSprite.apply(project) + + assert_file("lib/nimble_template_web.ex", fn file -> + assert file =~ """ + import NimbleTemplateWeb.IconHelper + """ + end) + end) + end + + test "copies the icon_helper.ex", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.SvgSprite.apply(project) + + assert_file("lib/nimble_template_web/helpers/icon_helper.ex") + end) + end + + test "copies the icon_helper_test.exs", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.SvgSprite.apply(project) + + assert_file("test/nimble_template_web/helpers/icon_helper_test.exs") + end) + end + + test "does NOT copy `.github/wiki/Icon-Sprite.md`", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.SvgSprite.apply(project) + + refute_file(".github/wiki/Icon-Sprite.md") + end) + end + + test "does NOT copy `.github/wiki/_Sidebar.md`", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.SvgSprite.apply(project) + + refute_file(".github/wiki/_Sidebar.md") + end) + end + end + + describe "#apply/2 with Github Wiki addon" do + @describetag required_addons: [{:Github, %{github_wiki: true}}, :"Phoenix.Web.NodePackage"] + + test "copies `.github/wiki/Icon-Sprite.md`", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.SvgSprite.apply(project) + + assert_file(".github/wiki/Icon-Sprite.md") + end) + end + + test "adds `Icon Sprite` into `.github/wiki/_Sidebar.md`", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + WebAddons.SvgSprite.apply(project) + + assert_file(".github/wiki/_Sidebar.md", fn file -> + assert file =~ """ + ## Operations + + - [[Icon Sprite]] + """ + end) + end) + end + end +end diff --git a/test/nimble_template/addons/variants/phoenix/web/wallaby_test.exs b/test/nimble_template/addons/variants/phoenix/web/wallaby_test.exs index c7f5bd70..1ff5f08d 100644 --- a/test/nimble_template/addons/variants/phoenix/web/wallaby_test.exs +++ b/test/nimble_template/addons/variants/phoenix/web/wallaby_test.exs @@ -9,7 +9,7 @@ defmodule NimbleTemplate.Addons.Phoenix.Web.WallabyTest do test_project_path: test_project_path } do in_test_project(test_project_path, fn -> - AddonsWeb.Wallaby.apply(project) + WebAddons.Wallaby.apply(project) assert_file("test/support/feature_case.ex", fn file -> assert file =~ """ @@ -18,11 +18,18 @@ defmodule NimbleTemplate.Addons.Phoenix.Web.WallabyTest do using do quote do + use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney use Wallaby.Feature + use Mimic import NimbleTemplate.Factory + import NimbleTemplateWeb.Gettext + alias NimbleTemplate.Repo + alias NimbleTemplateWeb.Endpoint alias NimbleTemplateWeb.Router.Helpers, as: Routes + + @moduletag :feature_test end end end @@ -36,7 +43,7 @@ defmodule NimbleTemplate.Addons.Phoenix.Web.WallabyTest do test_project_path: test_project_path } do in_test_project(test_project_path, fn -> - AddonsWeb.Wallaby.apply(project) + WebAddons.Wallaby.apply(project) assert_file("test/nimble_template_web/features/home_page/view_home_page_test.exs") end) @@ -47,13 +54,13 @@ defmodule NimbleTemplate.Addons.Phoenix.Web.WallabyTest do test_project_path: test_project_path } do in_test_project(test_project_path, fn -> - AddonsWeb.Wallaby.apply(project) + WebAddons.Wallaby.apply(project) assert_file("mix.exs", fn file -> assert file =~ """ defp deps do [ - {:wallaby, \"~> 0.26.2\", [only: :test, runtime: false]}, + {:wallaby, "~> 0.26.2", [only: :test, runtime: false]}, """ end) end) @@ -64,7 +71,7 @@ defmodule NimbleTemplate.Addons.Phoenix.Web.WallabyTest do test_project_path: test_project_path } do in_test_project(test_project_path, fn -> - AddonsWeb.Wallaby.apply(project) + WebAddons.Wallaby.apply(project) assert_file("test/test_helper.exs", fn file -> assert file =~ """ @@ -84,7 +91,7 @@ defmodule NimbleTemplate.Addons.Phoenix.Web.WallabyTest do test_project_path: test_project_path } do in_test_project(test_project_path, fn -> - AddonsWeb.Wallaby.apply(project) + WebAddons.Wallaby.apply(project) assert_file("lib/nimble_template_web/endpoint.ex", fn file -> assert file =~ """ @@ -103,12 +110,10 @@ defmodule NimbleTemplate.Addons.Phoenix.Web.WallabyTest do test_project_path: test_project_path } do in_test_project(test_project_path, fn -> - AddonsWeb.Wallaby.apply(project) + WebAddons.Wallaby.apply(project) assert_file("config/test.exs", fn file -> assert file =~ """ - config :nimble_template, NimbleTemplateWeb.Endpoint, - http: [port: 4002], server: true config :nimble_template, :sql_sandbox, true @@ -128,7 +133,7 @@ defmodule NimbleTemplate.Addons.Phoenix.Web.WallabyTest do test_project_path: test_project_path } do in_test_project(test_project_path, fn -> - AddonsWeb.Wallaby.apply(project) + WebAddons.Wallaby.apply(project) assert_file(".gitignore", fn file -> assert file =~ "**/tmp/" diff --git a/test/nimble_template/helpers/dependency_test.exs b/test/nimble_template/helpers/dependency_test.exs new file mode 100644 index 00000000..2739f608 --- /dev/null +++ b/test/nimble_template/helpers/dependency_test.exs @@ -0,0 +1,32 @@ +defmodule NimbleTemplate.DependencyTest do + use NimbleTemplate.AddonCase, async: false + + alias NimbleTemplate.{Addons, DependencyHelper} + alias NimbleTemplate.Addons.Phoenix, as: PhoenixAddons + + describe "order_dependencies!/0" do + @describetag mock_latest_package_versions: [{:exvcr, "0.12.2"}, {:mimic, "1.3.1"}] + + test "orders dependencies in alphabetical order", %{ + project: project, + test_project_path: test_project_path + } do + in_test_project(test_project_path, fn -> + PhoenixAddons.ExVCR.apply(project) + Addons.Mimic.apply(project) + + # Unordered mix dependencies + assert_file("mix.exs", fn file -> + assert file =~ ~r/(:mimic).*(:exvcr)/s + end) + + DependencyHelper.order_dependencies!() + + # Ordered mix dependencies + assert_file("mix.exs", fn file -> + assert file =~ ~r/(:exvcr).*(:mimic)/s + end) + end) + end + end +end diff --git a/test/support/addon_case.ex b/test/support/addon_case.ex index e5995e87..598199c2 100644 --- a/test/support/addon_case.ex +++ b/test/support/addon_case.ex @@ -3,14 +3,18 @@ defmodule NimbleTemplate.AddonCase do use Mimic - alias NimbleTemplate.Addons.Phoenix.Web, as: AddonsWeb - alias NimbleTemplate.{Addons, Project} + alias NimbleTemplate.Addons + alias NimbleTemplate.Addons.Phoenix.Api, as: ApiAddons + alias NimbleTemplate.Addons.Phoenix.Web, as: WebAddons alias NimbleTemplate.Hex.Package + alias NimbleTemplate.Projects.Project using do quote do alias NimbleTemplate.Addons - alias NimbleTemplate.Addons.Phoenix.Web, as: AddonsWeb + alias NimbleTemplate.Addons.Phoenix, as: PhoenixAddons + alias NimbleTemplate.Addons.Phoenix.Api, as: ApiAddons + alias NimbleTemplate.Addons.Phoenix.Web, as: WebAddons # ATTENTION: File.cd! doesn't support `async: true`, the test will fail randomly in async mode # https://elixirforum.com/t/randomly-getting-compilationerror-on-tests/17298/3 @@ -19,6 +23,9 @@ defmodule NimbleTemplate.AddonCase do defp assert_file(path), do: assert(File.regular?(path), "Expected #{path} to exist, but does not") + defp assert_directory(path), + do: assert(File.dir?(path), "Expected #{path} to exist, but does not") + defp assert_file(path, match) do assert_file(path) match.(File.read!(path)) @@ -34,16 +41,23 @@ defmodule NimbleTemplate.AddonCase do test_project_path = Path.join(parent_test_project_path, "/nimble_template") project = - if context[:mix_project?] == true do - create_mix_test_project(test_project_path) + cond do + context[:mix_project?] == true -> + create_mix_test_project(test_project_path) + + Project.new(mix: true) + + context[:live_project?] == true -> + create_phoenix_test_project(test_project_path) - Project.new(mix: true) - else - create_phoenix_test_project(test_project_path) + Project.new(web: true, live: true) - # Set Web Project as default, switch to either API or Live Project in each test case - # eg: project = %{project | api_project?: true, web_project?: false} - Project.new(web: true) + true -> + create_phoenix_test_project(test_project_path, "--no-live") + + # Set Web Project as default, switch to API in each test case + # eg: project = %{project | api_project?: true, web_project?: false} + Project.new(web: true) end on_exit(fn -> @@ -56,20 +70,26 @@ defmodule NimbleTemplate.AddonCase do if required_addons = context[:required_addons] do File.cd!(test_project_path, fn -> - Enum.each(required_addons, &Module.safe_concat([Addons, &1]).apply(project)) + Enum.each(required_addons, &apply_required_addon(&1, project)) end) end {:ok, project: project, test_project_path: test_project_path} end + defp apply_required_addon(required_addon, project) when is_atom(required_addon), + do: Module.safe_concat([Addons, required_addon]).apply(project) + + defp apply_required_addon({required_addon_module, required_addon_opt}, project), + do: Module.safe_concat([Addons, required_addon_module]).apply(project, required_addon_opt) + defp mock_latest_package_version({_package, version}), do: expect(Package, :get_latest_version, fn _package -> version end) - defp create_phoenix_test_project(test_project_path) do + defp create_phoenix_test_project(test_project_path, opts \\ "") do # N - in response to Fetch and install dependencies? Mix.shell().cmd( - "printf \"N\n\" | make create_phoenix_project PROJECT_DIRECTORY=#{test_project_path} > /dev/null" + "printf \"N\n\" | make create_phoenix_project PROJECT_DIRECTORY=#{test_project_path} OPTIONS=#{opts} > /dev/null" ) end