From f1e994cf7cb7b0ac745ebc668ec11ad7c4f1ded2 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Tue, 28 Feb 2023 10:30:04 +0530 Subject: [PATCH 001/131] refactor: rewrite from scratch The assembler package now has zero dependencies on AdonisJS and also do not expose any commands. It exposes general purpose class to run the dev server and build the project for production --- .github/COMMIT_CONVENTION.md | 70 -- .github/CONTRIBUTING.md | 46 - .github/ISSUE_TEMPLATE/bug_report.md | 29 - .github/ISSUE_TEMPLATE/feature_request.md | 28 - .github/PULL_REQUEST_TEMPLATE.md | 28 - .github/labels.json | 170 ++++ .github/workflows/test.yml | 36 +- .husky/commit-msg | 7 +- LICENSE.md | 2 +- bin/index.ts | 27 - bin/japaTypes.ts | 7 - bin/test.ts | 10 +- commands/Build.ts | 113 --- commands/Invoke.ts | 268 ------ commands/Make/Base.ts | 86 -- commands/Make/Command.ts | 75 -- commands/Make/Controller.ts | 86 -- commands/Make/Exception.ts | 71 -- commands/Make/Listener.ts | 61 -- commands/Make/Middleware.ts | 83 -- commands/Make/PreloadFile.ts | 143 --- commands/Make/Provider.ts | 82 -- commands/Make/Suite.ts | 120 --- commands/Make/Test.ts | 88 -- commands/Make/Validator.ts | 62 -- commands/Make/View.ts | 61 -- commands/Serve.ts | 88 -- commands/Test.ts | 158 --- commands/TypeCheck.ts | 50 - config/paths.ts | 16 - register.ts => index.ts | 6 +- package.json | 174 ++-- src/AssetsBundler/index.ts | 254 ----- src/Compiler/index.ts | 367 ------- src/Contracts/index.ts | 17 - src/DevServer/index.ts | 388 -------- src/EnvParser/index.ts | 49 - src/HttpServer/index.ts | 94 -- src/Manifest/index.ts | 89 -- src/RcFile/index.ts | 190 ---- src/Test/index.ts | 414 -------- src/Test/process.ts | 73 -- src/Ts/index.ts | 66 -- src/bundler.ts | 233 +++++ src/dev_server.ts | 309 ++++++ src/requireHook/index.ts | 27 - src/requireHook/ioc-transformer.ts | 21 - src/run.ts | 42 + src/types.ts | 49 + src/watch.ts | 40 + templates/command.txt | 33 - templates/controller.txt | 3 - templates/event-listener.txt | 3 - templates/exception.txt | 15 - templates/middleware.txt | 8 - templates/preload-file.txt | 9 - templates/provider.txt | 40 - templates/resource-controller.txt | 17 - templates/self-handle-exception.txt | 32 - templates/test-entrypoint.txt | 45 - templates/test.txt | 5 - templates/tests-contract.txt | 18 - templates/tests/bootstrap.txt | 69 -- .../tests/functional/hello_world_api.spec.txt | 8 - .../functional/hello_world_slim.spec.txt | 8 - .../tests/functional/hello_world_web.spec.txt | 8 - templates/validator.txt | 40 - templates/view.txt | 0 templates/webpack.config.txt | 214 ----- test-helpers/index.ts | 18 - test/compiler.spec.ts | 905 ------------------ test/configure-encore.spec.ts | 60 -- test/configure-tests.spec.ts | 49 - test/env-parser.spec.ts | 46 - test/invoke-command.spec.ts | 50 - test/make-command.spec.ts | 118 --- test/make-controller.spec.ts | 100 -- test/make-exception.spec.ts | 107 --- test/make-listener.spec.ts | 81 -- test/make-middleware.spec.ts | 81 -- test/make-preloaded-file.spec.ts | 174 ---- test/make-provider.spec.ts | 138 --- test/make-suite.spec.ts | 130 --- test/make-test.spec.ts | 109 --- test/make-validator.spec.ts | 85 -- test/make-view.spec.ts | 82 -- test/rc-file.spec.ts | 242 ----- tests/run.spec.ts | 124 +++ tests/watch.spec.ts | 131 +++ tsconfig.json | 33 +- 90 files changed, 1217 insertions(+), 7294 deletions(-) delete mode 100644 .github/COMMIT_CONVENTION.md delete mode 100644 .github/CONTRIBUTING.md delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.md delete mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/labels.json delete mode 100644 bin/index.ts delete mode 100644 bin/japaTypes.ts delete mode 100644 commands/Build.ts delete mode 100644 commands/Invoke.ts delete mode 100644 commands/Make/Base.ts delete mode 100644 commands/Make/Command.ts delete mode 100644 commands/Make/Controller.ts delete mode 100644 commands/Make/Exception.ts delete mode 100644 commands/Make/Listener.ts delete mode 100644 commands/Make/Middleware.ts delete mode 100644 commands/Make/PreloadFile.ts delete mode 100644 commands/Make/Provider.ts delete mode 100644 commands/Make/Suite.ts delete mode 100644 commands/Make/Test.ts delete mode 100644 commands/Make/Validator.ts delete mode 100644 commands/Make/View.ts delete mode 100644 commands/Serve.ts delete mode 100644 commands/Test.ts delete mode 100644 commands/TypeCheck.ts delete mode 100644 config/paths.ts rename register.ts => index.ts (54%) delete mode 100644 src/AssetsBundler/index.ts delete mode 100644 src/Compiler/index.ts delete mode 100644 src/Contracts/index.ts delete mode 100644 src/DevServer/index.ts delete mode 100644 src/EnvParser/index.ts delete mode 100644 src/HttpServer/index.ts delete mode 100644 src/Manifest/index.ts delete mode 100644 src/RcFile/index.ts delete mode 100644 src/Test/index.ts delete mode 100644 src/Test/process.ts delete mode 100644 src/Ts/index.ts create mode 100644 src/bundler.ts create mode 100644 src/dev_server.ts delete mode 100644 src/requireHook/index.ts delete mode 100644 src/requireHook/ioc-transformer.ts create mode 100644 src/run.ts create mode 100644 src/types.ts create mode 100644 src/watch.ts delete mode 100644 templates/command.txt delete mode 100644 templates/controller.txt delete mode 100644 templates/event-listener.txt delete mode 100644 templates/exception.txt delete mode 100644 templates/middleware.txt delete mode 100644 templates/preload-file.txt delete mode 100644 templates/provider.txt delete mode 100644 templates/resource-controller.txt delete mode 100644 templates/self-handle-exception.txt delete mode 100644 templates/test-entrypoint.txt delete mode 100644 templates/test.txt delete mode 100644 templates/tests-contract.txt delete mode 100644 templates/tests/bootstrap.txt delete mode 100644 templates/tests/functional/hello_world_api.spec.txt delete mode 100644 templates/tests/functional/hello_world_slim.spec.txt delete mode 100644 templates/tests/functional/hello_world_web.spec.txt delete mode 100644 templates/validator.txt delete mode 100644 templates/view.txt delete mode 100644 templates/webpack.config.txt delete mode 100644 test-helpers/index.ts delete mode 100644 test/compiler.spec.ts delete mode 100644 test/configure-encore.spec.ts delete mode 100644 test/configure-tests.spec.ts delete mode 100644 test/env-parser.spec.ts delete mode 100644 test/invoke-command.spec.ts delete mode 100644 test/make-command.spec.ts delete mode 100644 test/make-controller.spec.ts delete mode 100644 test/make-exception.spec.ts delete mode 100644 test/make-listener.spec.ts delete mode 100644 test/make-middleware.spec.ts delete mode 100644 test/make-preloaded-file.spec.ts delete mode 100644 test/make-provider.spec.ts delete mode 100644 test/make-suite.spec.ts delete mode 100644 test/make-test.spec.ts delete mode 100644 test/make-validator.spec.ts delete mode 100644 test/make-view.spec.ts delete mode 100644 test/rc-file.spec.ts create mode 100644 tests/run.spec.ts create mode 100644 tests/watch.spec.ts diff --git a/.github/COMMIT_CONVENTION.md b/.github/COMMIT_CONVENTION.md deleted file mode 100644 index fc852af..0000000 --- a/.github/COMMIT_CONVENTION.md +++ /dev/null @@ -1,70 +0,0 @@ -## Git Commit Message Convention - -> This is adapted from [Angular's commit convention](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular). - -Using conventional commit messages, we can automate the process of generating the CHANGELOG file. All commits messages will automatically be validated against the following regex. - -``` js -/^(revert: )?(feat|fix|docs|style|refactor|perf|test|workflow|ci|chore|types|build|improvement)((.+))?: .{1,50}/ -``` - -## Commit Message Format -A commit message consists of a **header**, **body** and **footer**. The header has a **type**, **scope** and **subject**: - -> The **scope** is optional - -``` -feat(router): add support for prefix - -Prefix makes it easier to append a path to a group of routes -``` - -1. `feat` is type. -2. `router` is scope and is optional -3. `add support for prefix` is the subject -4. The **body** is followed by a blank line. -5. The optional **footer** can be added after the body, followed by a blank line. - -## Types -Only one type can be used at a time and only following types are allowed. - -- feat -- fix -- docs -- style -- refactor -- perf -- test -- workflow -- ci -- chore -- types -- build - -If a type is `feat`, `fix` or `perf`, then the commit will appear in the CHANGELOG.md file. However if there is any BREAKING CHANGE, the commit will always appear in the changelog. - -### Revert -If the commit reverts a previous commit, it should begin with `revert:`, followed by the header of the reverted commit. In the body it should say: `This reverts commit `., where the hash is the SHA of the commit being reverted. - -## Scope -The scope could be anything specifying place of the commit change. For example: `router`, `view`, `querybuilder`, `database`, `model` and so on. - -## Subject -The subject contains succinct description of the change: - -- use the imperative, present tense: "change" not "changed" nor "changes". -- don't capitalize first letter -- no dot (.) at the end - -## Body - -Just as in the **subject**, use the imperative, present tense: "change" not "changed" nor "changes". -The body should include the motivation for the change and contrast this with previous behavior. - -## Footer - -The footer should contain any information about **Breaking Changes** and is also the place to -reference GitHub issues that this commit **Closes**. - -**Breaking Changes** should start with the word `BREAKING CHANGE:` with a space or two newlines. The rest of the commit message is then used for this. - diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md deleted file mode 100644 index f0c5446..0000000 --- a/.github/CONTRIBUTING.md +++ /dev/null @@ -1,46 +0,0 @@ -# Contributing - -AdonisJS is a community driven project. You are free to contribute in any of the following ways. - -- [Coding style](coding-style) -- [Fix bugs by creating PR's](fix-bugs-by-creating-prs) -- [Share an RFC for new features or big changes](share-an-rfc-for-new-features-or-big-changes) -- [Report security issues](report-security-issues) -- [Be a part of the community](be-a-part-of-community) - -## Coding style - -Majority of AdonisJS core packages are written in Typescript. Having a brief knowledge of Typescript is required to contribute to the core. - -## Fix bugs by creating PR's - -We appreciate every time you report a bug in the framework or related libraries. However, taking time to submit a PR can help us in fixing bugs quickly and ensure a healthy and stable eco-system. - -Go through the following points, before creating a new PR. - -1. Create an issue discussing the bug or short-coming in the framework. -2. Once approved, go ahead and fork the REPO. -3. Make sure to start from the `develop`, since this is the upto date branch. -4. Make sure to keep commits small and relevant. -5. We follow [conventional-commits](https://github.com/conventional-changelog/conventional-changelog) to structure our commit messages. Instead of running `git commit`, you must run `npm commit`, which will show you prompts to create a valid commit message. -6. Once done with all the changes, create a PR against the `develop` branch. - -## Share an RFC for new features or big changes - -Sharing PR's for small changes works great. However, when contributing big features to the framework, it is required to go through the RFC process. - -### What is an RFC? - -RFC stands for **Request for Commits**, a standard process followed by many other frameworks including [Ember](https://github.com/emberjs/rfcs), [yarn](https://github.com/yarnpkg/rfcs) and [rust](https://github.com/rust-lang/rfcs). - -In brief, RFC process allows you to talk about the changes with everyone in the community and get a view of the core team before dedicating your time to work on the feature. - -The RFC proposals are created as Pull Request on [adonisjs/rfcs](https://github.com/adonisjs/rfcs) repo. Make sure to read the README to learn about the process in depth. - -## Report security issues - -All of the security issues, must be reported via [email](mailto:virk@adonisjs.com) and not using any of the public channels. - -## Be a part of community - -We welcome you to participate in [GitHub Discussion](https://github.com/adonisjs/core/discussions) and the AdonisJS [Discord Server](https://discord.gg/vDcEjq6). You are free to ask your questions and share your work or contributions made to AdonisJS eco-system. diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index e65000c..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -name: Bug report -about: Report identified bugs ---- - - - -## Prerequisites - -We do our best to reply to all the issues on time. If you will follow the given guidelines, the turn around time will be faster. - -- Lots of raised issues are directly not bugs but instead are design decisions taken by us. -- Make use of our [GH discussions](https://github.com/adonisjs/core/discussions), or [discord server](https://discord.me/adonisjs), if you are not sure that you are reporting a bug. -- Ensure the issue isn't already reported. -- Ensure you are reporting the bug in the correct repo. - -*Delete the above section and the instructions in the sections below before submitting* - -## Package version - - -## Node.js and npm version - - -## Sample Code (to reproduce the issue) - - -## BONUS (a sample repo to reproduce the issue) - diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index abd44a5..0000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -name: Feature request -about: Propose changes for adding a new feature ---- - - - -## Prerequisites - -We do our best to reply to all the issues on time. If you will follow the given guidelines, the turn around time will be faster. - -## Consider an RFC - -Please create an [RFC](https://github.com/adonisjs/rfcs) instead, if - -- Feature introduces a breaking change -- Demands lots of time and changes in the current code base. - -*Delete the above section and the instructions in the sections below before submitting* - -## Why this feature is required (specific use-cases will be appreciated)? - - -## Have you tried any other work arounds? - - -## Are you willing to work on it with little guidance? - diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 844839b..0000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,28 +0,0 @@ - - -## Proposed changes - -Describe the big picture of your changes here to communicate to the maintainers why we should accept this pull request. If it fixes a bug or resolves a feature request, be sure to link to that issue. - -## Types of changes - -What types of changes does your code introduce? - -_Put an `x` in the boxes that apply_ - -- [ ] Bugfix (non-breaking change which fixes an issue) -- [ ] New feature (non-breaking change which adds functionality) -- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - -## Checklist - -_Put an `x` in the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code._ - -- [ ] I have read the [CONTRIBUTING](https://github.com/adonisjs/assembler/blob/master/.github/CONTRIBUTING.md) doc -- [ ] Lint and unit tests pass locally with my changes -- [ ] I have added tests that prove my fix is effective or that my feature works. -- [ ] I have added necessary documentation (if appropriate) - -## Further comments - -If this is a relatively large or complex change, kick off the discussion by explaining why you chose the solution you did and what alternatives you considered, etc... diff --git a/.github/labels.json b/.github/labels.json new file mode 100644 index 0000000..ba001c6 --- /dev/null +++ b/.github/labels.json @@ -0,0 +1,170 @@ +[ + { + "name": "Priority: Critical", + "color": "ea0056", + "description": "The issue needs urgent attention", + "aliases": [] + }, + { + "name": "Priority: High", + "color": "5666ed", + "description": "Look into this issue before picking up any new work", + "aliases": [] + }, + { + "name": "Priority: Medium", + "color": "f4ff61", + "description": "Try to fix the issue for the next patch/minor release", + "aliases": [] + }, + { + "name": "Priority: Low", + "color": "87dfd6", + "description": "Something worth considering, but not a top priority for the team", + "aliases": [] + }, + { + "name": "Semver: Alpha", + "color": "008480", + "description": "Will make it's way to the next alpha version of the package", + "aliases": [] + }, + { + "name": "Semver: Major", + "color": "ea0056", + "description": "Has breaking changes", + "aliases": [] + }, + { + "name": "Semver: Minor", + "color": "fbe555", + "description": "Mainly new features and improvements", + "aliases": [] + }, + { + "name": "Semver: Next", + "color": "5666ed", + "description": "Will make it's way to the bleeding edge version of the package", + "aliases": [] + }, + { + "name": "Semver: Patch", + "color": "87dfd6", + "description": "A bug fix", + "aliases": [] + }, + { + "name": "Status: Abandoned", + "color": "ffffff", + "description": "Dropped and not into consideration", + "aliases": ["wontfix"] + }, + { + "name": "Status: Accepted", + "color": "e5fbf2", + "description": "The proposal or the feature has been accepted for the future versions", + "aliases": [] + }, + { + "name": "Status: Blocked", + "color": "ea0056", + "description": "The work on the issue or the PR is blocked. Check comments for reasoning", + "aliases": [] + }, + { + "name": "Status: Completed", + "color": "008672", + "description": "The work has been completed, but not released yet", + "aliases": [] + }, + { + "name": "Status: In Progress", + "color": "73dbc4", + "description": "Still banging the keyboard", + "aliases": ["in progress"] + }, + { + "name": "Status: On Hold", + "color": "f4ff61", + "description": "The work was started earlier, but is on hold now. Check comments for reasoning", + "aliases": ["On Hold"] + }, + { + "name": "Status: Review Needed", + "color": "fbe555", + "description": "Review from the core team is required before moving forward", + "aliases": [] + }, + { + "name": "Status: Awaiting More Information", + "color": "89f8ce", + "description": "Waiting on the issue reporter or PR author to provide more information", + "aliases": [] + }, + { + "name": "Status: Need Contributors", + "color": "7057ff", + "description": "Looking for contributors to help us move forward with this issue or PR", + "aliases": [] + }, + { + "name": "Type: Bug", + "color": "ea0056", + "description": "The issue has indentified a bug", + "aliases": ["bug"] + }, + { + "name": "Type: Security", + "color": "ea0056", + "description": "Spotted security vulnerability and is a top priority for the core team", + "aliases": [] + }, + { + "name": "Type: Duplicate", + "color": "00837e", + "description": "Already answered or fixed previously", + "aliases": ["duplicate"] + }, + { + "name": "Type: Enhancement", + "color": "89f8ce", + "description": "Improving an existing feature", + "aliases": ["enhancement"] + }, + { + "name": "Type: Feature Request", + "color": "483add", + "description": "Request to add a new feature to the package", + "aliases": [] + }, + { + "name": "Type: Invalid", + "color": "dbdbdb", + "description": "Doesn't really belong here. Maybe use discussion threads?", + "aliases": ["invalid"] + }, + { + "name": "Type: Question", + "color": "eceafc", + "description": "Needs clarification", + "aliases": ["help wanted", "question"] + }, + { + "name": "Type: Documentation Change", + "color": "7057ff", + "description": "Documentation needs some improvements", + "aliases": ["documentation"] + }, + { + "name": "Type: Dependencies Update", + "color": "00837e", + "description": "Bump dependencies", + "aliases": ["dependencies"] + }, + { + "name": "Good First Issue", + "color": "008480", + "description": "Want to contribute? Just filter by this label", + "aliases": ["good first issue"] + } +] diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 788df91..2d9bc9e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,37 +3,5 @@ on: - push - pull_request jobs: - linux: - runs-on: ubuntu-latest - strategy: - matrix: - node-version: - - 14.15.4 - - 17.x - steps: - - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node-version }} - - name: Install - run: npm install - - name: Run tests - run: npm test - windows: - runs-on: windows-latest - strategy: - matrix: - node-version: - - 14.15.4 - - 17.x - steps: - - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node-version }} - - name: Install - run: npm install - - name: Run tests - run: npm test + test: + uses: adonisjs/.github/.github/workflows/test.yml@main diff --git a/.husky/commit-msg b/.husky/commit-msg index 4654c12..4002db7 100755 --- a/.husky/commit-msg +++ b/.husky/commit-msg @@ -1,3 +1,4 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" -HUSKY_GIT_PARAMS=$1 node ./node_modules/@adonisjs/mrm-preset/validate-commit/conventional/validate.js +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +npx --no -- commitlint --edit diff --git a/LICENSE.md b/LICENSE.md index 1c19428..59a3cfa 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ # The MIT License -Copyright 2022 Harminder Virk, contributors +Copyright (c) 2023 AdonisJS Framework Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/bin/index.ts b/bin/index.ts deleted file mode 100644 index 778afc5..0000000 --- a/bin/index.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { join } from 'path' -import { ManifestGenerator } from '@adonisjs/ace' -import { fsReadAll } from '@poppinss/utils/build/helpers' - -/** - * Get the file path to every assembler commands - */ -const commandsPaths = fsReadAll( - join(__dirname, '../commands'), - (file) => !file.includes('Base') && file.endsWith('.js') -) - .map((file) => `./commands/${file}`) - .map((file) => file.replace(/\\/g, '/')) - -/** - * Generates ace-manifest file - */ -new ManifestGenerator(join(__dirname, '..'), commandsPaths).generate() diff --git a/bin/japaTypes.ts b/bin/japaTypes.ts deleted file mode 100644 index d42cac6..0000000 --- a/bin/japaTypes.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Assert } from '@japa/assert' - -declare module '@japa/runner' { - interface TestContext { - assert: Assert - } -} diff --git a/bin/test.ts b/bin/test.ts index 5aba7ce..0bda2e2 100644 --- a/bin/test.ts +++ b/bin/test.ts @@ -1,8 +1,12 @@ import { assert } from '@japa/assert' +import { fileSystem } from '@japa/file-system' import { specReporter } from '@japa/spec-reporter' import { runFailedTests } from '@japa/run-failed-tests' +import { fileURLToPath, pathToFileURL } from 'node:url' import { processCliArgs, configure, run } from '@japa/runner' +const TEST_TMP_DIR_PATH = fileURLToPath(new URL('../tmp', import.meta.url)) + /* |-------------------------------------------------------------------------- | Configure tests @@ -19,10 +23,10 @@ import { processCliArgs, configure, run } from '@japa/runner' configure({ ...processCliArgs(process.argv.slice(2)), ...{ - files: ['test/**/*.spec.ts'], - plugins: [assert(), runFailedTests()], + files: ['tests/**/*.spec.ts'], + plugins: [assert(), runFailedTests(), fileSystem({ basePath: TEST_TMP_DIR_PATH })], reporters: [specReporter()], - importer: (filePath: string) => import(filePath), + importer: (filePath: string) => import(pathToFileURL(filePath).href), }, }) diff --git a/commands/Build.ts b/commands/Build.ts deleted file mode 100644 index 56f9105..0000000 --- a/commands/Build.ts +++ /dev/null @@ -1,113 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import hasYarn from 'has-yarn' -import { BaseCommand, flags } from '@adonisjs/core/build/standalone' -import { TSCONFIG_FILE_NAME } from '../config/paths' - -/** - * Compile typescript project Javascript - */ -export default class Build extends BaseCommand { - public static commandName = 'build' - public static description = - 'Compile project from Typescript to Javascript. Also compiles the frontend assets if using webpack encore' - - /** - * Build for production - */ - @flags.boolean({ description: 'Build for production', alias: 'prod' }) - public production: boolean - - /** - * Bundle frontend assets. Defaults to true - */ - @flags.boolean({ - description: - 'Build frontend assets when webpack encore is installed. Use --no-assets to disable', - }) - public assets: boolean = true - - /** - * Ignore ts errors and complete the build process. Defaults to false - */ - @flags.boolean({ - description: 'Ignore typescript errors and complete the build process', - }) - public ignoreTsErrors: boolean - - /** - * Path to the TypeScript project configuration file. Defaults to "tsconfig.json" - */ - @flags.string({ - description: 'Path to the TypeScript project configuration file', - }) - public tsconfig: string = TSCONFIG_FILE_NAME - - /** - * Arguments to pass to the `encore` binary - */ - @flags.array({ description: 'CLI options to pass to the encore command line' }) - public encoreArgs: string[] = [] - - /** - * Select the client for deciding the lock file to copy to the - * build folder - */ - @flags.string({ - description: 'Select the package manager to decide which lock file to copy to the build folder', - }) - public client: string - - /** - * Invoked automatically by ace - */ - public async run() { - const { Compiler } = await import('../src/Compiler') - - /** - * Deciding the client to use for installing dependencies - */ - this.client = this.client || hasYarn(this.application.appRoot) ? 'yarn' : 'npm' - if (this.client !== 'npm' && this.client !== 'yarn') { - this.logger.warning('--client must be set to "npm" or "yarn"') - this.exitCode = 1 - return - } - - /** - * Stop on error when "ignoreTsErrors" is not set - */ - const stopOnError = !this.ignoreTsErrors - - try { - const compiler = new Compiler( - this.application.appRoot, - this.encoreArgs, - this.assets, - this.logger, - this.tsconfig - ) - - const compiled = this.production - ? await compiler.compileForProduction(stopOnError, this.client) - : await compiler.compile(stopOnError) - - /** - * Set exitCode based upon the compiled status - */ - if (!compiled) { - this.exitCode = 1 - } - } catch (error) { - this.logger.fatal(error) - this.exitCode = 1 - } - } -} diff --git a/commands/Invoke.ts b/commands/Invoke.ts deleted file mode 100644 index 2191b9a..0000000 --- a/commands/Invoke.ts +++ /dev/null @@ -1,268 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { join } from 'path' -import { tasks, files, logger, utils } from '@adonisjs/sink' -import { BaseCommand, args } from '@adonisjs/core/build/standalone' - -import { Manifest } from '../src/Manifest' - -/** - * Configure a package - */ -export default class Configure extends BaseCommand { - public static commandName = 'configure' - public static description = 'Configure one or more AdonisJS packages' - public static aliases = ['invoke'] - - private appType = process.env['ADONIS_CREATE_APP_BOILERPLATE'] || 'web' - - /** - * Use yarn when building for production to install dependencies - */ - @args.spread({ - description: 'Name of the package(s) you want to configure', - }) - public packages: string[] - - /** - * Returns package manager for installing dependencies - */ - private getPackageManager() { - if (process.env['ADONIS_CREATE_APP_CLIENT']) { - return process.env['ADONIS_CREATE_APP_CLIENT'] as 'yarn' | 'npm' | 'pnpm' - } - return utils.getPackageManager(this.application.appRoot) - } - - /** - * Configure encore - */ - private async configureEncore() { - /** - * Create the webpack config file - */ - const webpackConfigFile = new files.MustacheFile( - this.application.appRoot, - 'webpack.config.js', - join(__dirname, '..', 'templates/webpack.config.txt') - ) - if (!webpackConfigFile.exists()) { - webpackConfigFile.apply({}).commit() - logger.action('create').succeeded('webpack.config.js') - } - - /** - * Create app.js entrypoint - */ - const entryPointFile = new files.NewLineFile(this.application.appRoot, 'resources/js/app.js') - if (!entryPointFile.exists()) { - entryPointFile.add('// app entrypoint').commit() - logger.action('create').succeeded('resources/js/app.js') - } - - /** - * Install Encore - */ - const pkgFile = new files.PackageJsonFile(this.application.appRoot) - pkgFile.install('@symfony/webpack-encore@4.1.1') - pkgFile.install('webpack@^5.72') - pkgFile.install('webpack-cli@^4.9.1') - pkgFile.install('@babel/core@^7.17.0') - pkgFile.install('@babel/preset-env@^7.16.0') - pkgFile.useClient(this.getPackageManager()) - - const spinner = logger.await(logger.colors.gray('configure @symfony/webpack-encore')) - - try { - const response = await pkgFile.commitAsync() - if (response && response.status === 1) { - spinner.stop() - logger.fatal({ message: 'Unable to configure encore', stack: response.stderr.toString() }) - } else { - spinner.stop() - logger.success('Configured encore successfully') - } - } catch (error) { - spinner.stop() - logger.fatal(error) - } - } - - /** - * Configure tests - */ - private async configureTests() { - /** - * Create "test.ts" file - */ - const testsEntryPointFile = new files.MustacheFile( - this.application.appRoot, - 'test.ts', - join(__dirname, '..', 'templates/test-entrypoint.txt') - ) - if (!testsEntryPointFile.exists()) { - testsEntryPointFile.apply({}).commit() - logger.action('create').succeeded('test.ts') - } - - /** - * Create "tests/bootstrap.ts" file - */ - const testsBootstrapFile = new files.MustacheFile( - this.application.appRoot, - 'tests/bootstrap.ts', - join(__dirname, '..', 'templates/tests/bootstrap.txt') - ) - if (!testsBootstrapFile.exists()) { - testsBootstrapFile.apply({}).commit() - logger.action('create').succeeded('tests/bootstrap.ts') - } - - /** - * Create "tests/functional/hello_world.spec.ts" file - */ - const helloWorldTestFile = new files.MustacheFile( - this.application.appRoot, - 'tests/functional/hello_world.spec.ts', - join(__dirname, '..', `templates/tests/functional/hello_world_${this.appType}.spec.txt`) - ) - if (!helloWorldTestFile.exists()) { - helloWorldTestFile.apply({}).commit() - logger.action('create').succeeded('tests/functional/hello_world.spec.ts') - } - - /** - * Create "contracts/tests.ts" file - */ - const testsContractsFile = new files.MustacheFile( - this.application.appRoot, - 'contracts/tests.ts', - join(__dirname, '..', 'templates/tests-contract.txt') - ) - if (!testsContractsFile.exists()) { - testsContractsFile.apply({}).commit() - logger.action('create').succeeded('contracts/tests.ts') - } - - /** - * Update AdonisRc file with test suites - */ - const rcFile = new files.AdonisRcFile(this.application.appRoot) - rcFile.set('tests', { - suites: [ - { - name: 'functional', - files: ['tests/functional/**/*.spec(.ts|.js)'], - timeout: 60 * 1000, - }, - ], - }) - rcFile.addTestProvider('@japa/preset-adonis/TestsProvider') - - rcFile.commit() - logger.action('update').succeeded('.adonisrc.json') - - /** - * Create ".env.test" file - */ - const testEnvFile = new files.NewLineFile(this.application.appRoot, '.env.test') - if (!testEnvFile.exists()) { - testEnvFile.add('NODE_ENV=test') - - /** - * Set additional .env variables for "web" boilerplate - */ - if (this.appType === 'web') { - testEnvFile.add(['ASSETS_DRIVER=fake', 'SESSION_DRIVER=memory']) - } - - testEnvFile.commit() - logger.action('create').succeeded('.env.test') - } - - /** - * Update "tsconfig.json" - */ - const tsConfig = new files.JsonFile(this.application.appRoot, 'tsconfig.json') - const existingTypes = tsConfig.get('compilerOptions.types') || [] - - if (!existingTypes.includes('@japa/preset-adonis/build/adonis-typings')) { - existingTypes.push('@japa/preset-adonis/build/adonis-typings') - } - tsConfig.set('compilerOptions.types', existingTypes) - - tsConfig.commit() - logger.action('update').succeeded('tsconfig.json') - - /** - * Set additional .env variables for "web" boilerplate - */ - if (this.appType === 'web') { - testEnvFile.add(['ASSETS_DRIVER=fake', 'SESSION_DRIVER=memory']) - } - - testEnvFile.commit() - logger.action('create').succeeded('.env.test') - - /** - * Install required dependencies - */ - const pkgFile = new files.PackageJsonFile(this.application.appRoot) - pkgFile.install('@japa/runner') - pkgFile.install('@japa/preset-adonis') - pkgFile.useClient(this.getPackageManager()) - - const spinner = logger.await(logger.colors.gray('installing @japa/runner, @japa/preset-adonis')) - - try { - const response = await pkgFile.commitAsync() - if (response && response.status === 1) { - spinner.stop() - logger.fatal({ - message: 'Unable to configure tests runner', - stack: response.stderr.toString(), - }) - } else { - spinner.stop() - logger.success('Configured tests runner successfully') - } - } catch (error) { - spinner.stop() - logger.fatal(error) - } - } - - /** - * Configure a give package - */ - private async configurePackage(name: string) { - if (name === 'encore') { - await this.configureEncore() - return - } - - if (name === 'tests') { - await this.configureTests() - return - } - - await new tasks.Instructions(name, this.application.appRoot, this.application, true).execute() - await new Manifest(this.application.appRoot, this.logger).generate() - } - - /** - * Invoked automatically by ace - */ - public async run() { - for (let name of this.packages) { - await this.configurePackage(name) - } - } -} diff --git a/commands/Make/Base.ts b/commands/Make/Base.ts deleted file mode 100644 index 5e704b8..0000000 --- a/commands/Make/Base.ts +++ /dev/null @@ -1,86 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { join } from 'path' -import { pathExists } from 'fs-extra' -import { BaseCommand } from '@adonisjs/core/build/standalone' - -/** - * Base class to generate framework entities - */ -export abstract class BaseGenerator extends BaseCommand { - protected abstract resourceName: string - protected abstract createExact: boolean - protected abstract getStub(): string - protected abstract getDestinationPath(): string - - protected suffix?: string - protected extname: string = '.ts' - protected form?: 'singular' | 'plural' - protected pattern?: 'camelcase' | 'snakecase' | 'pascalcase' - protected formIgnoreList?: string[] - protected templateData(): any { - return {} - } - - /** - * Returns path for a given namespace by replacing the base namespace - * with the defined directories map inside the `.adonisrc.json` - * file - */ - protected getPathForNamespace(namespaceFor: string): string | null { - return this.application.resolveNamespaceDirectory(namespaceFor) - } - - /** - * Returns contents of the rcFile - */ - protected async hasRcFile(cwd: string) { - const filePath = join(cwd, '.adonisrc.json') - return pathExists(filePath) - } - - /** - * Handle command - */ - public async generate() { - const hasRcFile = await this.hasRcFile(this.application.appRoot) - - /** - * Ensure `.adonisrc.json` file exists - */ - if (!hasRcFile) { - this.logger.error('Make sure your project root has ".adonisrc.json" file') - return - } - - const transformations = this.createExact - ? { - extname: this.extname, - } - : { - form: this.form, - suffix: this.suffix, - formIgnoreList: this.formIgnoreList, - pattern: this.pattern, - extname: this.extname, - } - - const file = this.generator - .addFile(this.resourceName, transformations) - .stub(this.getStub()) - .useMustache() - .destinationDir(this.getDestinationPath()) - .appRoot(this.application.appRoot) - .apply(this.templateData()) - - await this.generator.run() - return file - } -} diff --git a/commands/Make/Command.ts b/commands/Make/Command.ts deleted file mode 100644 index 0cfacdb..0000000 --- a/commands/Make/Command.ts +++ /dev/null @@ -1,75 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { join } from 'path' -import { args, flags } from '@adonisjs/core/build/standalone' -import { string } from '@poppinss/utils/build/helpers' - -import { BaseGenerator } from './Base' - -/** - * Command to make a new command - */ -export default class MakeCommand extends BaseGenerator { - /** - * Required by BaseGenerator - */ - protected pattern = 'pascalcase' as const - protected resourceName: string - protected createExact: boolean - - /** - * Command meta data - */ - public static commandName = 'make:command' - public static description = 'Make a new ace command' - - @args.string({ description: 'Name of the command class' }) - public name: string - - @flags.boolean({ - description: 'Create the command with the exact name as provided', - alias: 'e', - }) - public exact: boolean - - /** - * Returns the template stub based upon the `--resource` - * flag value - */ - protected getStub(): string { - return join(__dirname, '..', '..', 'templates', 'command.txt') - } - - /** - * Path to the commands directory - */ - protected getDestinationPath(): string { - return this.application.rcFile.directories.commands || 'commands' - } - - /** - * Passed down to the template. - */ - protected templateData() { - return { - toCommandName: () => { - return function (filename: string, render: any) { - return string.snakeCase(render(filename)).replace(/_/, ':') - } - }, - } - } - - public async run(): Promise { - this.resourceName = this.name - this.createExact = this.exact - await super.generate() - } -} diff --git a/commands/Make/Controller.ts b/commands/Make/Controller.ts deleted file mode 100644 index 813680d..0000000 --- a/commands/Make/Controller.ts +++ /dev/null @@ -1,86 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { join } from 'path' -import { args, flags } from '@adonisjs/core/build/standalone' -import { BaseGenerator } from './Base' - -/** - * Command to make a new HTTP Controller - */ -export default class MakeController extends BaseGenerator { - /** - * Required by BaseGenerator - */ - protected suffix = 'Controller' - protected form = 'plural' as const - protected pattern = 'pascalcase' as const - protected resourceName: string - protected createExact: boolean - - /** - * Do not pluralize following controller names - */ - protected formIgnoreList = [ - 'Home', - 'Auth', - 'Login', - 'Authentication', - 'Adonis', - 'Dashboard', - 'Signup', - 'Api', - ] - - /** - * Command meta data - */ - public static commandName = 'make:controller' - public static description = 'Make a new HTTP controller' - - @args.string({ description: 'Name of the controller class' }) - public name: string - - @flags.boolean({ description: 'Add resourceful methods to the controller class', alias: 'r' }) - public resource: boolean - - @flags.boolean({ - description: 'Create the controller with the exact name as provided', - alias: 'e', - }) - public exact: boolean - - /** - * Returns the template stub based upon the `--resource` - * flag value - */ - protected getStub(): string { - return join( - __dirname, - '..', - '..', - 'templates', - this.resource ? 'resource-controller.txt' : 'controller.txt' - ) - } - - /** - * Pull path from the `httpControllers` directory declaration from - * the `.adonisrc.json` file or fallback to `app/Controllers/Http` - */ - protected getDestinationPath(): string { - return this.getPathForNamespace('httpControllers') || 'app/Controllers/Http' - } - - public async run() { - this.resourceName = this.name - this.createExact = this.exact - await super.generate() - } -} diff --git a/commands/Make/Exception.ts b/commands/Make/Exception.ts deleted file mode 100644 index 0421af7..0000000 --- a/commands/Make/Exception.ts +++ /dev/null @@ -1,71 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { join } from 'path' -import { args, flags } from '@adonisjs/core/build/standalone' -import { BaseGenerator } from './Base' - -/** - * Command to make a new event exceptions class - */ -export default class MakeException extends BaseGenerator { - /** - * Required by BaseGenerator - */ - protected form = 'singular' as const - protected pattern = 'pascalcase' as const - protected resourceName: string - protected suffix = 'Exception' - protected createExact: boolean - - /** - * Command meta data - */ - public static commandName = 'make:exception' - public static description = 'Make a new custom exception class' - - @args.string({ description: 'Name of the exception class' }) - public name: string - - @flags.boolean({ description: 'Add the handle method to self handle the exception' }) - public selfHandle: boolean - - @flags.boolean({ - description: 'Create the exception class with the exact name as provided', - alias: 'e', - }) - public exact: boolean - - /** - * Returns the template stub - */ - protected getStub(): string { - return join( - __dirname, - '..', - '..', - 'templates', - this.selfHandle ? 'self-handle-exception.txt' : 'exception.txt' - ) - } - - /** - * Pull path from the `exceptions` namespace declaration from - * the `.adonisrc.json` file or fallback to `app/Exceptions` - */ - protected getDestinationPath(): string { - return this.getPathForNamespace('exceptions') || 'app/Exceptions' - } - - public async run() { - this.resourceName = this.name - this.createExact = this.exact - await super.generate() - } -} diff --git a/commands/Make/Listener.ts b/commands/Make/Listener.ts deleted file mode 100644 index f6920b0..0000000 --- a/commands/Make/Listener.ts +++ /dev/null @@ -1,61 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { join } from 'path' -import { args, flags } from '@adonisjs/core/build/standalone' -import { BaseGenerator } from './Base' - -/** - * Command to make a new event listener class - */ -export default class MakeListener extends BaseGenerator { - /** - * Required by BaseGenerator - */ - protected form = 'singular' as const - protected pattern = 'pascalcase' as const - protected resourceName: string - protected createExact: boolean - - /** - * Command meta data - */ - public static commandName = 'make:listener' - public static description = 'Make a new event listener class' - - @args.string({ description: 'Name of the event listener class' }) - public name: string - - @flags.boolean({ - description: 'Create the listener with the exact name as provided', - alias: 'e', - }) - public exact: boolean - - /** - * Returns the template stub - */ - protected getStub(): string { - return join(__dirname, '..', '..', 'templates', 'event-listener.txt') - } - - /** - * Pull path from the `listeners` directory declaration from - * the `.adonisrc.json` file or fallback to `app/Listeners` - */ - protected getDestinationPath(): string { - return this.getPathForNamespace('eventListeners') || 'app/Listeners' - } - - public async run() { - this.resourceName = this.name - this.createExact = this.exact - await super.generate() - } -} diff --git a/commands/Make/Middleware.ts b/commands/Make/Middleware.ts deleted file mode 100644 index af65627..0000000 --- a/commands/Make/Middleware.ts +++ /dev/null @@ -1,83 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { join } from 'path' -import { args, flags } from '@adonisjs/core/build/standalone' -import { BaseGenerator } from './Base' - -/** - * Command to make a new middleware - */ -export default class MakeMiddleware extends BaseGenerator { - /** - * Required by BaseGenerator - */ - protected suffix = '' - protected form = 'singular' as const - protected pattern = 'pascalcase' as const - protected resourceName: string - protected createExact: boolean - - /** - * Command meta data - */ - public static commandName = 'make:middleware' - public static description = 'Make a new middleware' - - @args.string({ description: 'Name of the middleware class' }) - public name: string - - @flags.boolean({ - description: 'Create the middleware with the exact name as provided', - alias: 'e', - }) - public exact: boolean - - /** - * Returns the template stub path - */ - protected getStub(): string { - return join(__dirname, '..', '..', 'templates', 'middleware.txt') - } - - /** - * Middleware are always created inside `app/Middleware` directory. - * We can look into configuring it later. - */ - protected getDestinationPath(): string { - return this.getPathForNamespace('middleware') || 'app/Middleware' - } - - public async run() { - this.resourceName = this.name - this.createExact = this.exact - const middlewareNamespace = this.application.rcFile.namespaces.middleware || 'App/Middleware' - - const file = await super.generate() - if (!file) { - return - } - - const fileJSON = file.toJSON() - - if (fileJSON.state === 'persisted') { - this.ui - .instructions() - .heading('Register middleware') - .add(`Open ${this.colors.cyan('start/kernel.ts')} file`) - .add(`Register the following function as a global or a named middleware`) - .add( - this.colors - .cyan() - .underline(`() => import('${middlewareNamespace}/${fileJSON.filename}')`) - ) - .render() - } - } -} diff --git a/commands/Make/PreloadFile.ts b/commands/Make/PreloadFile.ts deleted file mode 100644 index dd8237a..0000000 --- a/commands/Make/PreloadFile.ts +++ /dev/null @@ -1,143 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import slash from 'slash' -import { join, extname } from 'path' -import { args, flags } from '@adonisjs/core/build/standalone' - -import { BaseGenerator } from './Base' -import type { AppEnvironments } from '@ioc:Adonis/Core/Application' - -const ALLOWED_ENVIRONMENTS: AppEnvironments[] = ['console', 'web', 'repl', 'test'] - -/** - * Command to make a new preloaded file - */ -export default class MakePreloadFile extends BaseGenerator { - /** - * Required by BaseGenerator - */ - protected resourceName: string - protected createExact = true - - /** - * Command name - */ - public static commandName = 'make:prldfile' - - /** - * Command description - */ - public static description = 'Make a new preload file' - - @args.string({ description: 'Name of the file' }) - public name: string - - @flags.array({ - description: `Define the preload file environment. Accepted values "${ALLOWED_ENVIRONMENTS}"`, - }) - public environment: AppEnvironments[] - - /** - * Check if the mentioned environments are valid - */ - private isValidEnviroment(environment: string[]): environment is AppEnvironments[] { - return !environment.find((one) => !ALLOWED_ENVIRONMENTS.includes(one as any)) - } - - /** - * Returns the template stub path - */ - protected getStub(): string { - return join(__dirname, '..', '..', 'templates', 'preload-file.txt') - } - - /** - * Path to the start directory - */ - protected getDestinationPath(): string { - return this.application.rcFile.directories.start || 'start' - } - - /** - * Run command - */ - public async run() { - /** - * Ensure the environments are valid when provided via flag - */ - if (this.environment && this.environment.length && !this.isValidEnviroment(this.environment)) { - this.logger.error( - `Invalid environment(s) "${this.environment}". Only "${ALLOWED_ENVIRONMENTS}" are allowed` - ) - return - } - - let environments: string[] = this.environment - - /** - * Prompt user to select one or more environments - */ - if (!environments) { - environments = await this.prompt.multiple( - 'Select the environment(s) in which you want to load this file', - [ - { - name: 'all', - message: 'Load file in all environments', - }, - { - name: 'console', - message: 'Environment for ace commands', - }, - { - name: 'repl', - message: 'Environment for the REPL session', - }, - { - name: 'web', - message: 'Environment for HTTP requests', - }, - { - name: 'test', - message: 'Environment for the test process', - }, - ] - ) - } - - /** - * Generate resource file - */ - this.resourceName = this.name - const file = await super.generate() - - if (!file) { - return - } - - /** - * Update preload file - */ - const { files } = await import('@adonisjs/sink') - const relativePath = file.toJSON().relativepath - const rcFile = new files.AdonisRcFile(this.application.appRoot) - - if (!environments || !environments.length || environments.includes('all')) { - rcFile.setPreload(`./${slash(relativePath).replace(extname(relativePath), '')}`) - } else { - rcFile.setPreload( - `./${slash(relativePath).replace(extname(relativePath), '')}`, - environments as AppEnvironments[] - ) - } - - rcFile.commit() - } -} diff --git a/commands/Make/Provider.ts b/commands/Make/Provider.ts deleted file mode 100644 index 3e4f897..0000000 --- a/commands/Make/Provider.ts +++ /dev/null @@ -1,82 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import slash from 'slash' -import { join, extname } from 'path' -import { args, flags } from '@adonisjs/core/build/standalone' - -import { BaseGenerator } from './Base' - -/** - * Command to make a new provider - */ -export default class MakeProvider extends BaseGenerator { - /** - * Required by BaseGenerator - */ - protected suffix = 'Provider' - protected form = 'singular' as const - protected pattern = 'pascalcase' as const - protected resourceName: string - protected createExact: boolean - - /** - * Command meta data - */ - public static commandName = 'make:provider' - public static description = 'Make a new provider class' - - @args.string({ description: 'Name of the provider class' }) - public name: string - - @flags.boolean({ description: 'Register provider under the ace providers array' }) - public ace: boolean - - @flags.boolean({ - description: 'Create the provider with the exact name as provided', - alias: 'e', - }) - public exact: boolean - - /** - * Returns the template stub path - */ - protected getStub(): string { - return join(__dirname, '..', '..', 'templates', 'provider.txt') - } - - /** - * Path to the providers directory - */ - protected getDestinationPath(): string { - return this.application.rcFile.directories.providers || 'providers' - } - - public async run() { - this.resourceName = this.name - this.createExact = this.exact - const file = await super.generate() - - if (!file) { - return - } - - const { files } = await import('@adonisjs/sink') - const relativePath = file.toJSON().relativepath - const rcFile = new files.AdonisRcFile(this.application.appRoot) - - if (this.ace) { - rcFile.addAceProvider(`./${slash(relativePath).replace(extname(relativePath), '')}`) - } else { - rcFile.addProvider(`./${slash(relativePath).replace(extname(relativePath), '')}`) - } - - rcFile.commit() - } -} diff --git a/commands/Make/Suite.ts b/commands/Make/Suite.ts deleted file mode 100644 index 6859718..0000000 --- a/commands/Make/Suite.ts +++ /dev/null @@ -1,120 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) AdonisJS - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { BaseCommand, args, flags } from '@adonisjs/core/build/standalone' -import { files, logger } from '@adonisjs/sink' -import globParent from 'glob-parent' -import { join } from 'path' - -/** - * Create a new test suite - */ -export default class CreateSuite extends BaseCommand { - public static commandName = 'make:suite' - public static description = 'Create a new test suite' - - /** - * Name of the test suite to be created - */ - @args.string({ description: 'Name of the test suite' }) - public suite: string - - /** - * Glob pattern for the test suite, or only location to the test suite - */ - @args.string({ description: 'Path to the test suite directory', required: false }) - public location: string = '' - - /** - * Should add a sample test file - */ - @flags.boolean({ description: 'Add a sample test file' }) - public withExampleTest: boolean = true - - /** - * Get the destination path for the sample test file - */ - private getExampleTestDestinationPath() { - return globParent(this.location) + '/test.spec.ts' - } - - /** - * Generate suite glob pattern based on `location` argument - */ - private generateSuiteGlobPattern() { - if (!this.location) { - this.location = `tests/${this.suite}` - } - - if (!['*', '.js', '.ts'].find((keyword) => this.location.includes(keyword))) { - this.location = `${this.location}/**/*.spec(.ts|.js)` - } - } - - /** - * Check if the suite name is already defined in RcFile - */ - private checkIfSuiteExists(rcFile: files.AdonisRcFile) { - const existingSuites = rcFile.get('tests.suites') || [] - const existingSuitesNames = existingSuites.map((suite) => suite.name) - - return existingSuitesNames.includes(this.suite) - } - - /** - * Add the new test suite to the AdonisRC File and save it - */ - private async addSuiteToRcFile() { - const rcFile = new files.AdonisRcFile(this.application.appRoot) - const existingSuites = rcFile.get('tests.suites') || [] - - if (this.checkIfSuiteExists(rcFile)) { - return logger.action('update').skipped(`Suite ${this.suite} already exists`) - } - - rcFile.set('tests.suites', [ - ...existingSuites, - { - name: this.suite, - files: [this.location], - timeout: 60 * 1000, - }, - ]) - - rcFile.commit() - logger.action('update').succeeded('.adonisrc.json') - } - - /** - * Add a sample test file to the new suite folder - */ - private createSampleTestFile() { - const path = this.getExampleTestDestinationPath() - const testFile = new files.MustacheFile( - this.application.appRoot, - path, - join(__dirname, '../..', 'templates/test.txt') - ) - - if (!testFile.exists()) { - testFile.apply({}).commit() - logger.action('create').succeeded(path) - } - } - - public async run() { - this.generateSuiteGlobPattern() - - await this.addSuiteToRcFile() - - if (this.withExampleTest) { - this.createSampleTestFile() - } - } -} diff --git a/commands/Make/Test.ts b/commands/Make/Test.ts deleted file mode 100644 index a08506e..0000000 --- a/commands/Make/Test.ts +++ /dev/null @@ -1,88 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { join } from 'path' -import globParent from 'glob-parent' -import { string } from '@poppinss/utils/build/helpers' -import { args, flags } from '@adonisjs/core/build/standalone' -import { BaseGenerator } from './Base' - -/** - * Command to make a new test - */ -export default class MakeTest extends BaseGenerator { - /** - * Required by BaseGenerator - */ - protected extname = '.spec.ts' - protected form = 'singular' as const - protected pattern = 'snakecase' as const - protected resourceName: string - protected createExact: boolean - - /** - * Command meta data - */ - public static commandName = 'make:test' - public static description = 'Make a new test' - - @args.string({ description: 'Name of the test suite' }) - public suite: string - - @args.string({ description: 'Name of the test file' }) - public name: string - - @flags.boolean({ - description: 'Create the test file with the exact name as provided', - alias: 'e', - }) - public exact: boolean - - /** - * Returns the template stub path - */ - protected getStub(): string { - return join(__dirname, '..', '..', 'templates', 'test.txt') - } - - /** - * The file is created inside the parent directory of the first - * glob pattern - */ - protected getDestinationPath(): string { - const testSuites = this.application.rcFile.tests.suites - const mentionedSuite = testSuites.find(({ name }) => this.suite === name)! - const suiteGlob = Array.isArray(mentionedSuite.files) - ? mentionedSuite.files[0] - : mentionedSuite.files - - return globParent(suiteGlob) - } - - protected templateData() { - return { - name: string.sentenceCase(this.name), - } - } - - public async run() { - const testSuites = this.application.rcFile.tests.suites - const mentionedSuite = testSuites.find(({ name }) => this.suite === name)! - if (!mentionedSuite) { - this.logger.error( - `Invalid suite "${this.suite}". Make sure the suite is registered inside the .adonisrc.json file` - ) - return - } - - this.resourceName = this.name - this.createExact = this.exact - await super.generate() - } -} diff --git a/commands/Make/Validator.ts b/commands/Make/Validator.ts deleted file mode 100644 index 333d9be..0000000 --- a/commands/Make/Validator.ts +++ /dev/null @@ -1,62 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { join } from 'path' -import { args, flags } from '@adonisjs/core/build/standalone' -import { BaseGenerator } from './Base' - -/** - * Command to make a new validator - */ -export default class MakeValidator extends BaseGenerator { - /** - * Required by BaseGenerator - */ - protected suffix = 'Validator' - protected form = 'singular' as const - protected pattern = 'pascalcase' as const - protected resourceName: string - protected createExact: boolean - - /** - * Command meta data - */ - public static commandName = 'make:validator' - public static description = 'Make a new validator' - - @args.string({ description: 'Name of the validator class' }) - public name: string - - @flags.boolean({ - description: 'Create the validator with the exact name as provided', - alias: 'e', - }) - public exact: boolean - - /** - * Returns the template stub path - */ - protected getStub(): string { - return join(__dirname, '..', '..', 'templates', 'validator.txt') - } - - /** - * Pull path for the `validators` directory declaration from - * the `.adonisrc.json` file or fallback to `app/Validators` - */ - protected getDestinationPath(): string { - return this.getPathForNamespace('validators') || 'app/Validators' - } - - public async run() { - this.resourceName = this.name - this.createExact = this.exact - await super.generate() - } -} diff --git a/commands/Make/View.ts b/commands/Make/View.ts deleted file mode 100644 index ae101b1..0000000 --- a/commands/Make/View.ts +++ /dev/null @@ -1,61 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { join } from 'path' -import { args, flags } from '@adonisjs/core/build/standalone' -import { BaseGenerator } from './Base' - -/** - * Command to make a new view - */ -export default class MakeView extends BaseGenerator { - /** - * Required by BaseGenerator - */ - protected suffix = '' - protected extname = '.edge' - protected pattern = 'snakecase' as const - protected resourceName: string - protected createExact: boolean - - /** - * Command meta data - */ - public static commandName = 'make:view' - public static description = 'Make a new view template' - - @args.string({ description: 'Name of the view' }) - public name: string - - @flags.boolean({ - description: 'Create the template file with the exact name as provided', - alias: 'e', - }) - public exact: boolean - - /** - * Returns the template stub path - */ - protected getStub(): string { - return join(__dirname, '..', '..', 'templates', 'view.txt') - } - - /** - * Path to the providers directory - */ - protected getDestinationPath(): string { - return this.application.rcFile.directories.views || 'resources/views' - } - - public async run() { - this.resourceName = this.name - this.createExact = this.exact - await super.generate() - } -} diff --git a/commands/Serve.ts b/commands/Serve.ts deleted file mode 100644 index 1cf608b..0000000 --- a/commands/Serve.ts +++ /dev/null @@ -1,88 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { BaseCommand, flags } from '@adonisjs/core/build/standalone' - -/** - * Compile typescript project to Javascript and start - * the HTTP server - */ -export default class Serve extends BaseCommand { - public static commandName = 'serve' - public static description = - 'Start the AdonisJS HTTP server, along with the file watcher. Also starts the webpack dev server when webpack encore is installed' - - public static settings = { - stayAlive: true, - } - - /** - * Bundle frontend assets. Defaults to true - */ - @flags.boolean({ - description: 'Start webpack dev server when encore is installed. Use "--no-assets" to disable', - }) - public assets: boolean = true - - /** - * Allows watching for file changes - */ - @flags.boolean({ - description: 'Watch for file changes and re-start the HTTP server on change', - alias: 'w', - }) - public watch: boolean - - /** - * Detect changes by polling files - */ - @flags.boolean({ - description: 'Detect file changes by polling files instead of listening to filesystem events', - alias: 'p', - }) - public poll: boolean - - /** - * Arguments to pass to the `node` binary - */ - @flags.array({ description: 'CLI options to pass to the node command line' }) - public nodeArgs: string[] = [] - - /** - * Arguments to pass to the `encore` binary - */ - @flags.array({ description: 'CLI options to pass to the encore command line' }) - public encoreArgs: string[] = [] - - public async run() { - const { DevServer } = await import('../src/DevServer') - - try { - if (this.watch) { - await new DevServer( - this.application.appRoot, - this.nodeArgs, - this.encoreArgs, - this.assets, - this.logger - ).watch(this.poll) - } else { - await new DevServer( - this.application.appRoot, - this.nodeArgs, - this.encoreArgs, - this.assets, - this.logger - ).start() - } - } catch (error) { - this.logger.fatal(error) - } - } -} diff --git a/commands/Test.ts b/commands/Test.ts deleted file mode 100644 index ca79310..0000000 --- a/commands/Test.ts +++ /dev/null @@ -1,158 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { BaseCommand, flags, args } from '@adonisjs/core/build/standalone' -import { JapaFlags } from '../src/Contracts' - -/** - * Run tests - */ -export default class Test extends BaseCommand { - public static commandName = 'test' - public static description = 'Run AdonisJS tests' - public static settings = { - stayAlive: true, - } - - @args.spread({ description: 'Run tests for only the specified suites', required: false }) - public suites: string[] - - /** - * Allows watching for file changes - */ - @flags.array({ - description: 'Run tests for the mentioned files only', - }) - public files: string[] - - /** - * Allows watching for file changes - */ - @flags.boolean({ - description: 'Watch for file changes and re-run tests on file change', - alias: 'w', - }) - public watch: boolean - - /** - * Detect changes by polling files - */ - @flags.boolean({ - description: 'Detect file changes by polling files instead of listening to filesystem events', - alias: 'p', - }) - public poll: boolean - - /** - * Arguments to pass to the `node` binary - */ - @flags.array({ description: 'CLI options to pass to the node command line' }) - public nodeArgs: string[] = [] - - /** - * Filter by tags - */ - @flags.array({ description: 'Filter tests by tags' }) - public tags: string[] - - /** - * Filter by tags - */ - @flags.array({ description: 'Filter tests by ignoring tags' }) - public ignoreTags: string[] - - /** - * Filter by test title - */ - @flags.array({ description: 'Filter tests by title' }) - public tests: string[] - - /** - * Filter by group title - */ - @flags.array({ description: 'Filter tests by group title' }) - public groups: string[] - - /** - * Customize tests timeout - */ - @flags.number({ description: 'Customize tests timeout' }) - public timeout: number - - /** - * Force exit the tests runner - */ - @flags.boolean({ description: 'Force exit the tests runner process' }) - public forceExit: boolean - - /** - * Convert command flags to test filters - */ - private getTestFilters() { - const filters: JapaFlags = {} - if (this.forceExit) { - filters['--force-exit'] = true - } - - if (this.files) { - filters['--files'] = this.files - } - - if (this.timeout !== undefined) { - filters['--timeout'] = this.timeout - } - - if (this.tags) { - filters['--tags'] = this.tags - } - - if (this.suites) { - filters._ = this.suites - } - - if (this.ignoreTags) { - filters['--ignore-tags'] = this.ignoreTags - } - - if (this.tests) { - filters['--tests'] = this.tests - } - - if (this.groups) { - filters['--groups'] = this.groups - } - - return filters - } - - public async run() { - const { TestsServer } = await import('../src/Test') - - try { - if (this.watch) { - await new TestsServer( - this.application.appRoot, - this.getTestFilters(), - this.nodeArgs, - this.logger - ).watch() - } else { - await new TestsServer( - this.application.appRoot, - this.getTestFilters(), - this.nodeArgs, - this.logger - ).run() - } - } catch (error) { - this.exitCode = 1 - this.logger.fatal(error) - } - } -} diff --git a/commands/TypeCheck.ts b/commands/TypeCheck.ts deleted file mode 100644 index a790099..0000000 --- a/commands/TypeCheck.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { BaseCommand, flags } from '@adonisjs/core/build/standalone' -import { TSCONFIG_FILE_NAME } from '../config/paths' - -/** - * TypeCheck project without writing the compiled output to the disk - */ -export default class TypeCheck extends BaseCommand { - public static commandName = 'type-check' - public static description = - 'Type check TypeScript source without writing the compiled output on disk' - - /** - * Path to the TypeScript project configuration file. Defaults to "tsconfig.json" - */ - @flags.string({ - description: 'Path to the TypeScript project configuration file', - }) - public tsconfig: string = TSCONFIG_FILE_NAME - - /** - * Invoked automatically by ace - */ - public async run() { - const { Compiler } = await import('../src/Compiler') - - try { - const compiler = new Compiler(this.application.appRoot, [], false, this.logger, this.tsconfig) - const success = await compiler.typeCheck() - - /** - * Set exitCode based upon the typecheck status - */ - if (!success) { - this.exitCode = 1 - } - } catch (error) { - this.logger.fatal(error) - this.exitCode = 1 - } - } -} diff --git a/config/paths.ts b/config/paths.ts deleted file mode 100644 index e599df4..0000000 --- a/config/paths.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -export const ACE_FILE_NAME = 'ace' -export const DEFAULT_BUILD_DIR = 'build' -export const RCFILE_NAME = '.adonisrc.json' -export const ENV_FILES = ['.env', '.env.testing'] -export const SERVER_ENTRY_FILE = 'server.ts' -export const TESTS_ENTRY_FILE = 'test.ts' -export const TSCONFIG_FILE_NAME = 'tsconfig.json' diff --git a/register.ts b/index.ts similarity index 54% rename from register.ts rename to index.ts index b709b29..c255cae 100644 --- a/register.ts +++ b/index.ts @@ -1,11 +1,11 @@ /* * @adonisjs/assembler * - * (c) Harminder Virk + * (c) AdonisJS * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -import register from './src/requireHook' -register(process.env.ADONIS_ACE_CWD || process.cwd()) +export { Bundler } from './src/bundler.js' +export { DevServer } from './src/dev_server.js' diff --git a/package.json b/package.json index e57f73a..73d60da 100644 --- a/package.json +++ b/package.json @@ -1,135 +1,87 @@ { "name": "@adonisjs/assembler", "version": "5.9.5", - "description": "Core commands to compiler and build AdonisJs project", - "main": "build/ace-manifest.json", + "description": "Provides utilities to run AdonisJS development server and build project for production", + "main": "build/index.js", + "type": "module", "files": [ - "build/commands", - "build/config", - "build/templates", "build/src", - "build/register.js", - "build/register.d.ts", - "build/ace-manifest.json" + "build/index.d.ts", + "build/index.js" ], + "exports": { + ".": "./build/index.js", + "./types": "./build/src/types.js" + }, "scripts": { - "mrm": "mrm --preset=@adonisjs/mrm-preset", "pretest": "npm run lint", - "test": "cross-env FORCE_COLOR=true node -r @adonisjs/require-ts/build/register ./bin/test.ts", + "test": "cross-env NODE_DEBUG=adonisjs:assembler c8 npm run vscode:test", "lint": "eslint . --ext=.ts", "clean": "del-cli build", "compile": "npm run lint && npm run clean && tsc", - "build": "npm run compile && node build/bin/index.js && copyfiles \"templates/**\" build", - "commit": "git-cz", - "release": "np --message=\"chore(release): %s\"", + "build": "npm run compile", + "release": "np", "version": "npm run build", - "sync-labels": "github-label-sync --labels ./node_modules/@adonisjs/mrm-preset/gh-labels.json adonisjs/assembler", + "sync-labels": "github-label-sync --labels .github/labels.json adonisjs/assembler", "format": "prettier --write .", - "prepublishOnly": "npm run build" - }, - "repository": { - "type": "git", - "url": "git+ssh://git@github.com/adonisjs/assembler.git" + "prepublishOnly": "npm run build", + "vscode:test": "node --loader=ts-node/esm bin/test.ts" }, "keywords": [ "adonisjs", - "boot", "build", "ts" ], "author": "virk,adonisjs", "license": "MIT", - "bugs": { - "url": "https://github.com/adonisjs/assembler/issues" - }, - "homepage": "https://github.com/adonisjs/assembler#readme", "devDependencies": { - "@adonisjs/ace": "^11.3.1", - "@adonisjs/core": "^5.8.9", - "@adonisjs/mrm-preset": "^5.0.3", - "@japa/assert": "^1.3.6", - "@japa/run-failed-tests": "^1.1.0", - "@japa/runner": "^2.2.2", - "@japa/spec-reporter": "^1.3.2", + "@adonisjs/env": "^4.2.0-0", + "@commitlint/cli": "^17.4.4", + "@commitlint/config-conventional": "^17.4.4", + "@japa/assert": "^1.4.1", + "@japa/file-system": "^1.0.1", + "@japa/run-failed-tests": "^1.1.1", + "@japa/runner": "^2.5.1", + "@japa/spec-reporter": "^1.3.3", + "@poppinss/cliui": "^6.1.1-0", "@poppinss/dev-utils": "^2.0.3", - "@types/node": "^18.11.9", - "commitizen": "^4.2.5", + "@swc/core": "^1.3.36", + "@types/node": "^18.14.1", + "c8": "^7.13.0", "copyfiles": "^2.4.1", "cross-env": "^7.0.3", - "cz-conventional-changelog": "^3.3.0", "del-cli": "^5.0.0", - "eslint": "^8.27.0", - "eslint-config-prettier": "^8.5.0", - "eslint-plugin-adonis": "^2.1.1", + "eslint": "^8.34.0", + "eslint-config-prettier": "^8.6.0", + "eslint-plugin-adonis": "^3.0.3", "eslint-plugin-prettier": "^4.2.1", "github-label-sync": "^2.2.0", - "husky": "^8.0.2", - "mrm": "^4.1.13", - "np": "^7.6.2", - "prettier": "^2.7.1", - "typescript": "^4.9.3" - }, - "nyc": { - "exclude": [ - "test" - ], - "extension": [ - ".ts" - ] - }, - "husky": { - "hooks": { - "commit-msg": "node ./node_modules/@adonisjs/mrm-preset/validateCommit/conventional/validate.js" - } - }, - "config": { - "commitizen": { - "path": "cz-conventional-changelog" - } - }, - "np": { - "contents": ".", - "anyBranch": false + "husky": "^8.0.3", + "np": "^7.6.3", + "p-event": "^5.0.1", + "prettier": "^2.8.4", + "ts-node": "^10.9.1", + "typescript": "^4.9.5" }, "dependencies": { - "@adonisjs/application": "^5.2.5", - "@adonisjs/env": "^3.0.9", - "@adonisjs/ioc-transformer": "^2.3.4", - "@adonisjs/require-ts": "^2.0.13", - "@adonisjs/sink": "^5.4.2", - "@poppinss/chokidar-ts": "^3.3.5", - "@poppinss/cliui": "^3.0.5", - "@poppinss/utils": "^5.0.0", - "cpy": "^8.1.2", - "emittery": "^0.13.1", - "execa": "^5.1.1", - "fs-extra": "^10.1.0", - "get-port": "^5.1.1", - "glob-parent": "^6.0.2", - "has-yarn": "^2.1.0", + "@poppinss/chokidar-ts": "^4.0.0-0", + "@types/fs-extra": "^11.0.1", + "@types/picomatch": "^2.3.0", + "cpy": "^9.0.1", + "execa": "^7.0.0", + "fs-extra": "^11.1.0", + "get-port": "^6.1.2", "picomatch": "^2.3.1", - "slash": "^3.0.0" + "slash": "^5.0.0" }, - "peerDependencies": { - "@adonisjs/core": "^5.1.0" - }, - "publishConfig": { - "access": "public", - "tag": "latest" + "repository": { + "type": "git", + "url": "git+ssh://git@github.com/adonisjs/assembler.git" }, - "mrmConfig": { - "core": true, - "license": "MIT", - "services": [ - "github-actions" - ], - "minNodeVersion": "14.15.4", - "probotApps": [ - "stale", - "lock" - ], - "runGhActionsOnWindows": true + "bugs": { + "url": "https://github.com/adonisjs/assembler/issues" }, + "homepage": "https://github.com/adonisjs/assembler#readme", "eslintConfig": { "extends": [ "plugin:adonis/typescriptPackage", @@ -159,5 +111,31 @@ "bracketSpacing": true, "arrowParens": "always", "printWidth": 100 + }, + "commitlint": { + "extends": [ + "@commitlint/config-conventional" + ] + }, + "publishConfig": { + "access": "public", + "tag": "next" + }, + "np": { + "message": "chore(release): %s", + "tag": "next", + "branch": "main", + "anyBranch": false + }, + "c8": { + "reporter": [ + "text", + "html" + ], + "exclude": [ + "tests/**", + "build/**", + "examples/**" + ] } } diff --git a/src/AssetsBundler/index.ts b/src/AssetsBundler/index.ts deleted file mode 100644 index 3390083..0000000 --- a/src/AssetsBundler/index.ts +++ /dev/null @@ -1,254 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import execa from 'execa' -import getPort from 'get-port' -import Emittery from 'emittery' -import { logger as uiLogger } from '@poppinss/cliui' -import { resolveDir } from '@poppinss/utils/build/helpers' - -export type DevServerResponse = - | { - state: 'not-installed' | 'no-assets' - } - | { - state: 'running' - port: string - host: string - } - -/** - * Assets bundler uses webpack encore to build frontend dependencies - */ -export class AssetsBundler extends Emittery { - /** - * Binary to execute - */ - private binaryName = 'encore' - - private encoreArgs: string[] = [] - - /** - * Options passed to spawn a child process - */ - private execaOptions = { - preferLocal: true, - buffer: false, - stdio: 'pipe' as const, - localDir: this.projectRoot, - cwd: this.projectRoot, - windowsHide: false, - env: { - FORCE_COLOR: 'true', - ...this.env, - }, - } - - constructor( - private projectRoot: string, - encoreArgs: string[] = [], - private buildAssets: boolean = true, - private logger: typeof uiLogger, - private env: { [key: string]: string } = {} - ) { - super() - this.encoreArgs = encoreArgs.reduce((result, arg) => { - result = result.concat(arg.split(' ')) - return result - }, [] as string[]) - } - - /** - * Find if encore is installed - */ - private isEncoreInstalled() { - try { - resolveDir(this.projectRoot, '@symfony/webpack-encore') - return true - } catch { - return false - } - } - - /** - * Notify user that we are about use encore - */ - private notifyAboutEncore() { - this.logger.info(`detected { ${this.logger.colors.dim().yellow('@symfony/webpack-encore')} }`) - this.logger.info( - `building frontend assets. Use { ${this.logger.colors - .dim() - .yellow('--no-assets')} } to disable` - ) - } - - /** - * Logs the line to stdout - */ - private log(line: Buffer | string) { - line = line.toString().trim() - if (!line.length) { - return - } - console.log(`[ ${this.logger.colors.cyan('encore')} ] ${line}`) - } - - /** - * Logs the line to stderr - */ - private logError(line: Buffer | string) { - line = line.toString().trim() - if (!line.length) { - return - } - console.error(`[ ${this.logger.colors.cyan('encore')} ] ${line}`) - } - - /** - * Returns the custom port defined using the `--port` flag in encore - * flags - */ - private findCustomPort(): undefined | string { - let portIndex = this.encoreArgs.findIndex((arg) => arg === '--port') - if (portIndex > -1) { - return this.encoreArgs[portIndex + 1] - } - - portIndex = this.encoreArgs.findIndex((arg) => arg.includes('--port')) - if (portIndex > -1) { - const tokens = this.encoreArgs[portIndex].split('=') - return tokens[1] && tokens[1].trim() - } - } - - /** - * Returns the custom host defined using the `--host` flag in encore - * flags - */ - private findCustomHost(): undefined | string { - let hostIndex = this.encoreArgs.findIndex((arg) => arg === '--host') - if (hostIndex > -1) { - return this.encoreArgs[hostIndex + 1] - } - - hostIndex = this.encoreArgs.findIndex((arg) => arg.includes('--host')) - if (hostIndex > -1) { - const tokens = this.encoreArgs[hostIndex].split('=') - return tokens[1] && tokens[1].trim() - } - } - - /** - * Execute command - */ - private exec(args: string[]): Promise { - return new Promise((resolve, reject) => { - const childProcess = execa(this.binaryName, args, this.execaOptions) - - childProcess.stdout?.on('data', (line: Buffer) => this.log(line)) - childProcess.stderr?.on('data', (line: Buffer) => this.logError(line)) - childProcess.on('error', (error) => reject(error)) - childProcess.on('close', (code) => { - if (code && code !== 0) { - reject(`Process exited with code ${code}`) - } else { - resolve() - } - }) - }) - } - - /** - * Build assets using encore - */ - public async build(): Promise<{ hasErrors: boolean }> { - if (!this.buildAssets) { - return { hasErrors: false } - } - - if (!this.isEncoreInstalled()) { - return { hasErrors: false } - } - - this.notifyAboutEncore() - - try { - await this.exec(['dev'].concat(this.encoreArgs)) - return { hasErrors: false } - } catch (error) { - return { hasErrors: true } - } - } - - /** - * Build assets for production - */ - public async buildForProduction(): Promise<{ hasErrors: boolean }> { - if (!this.buildAssets) { - return { hasErrors: false } - } - - if (!this.isEncoreInstalled()) { - return { hasErrors: false } - } - - this.notifyAboutEncore() - - try { - await this.exec(['production'].concat(this.encoreArgs)) - return { hasErrors: false } - } catch (error) { - return { hasErrors: true } - } - } - - /** - * Start the webpack dev server - */ - public async startDevServer(): Promise { - if (!this.isEncoreInstalled()) { - return { state: 'not-installed' } - } - - if (!this.buildAssets) { - return { state: 'no-assets' } - } - - const customHost = this.findCustomHost() || 'localhost' - - /** - * Define a random port when the "--port" flag is not passed. - * - * Encore anyways doesn't allow defining port inside the webpack.config.js - * file for generating the manifest and entrypoints file. - * - * @see - * https://github.com/symfony/webpack-encore/issues/941#issuecomment-787568811 - */ - let customPort = this.findCustomPort() - if (!customPort) { - const randomPort = await getPort({ port: 8080, host: 'localhost' }) - customPort = String(randomPort) - this.encoreArgs.push('--port', customPort) - } - - const childProcess = execa( - this.binaryName, - ['dev-server'].concat(this.encoreArgs), - this.execaOptions - ) - - childProcess.stdout?.on('data', (line: Buffer) => this.log(line)) - childProcess.stderr?.on('data', (line: Buffer) => this.logError(line)) - childProcess.on('close', (code, signal) => this.emit('close', { code, signal })) - childProcess.on('exit', (code, signal) => this.emit('exit', { code, signal })) - - return { state: 'running', port: customPort, host: customHost } - } -} diff --git a/src/Compiler/index.ts b/src/Compiler/index.ts deleted file mode 100644 index b8a97d0..0000000 --- a/src/Compiler/index.ts +++ /dev/null @@ -1,367 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import slash from 'slash' -import copyfiles from 'cpy' -import tsStatic from 'typescript' -import { join, relative } from 'path' -import { remove, outputJSON } from 'fs-extra' -import { iocTransformer } from '@adonisjs/ioc-transformer' -import { logger as uiLogger, instructions } from '@poppinss/cliui' - -import { Ts } from '../Ts' -import { RcFile } from '../RcFile' -import { Manifest } from '../Manifest' -import { RCFILE_NAME } from '../../config/paths' -import { AssetsBundler } from '../AssetsBundler' - -/** - * Exposes the API to build the AdonisJs project for development or - * production. The production build has it's own set of node_modules - */ -export class Compiler { - /** - * Reference to typescript compiler - */ - private ts: Ts - - /** - * Reference to rc File - */ - private rcFile = new RcFile(this.appRoot) - - constructor( - public appRoot: string, - private encoreArgs: string[], - private buildAssets: boolean, - private logger: typeof uiLogger = uiLogger, - tsconfig?: string - ) { - this.ts = new Ts(this.appRoot, this.logger, tsconfig) - this.ts.tsCompiler.use(() => { - return iocTransformer(this.ts.tsCompiler.ts, this.rcFile.application.rcFile) - }, 'after') - } - - /** - * Returns relative unix path from the project root. Used for - * display only - */ - private getRelativeUnixPath(absPath: string): string { - return slash(relative(this.appRoot, absPath)) - } - - /** - * Cleans up the build directory - */ - private async cleanupBuildDirectory(outDir: string) { - this.getRelativeUnixPath(outDir) - this.logger.info( - `cleaning up ${this.logger.colors - .dim() - .yellow(`"./${this.getRelativeUnixPath(outDir)}"`)} directory` - ) - await remove(outDir) - } - - /** - * Copies .adonisrc.json file to the destination - */ - private async copyAdonisRcFile(outDir: string) { - this.logger.info( - `copy { ${this.logger.colors - .dim() - .yellow(`${RCFILE_NAME} => ${this.getRelativeUnixPath(outDir)}`)} }` - ) - - await outputJSON( - join(outDir, RCFILE_NAME), - Object.assign({}, this.rcFile.getDiskContents(), { - typescript: false, - lastCompiledAt: new Date().toISOString(), - }), - { spaces: 2 } - ) - } - - /** - * Copy all meta files to the build directory - */ - private async copyMetaFiles(outDir: string, extraFiles?: string[]) { - const metaFiles = this.rcFile.getMetaFilesGlob().concat(extraFiles || []) - this.logger.info( - `copy { ${this.logger.colors - .dim() - .yellow(`${metaFiles.join(',')} => ${this.getRelativeUnixPath(outDir)}`)} }` - ) - await this.copyFiles(metaFiles, outDir) - } - - /** - * Copy files to destination directory - */ - private async copyFiles(files: string[], outDir: string) { - try { - await copyfiles(files, outDir, { cwd: this.appRoot, parents: true }) - } catch (error) { - if (!error.message.includes("the file doesn't exist")) { - throw error - } - } - } - - /** - * Build typescript source files - */ - private buildTypescriptSource(config: tsStatic.ParsedCommandLine): { - skipped: boolean - hasErrors: boolean - } { - this.logger.info('compiling typescript source files') - - const builder = this.ts.tsCompiler.builder(config) - const { skipped, diagnostics } = builder.build() - - if (skipped) { - this.logger.warning('typescript emit skipped') - } - - if (diagnostics.length) { - this.logger.error('typescript compiler errors') - this.ts.renderDiagnostics(diagnostics, builder.host) - } - - return { - skipped, - hasErrors: diagnostics.length > 0, - } - } - - /** - * Log the message that ts build and failed - */ - private logTsBuildFailed() { - this.logger.logError('') - this.logger.logError( - this.logger.colors.bgRed( - `Cannot complete the build process as there are typescript errors. Use "--ignore-ts-errors" flag to ignore Typescript errors` - ) - ) - } - - /** - * Typecheck the project without emit - */ - public async typeCheck(): Promise { - const config = this.ts.parseConfig() - if (!config) { - return false - } - - this.logger.info('type checking typescript source files') - - config.options.noEmit = true - const builder = this.ts.tsCompiler.builder(config) - const { diagnostics } = builder.build() - - if (diagnostics.length) { - this.logger.error('typescript compiler errors') - this.ts.renderDiagnostics(diagnostics, builder.host) - return false - } - - this.logger.success('built successfully') - return true - } - - /** - * Compile project. See [[Compiler.compileForProduction]] for - * production build - */ - public async compile(stopOnError: boolean = true, extraFiles?: string[]): Promise { - const config = this.ts.parseConfig() - if (!config) { - return false - } - - /** - * Bundle frontend assets when encore is installed - */ - const encore = await new AssetsBundler( - this.appRoot, - this.encoreArgs, - this.buildAssets, - this.logger - ).build() - - /** - * Skipped, coz of frontend errors - */ - if (encore.hasErrors) { - return false - } - - /** - * Always cleanup the out directory - */ - await this.cleanupBuildDirectory(config.options.outDir!) - - /** - * Build typescript source - */ - const ts = this.buildTypescriptSource(config) - - /** - * Do not continue when output was skipped - */ - if (ts.skipped) { - return false - } - - /** - * Do not continue when has errors and "stopOnError" is true - */ - if (stopOnError && ts.hasErrors) { - this.logTsBuildFailed() - await this.cleanupBuildDirectory(config.options.outDir!) - return false - } - - /** - * Begin by copying meta files - */ - await this.copyMetaFiles(config.options.outDir!, extraFiles) - - /** - * Copy `.adonisrc.json` file - */ - await this.copyAdonisRcFile(config.options.outDir!) - - /** - * Manifest instance to generate ace manifest file - */ - const manifest = new Manifest(config.options.outDir!, this.logger) - const created = await manifest.generate() - - /** - * Do not continue when unable to generate the manifest file as commands - * won't be available - */ - if (!created) { - await this.cleanupBuildDirectory(config.options.outDir!) - return false - } - - this.logger.success('built successfully') - return true - } - - /** - * Compile project. See [[Compiler.compile]] for development build - */ - public async compileForProduction( - stopOnError: boolean = true, - client: 'npm' | 'yarn' - ): Promise { - const config = this.ts.parseConfig() - if (!config) { - return false - } - - /** - * Bundle frontend assets when encore is installed - */ - const encore = await new AssetsBundler( - this.appRoot, - this.encoreArgs, - this.buildAssets, - this.logger - ).buildForProduction() - - /** - * Skipped, coz of frontend errors - */ - if (encore.hasErrors) { - return false - } - - const pkgFiles = - client === 'npm' ? ['package.json', 'package-lock.json'] : ['package.json', 'yarn.lock'] - - /** - * Always cleanup the out directory - */ - await this.cleanupBuildDirectory(config.options.outDir!) - - /** - * Build typescript source - */ - const { skipped, hasErrors } = this.buildTypescriptSource(config) - - /** - * Do not continue when output was skipped - */ - if (skipped) { - return false - } - - /** - * Do not continue when has errors and "stopOnError" is true and cleanup - * the build directory - */ - if (stopOnError && hasErrors) { - this.logTsBuildFailed() - await this.cleanupBuildDirectory(config.options.outDir!) - return false - } - - /** - * Begin by copying meta files - */ - await this.copyMetaFiles(config.options.outDir!, pkgFiles) - - /** - * Copy `.adonisrc.json` file - */ - await this.copyAdonisRcFile(config.options.outDir!) - - /** - * Generate commands manifest - */ - const manifest = new Manifest(config.options.outDir!, this.logger) - const created = await manifest.generate() - - /** - * Do not continue when unable to generate the manifest file as commands - * won't be available - */ - if (!created) { - await this.cleanupBuildDirectory(config.options.outDir!) - return false - } - - /** - * Print usage instructions - */ - const installCommand = client === 'npm' ? 'npm ci --production' : 'yarn install --production' - const relativeBuildPath = this.getRelativeUnixPath(config.options.outDir!) - - this.logger.success('built successfully') - this.logger.log('') - - instructions() - .heading('Run the following commands to start the server in production') - .add(this.logger.colors.cyan(`cd ${relativeBuildPath}`)) - .add(this.logger.colors.cyan(installCommand)) - .add(this.logger.colors.cyan('node server.js')) - .render() - - return true - } -} diff --git a/src/Contracts/index.ts b/src/Contracts/index.ts deleted file mode 100644 index a7aee0e..0000000 --- a/src/Contracts/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -export type JapaFlags = Partial<{ - '_': string[] - '--tags': string[] - '--ignore-tags': string[] - '--files': string[] - '--timeout': number - '--force-exit': boolean -}> diff --git a/src/DevServer/index.ts b/src/DevServer/index.ts deleted file mode 100644 index 60686d0..0000000 --- a/src/DevServer/index.ts +++ /dev/null @@ -1,388 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import getPort from 'get-port' -import { getWatcherHelpers } from '@adonisjs/require-ts' -import { logger as uiLogger, sticker } from '@poppinss/cliui' - -import { Ts } from '../Ts' -import { RcFile } from '../RcFile' -import { Manifest } from '../Manifest' -import { EnvParser } from '../EnvParser' -import { HttpServer } from '../HttpServer' - -import { ENV_FILES, SERVER_ENTRY_FILE } from '../../config/paths' -import { AssetsBundler, DevServerResponse } from '../AssetsBundler' - -/** - * Exposes the API to watch project for compilition changes. - */ -export class DevServer { - private httpServer: HttpServer - - /** - * HTTP server port - */ - private serverPort?: number - - /** - * HTTP server host - */ - private serverHost?: string - - /** - * Encore dev server host - */ - private encoreDevServerResponse: DevServerResponse - - /** - * A boolean to know if we are watching for filesystem - */ - private watchingFileSystem: boolean = false - - /** - * Watcher state - */ - private watcherState: 'pending' | 'error' | 'ready' = 'pending' - - /** - * Reference to the typescript compiler - */ - private ts = new Ts(this.appRoot, this.logger) - - /** - * Reference to the RCFile - */ - private rcFile = new RcFile(this.appRoot) - - /** - * Manifest instance to generate ace manifest file - */ - private manifest = new Manifest(this.appRoot, this.logger) - - /** - * Require-ts watch helpers - */ - private watchHelpers = getWatcherHelpers(this.appRoot) - - constructor( - private appRoot: string, - private nodeArgs: string[] = [], - private encoreArgs: string[], - private buildAssets: boolean, - private logger: typeof uiLogger = uiLogger - ) {} - - /** - * Kill current process - */ - private kill() { - this.logger.info('shutting down') - process.exit() - } - - /** - * Create the http server - */ - private async createHttpServer() { - if (this.httpServer) { - return - } - - const envParser = new EnvParser() - await envParser.parse(this.appRoot) - - const envOptions = envParser.asEnvObject(['PORT', 'TZ', 'HOST']) - const HOST = process.env.HOST || envOptions.HOST || '0.0.0.0' - let PORT = process.env.PORT || envOptions.PORT || '3333' - - /** - * Obtains a random port by giving preference to the one defined inside - * the `.env` file. This eases the process of running the application - * without manually changing ports inside the `.env` file when - * original port is in use. - */ - if (!isNaN(Number(PORT))) { - PORT = String( - await getPort({ - port: [Number(PORT)], - host: HOST, - }) - ) - } - - this.httpServer = new HttpServer(SERVER_ENTRY_FILE, this.appRoot, this.nodeArgs, this.logger, { - PORT, - HOST, - TZ: envOptions.TZ, - }) - } - - /** - * Renders box to notify about the server state - */ - private renderServerIsReady() { - if (!this.serverHost || !this.serverPort) { - return - } - - if (this.watchingFileSystem && this.watcherState === 'pending') { - return - } - - const stickerInstance = sticker() - - stickerInstance - .add( - `Server address: ${this.logger.colors.cyan( - `http://${this.serverHost === '0.0.0.0' ? '127.0.0.1' : this.serverHost}:${ - this.serverPort - }` - )}` - ) - .add( - `Watching filesystem for changes: ${this.logger.colors.cyan( - this.watchingFileSystem ? 'YES' : 'NO' - )}` - ) - - /** - * Running the encore dev server - */ - if (this.encoreDevServerResponse.state === 'running') { - stickerInstance.add( - `Encore server address: ${this.logger.colors.cyan( - `http://${this.encoreDevServerResponse.host}:${this.encoreDevServerResponse.port}` - )}` - ) - } - - stickerInstance.render() - } - - /** - * Start the dev server. Use [[watch]] to also watch for file - * changes - */ - public async start() { - /** - * Log getting ready - */ - this.logger.info('building project...') - - /** - * Start the HTTP server right away - */ - await this.createHttpServer() - this.httpServer.start() - - /** - * Notify that the http server has died - */ - this.httpServer.on('exit', ({ code }) => { - this.logger.warning(`Underlying HTTP server died with "${code} code"`) - }) - - /** - * Notify that the http server is running - */ - this.httpServer.on('ready', ({ port, host }) => { - this.serverPort = port - this.serverHost = host - this.renderServerIsReady() - }) - - const encore = new AssetsBundler(this.appRoot, this.encoreArgs, this.buildAssets, this.logger) - encore.on('exit', ({ code }) => { - this.logger.warning(`Underlying encore dev server died with "${code} code"`) - }) - - this.encoreDevServerResponse = await encore.startDevServer() - } - - /** - * Build and watch for file changes - */ - public async watch(poll = false) { - this.watchingFileSystem = true - - /** - * Clear require-ts cache - */ - this.watchHelpers.clear() - - /** - * Start HTTP server - */ - await this.start() - - /** - * Parse config to find the files excluded inside - * tsconfig file - */ - const config = this.ts.parseConfig() - if (!config) { - this.logger.warning('Cannot start watcher because of errors in the config file') - this.watcherState = 'error' - this.renderServerIsReady() - return - } - - /** - * Stick file watcher - */ - const watcher = this.ts.tsCompiler.watcher(config, 'raw') - - /** - * Watcher is ready after first compile - */ - watcher.on('watcher:ready', () => { - this.logger.info('watching file system for changes') - this.watcherState = 'ready' - this.renderServerIsReady() - }) - - /** - * Source file removed - */ - watcher.on('source:unlink', async ({ absPath, relativePath }) => { - this.watchHelpers.clear(absPath) - this.logger.action('delete').succeeded(relativePath) - - /** - * Generate manifest when filePath is a commands path - */ - if (this.rcFile.isCommandsPath(relativePath)) { - this.manifest.generate() - } - - this.httpServer.restart() - }) - - /** - * Source file added - */ - watcher.on('source:add', async ({ absPath, relativePath }) => { - this.watchHelpers.clear(absPath) - this.logger.action('add').succeeded(relativePath) - - /** - * Generate manifest when filePath if file is in commands path - */ - if (this.rcFile.isCommandsPath(relativePath)) { - this.manifest.generate() - } - - this.httpServer.restart() - }) - - /** - * Source file changed - */ - watcher.on('source:change', async ({ absPath, relativePath }) => { - this.watchHelpers.clear(absPath) - this.logger.action('update').succeeded(relativePath) - - /** - * Generate manifest when filePath is a commands path - */ - if (this.rcFile.isCommandsPath(relativePath)) { - this.manifest.generate() - } - - this.httpServer.restart() - }) - - /** - * New file added - */ - watcher.on('add', async ({ relativePath }) => { - if (ENV_FILES.includes(relativePath)) { - this.logger.action('create').succeeded(relativePath) - this.httpServer.restart() - return - } - - const metaData = this.rcFile.getMetaData(relativePath) - if (!metaData.metaFile) { - return - } - - this.logger.action('create').succeeded(relativePath) - if (metaData.reload) { - this.httpServer.restart() - } - }) - - /** - * File changed - */ - watcher.on('change', async ({ relativePath }) => { - if (ENV_FILES.includes(relativePath)) { - this.logger.action('update').succeeded(relativePath) - this.httpServer.restart() - return - } - - const metaData = this.rcFile.getMetaData(relativePath) - if (!metaData.metaFile) { - return - } - - this.logger.action('update').succeeded(relativePath) - - if (metaData.reload || metaData.rcFile) { - this.httpServer.restart() - } - }) - - /** - * File removed - */ - watcher.on('unlink', async ({ relativePath }) => { - if (ENV_FILES.includes(relativePath)) { - this.logger.action('delete').succeeded(relativePath) - this.httpServer.restart() - return - } - - const metaData = this.rcFile.getMetaData(relativePath) - if (!metaData.metaFile) { - return - } - - if (metaData.rcFile) { - this.logger.info('cannot continue after deletion of .adonisrc.json file') - watcher.chokidar.close() - this.kill() - return - } - - this.logger.action('delete').succeeded(relativePath) - if (metaData.reload) { - this.httpServer.restart() - } - }) - - /** - * Start the watcher - */ - watcher.watch(['.'], { - usePolling: poll, - }) - - /** - * Kill when watcher recieves an error - */ - watcher.chokidar.on('error', (error) => { - this.logger.fatal(error) - this.kill() - }) - } -} diff --git a/src/EnvParser/index.ts b/src/EnvParser/index.ts deleted file mode 100644 index 19f503a..0000000 --- a/src/EnvParser/index.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { EnvParser as Parser, envLoader } from '@adonisjs/env' - -/** - * Parses the env file inside the project root. - */ -export class EnvParser { - private envContents: any = {} - - constructor() {} - - /** - * Parse .env file contents - */ - public async parse(rootDir: string) { - const { envContents, testEnvContent } = envLoader(rootDir) - const envVars = new Parser(true).parse(envContents) - const testEnvVars = new Parser(true).parse(testEnvContent) - this.envContents = { ...envVars, ...testEnvVars } - } - - /** - * Returns value for a key inside the `.env` file - */ - public get(key: string): string | undefined { - return this.envContents[key] - } - - /** - * Returns an env object for the keys that has defined values - */ - public asEnvObject(keys: string[]): { [key: string]: string } { - return keys.reduce((result, key) => { - const value = this.get(key) - if (value !== undefined) { - result[key] = value - } - return result - }, {}) - } -} diff --git a/src/HttpServer/index.ts b/src/HttpServer/index.ts deleted file mode 100644 index e7b94ac..0000000 --- a/src/HttpServer/index.ts +++ /dev/null @@ -1,94 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import execa from 'execa' -import Emittery from 'emittery' -import { logger as uiLogger } from '@poppinss/cliui' - -/** - * Exposes the API to start Node.js HTTP server as a child process. The - * child process is full managed and cleans up when parent process - * dies. - */ -export class HttpServer extends Emittery { - private childProcess?: execa.ExecaChildProcess - private nodeArgs: string[] = [] - - constructor( - private sourceFile: string, - private projectRoot: string, - nodeArgs: string[] = [], - private logger: typeof uiLogger, - private env: { [key: string]: string } = {} - ) { - super() - this.nodeArgs = nodeArgs.reduce((result, arg) => { - result = result.concat(arg.split(' ')) - return result - }, [] as string[]) - } - - /** - * Whether or not the underlying process is connected - */ - public get isConnected() { - return this.childProcess && this.childProcess.connected && !this.childProcess.killed - } - - /** - * Start the HTTP server as a child process. - */ - public start() { - if (this.isConnected) { - throw new Error('Http server is already connected. Call restart instead') - } - - this.logger.info(this.childProcess ? 're-starting http server...' : 'starting http server...') - - this.childProcess = execa.node(this.sourceFile, [], { - buffer: false, - stdio: 'inherit', - cwd: this.projectRoot, - env: { - FORCE_COLOR: 'true', - ...this.env, - }, - nodeOptions: ['-r', '@adonisjs/assembler/build/register'].concat(this.nodeArgs), - }) - - /** - * Notify about server events - */ - this.childProcess.on('message', (message) => { - if (message && message['isAdonisJS'] && message['environment'] === 'web') { - this.emit('ready', message) - } - }) - this.childProcess.on('close', (code, signal) => this.emit('close', { code, signal })) - this.childProcess.on('exit', (code, signal) => this.emit('exit', { code, signal })) - } - - /** - * Stop the underlying process - */ - public stop() { - if (this.childProcess) { - this.childProcess.removeAllListeners() - this.childProcess.kill('SIGKILL') - } - } - - /** - * Restart the server by killing the old one - */ - public restart() { - this.stop() - this.start() - } -} diff --git a/src/Manifest/index.ts b/src/Manifest/index.ts deleted file mode 100644 index bdc1194..0000000 --- a/src/Manifest/index.ts +++ /dev/null @@ -1,89 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import execa from 'execa' -import { logger as uiLogger } from '@poppinss/cliui' - -const WARN_MESSAGE = [ - 'Unable to generate manifest file.', - 'Check the following error for more info', -].join(' ') - -/** - * Exposes the API to execute generate manifest file - */ -export class Manifest { - /** - * The maximum number of times we should attempt to generate - * the manifest file before giving up. - * - * This number may sound too big, but in real world scanerio, we - * have seen encountered malformed JSON between 10-12 times. - * - * The JSON gets malformed, when a parallel process (node ace serve --watch) - * is trying to update it. - */ - private maxAttempts = 15 - private attempts = 0 - - constructor(private appRoot: string, private logger: typeof uiLogger) {} - - /** - * Returns a boolean telling if the error message is pointing - * towards invalid or empty JSON file read attempt. - */ - private isMalformedJSONError(error: string) { - return error.includes('Unexpected end of JSON input') - } - - /** - * Generates the manifest file. We ignore `generate:manifest` errors for - * now, since it's a secondary task for us and one should run it - * in seperate process to find the actual errors. - */ - public async generate(): Promise { - try { - const response = await execa(process.execPath, ['ace', 'generate:manifest'], { - buffer: true, - cwd: this.appRoot, - env: { - FORCE_COLOR: 'true', - }, - }) - - /** - * Log success - */ - if (response.stdout) { - this.logger.log(response.stdout) - } - - return true - } catch (error) { - if (this.isMalformedJSONError(error.stderr) && this.attempts < this.maxAttempts) { - this.attempts++ - return this.generate() - } - - /** - * Print warning on error - */ - this.logger.warning(WARN_MESSAGE) - if (error.stderr) { - this.logger.logError(error.stderr) - } - - if (error.stdout) { - this.logger.logError(error.stdout) - } - - return false - } - } -} diff --git a/src/RcFile/index.ts b/src/RcFile/index.ts deleted file mode 100644 index 881f96a..0000000 --- a/src/RcFile/index.ts +++ /dev/null @@ -1,190 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import slash from 'slash' -import picomatch from 'picomatch' -import { join, relative } from 'path' -import { readJSONSync } from 'fs-extra' -import { Application } from '@adonisjs/application' -import { resolveFrom } from '@poppinss/utils/build/helpers' - -import { RCFILE_NAME, ACE_FILE_NAME } from '../../config/paths' - -/** - * Exposes the API to pull meta files from the `.adonisrc.json` file and - * also match relative file paths against the defined globs. - */ -export class RcFile { - public rcFilePath = resolveFrom(this.appRoot, `./${RCFILE_NAME}`) - - /** - * Raw rcfile contents - */ - public raw = this.getDiskContents() - - /** - * Reference to application - */ - public application = new Application(this.appRoot, 'console', this.raw) - - /** - * A matcher to know if a file is part of the meta files globs - */ - public isMetaFile: (filePath: string) => boolean = picomatch(this.getMetaFilesGlob()) - - /** - * A matcher to know if file is a test file or not - */ - public isTestsFile: (filePath: string) => boolean = picomatch(this.getTestsFileGlob()) - - /** - * A matcher to know if a file is part of the restart server files globs - */ - public isRestartServerFile: (filePath: string) => boolean = picomatch( - this.getRestartServerFilesGlob() - ) - - /** - * Commands match to know, if file path is part of the commands paths defined - * inside `.adonisrc.json` file - */ - public isCommandsPath: (filePath: string) => boolean = picomatch(this.commandsGlob()) - - constructor(private appRoot: string) {} - - /** - * Returns an array of globs for the meta files that has `reloadServer` - * set to true - */ - private getRestartServerFilesGlob(): string[] { - return this.application.rcFile.metaFiles - .filter(({ reloadServer, pattern }) => { - return reloadServer === true && ![RCFILE_NAME, ACE_FILE_NAME].includes(pattern) - }) - .map(({ pattern }) => pattern) - } - - /** - * Returns the commands glob for registered commands. We convert the - * command paths to glob pattern - */ - private commandsGlob(): string[] { - const commands = this.application.rcFile.commands.reduce((result: string[], commandPath) => { - if (/^(.){1,2}\//.test(commandPath)) { - commandPath = slash(relative(this.appRoot, join(this.appRoot, commandPath))) - result = result.concat([`${commandPath}.*`, `${commandPath}/**/*`]) - } - return result - }, []) - - return commands - } - - /** - * Returns true when file is `.adonisrc.json` itself - */ - private isRcFile(filePath: string) { - return filePath === RCFILE_NAME - } - - /** - * Returns an array of globs for the meta files - * to be copied - */ - public getMetaFilesGlob(): string[] { - return this.application.rcFile.metaFiles - .filter(({ pattern }) => ![RCFILE_NAME, ACE_FILE_NAME].includes(pattern)) - .map(({ pattern }) => pattern) - .concat([ACE_FILE_NAME]) - } - - /** - * Returns an array of globs for the test files - */ - public getTestsFileGlob(): string[] { - return this.application.rcFile.tests.suites.reduce((result, suite) => { - if (suite.files) { - result = result.concat(suite.files) - } - - return result - }, [] as string[]) - } - - /** - * Reloads the rcfile.json - */ - public getDiskContents(): any { - return readJSONSync(this.rcFilePath) - } - - /** - * Returns metadata for a given file path. The metadata can - * be used to execute certain actions during file watch. - */ - public getMetaData(filePath: string) { - /** - * File path === '.adonisrc.json' - */ - if (this.isRcFile(filePath)) { - return { - reload: true, - rcFile: true, - metaFile: true, - testFile: false, - } - } - - /** - * File is part of `reloadServer` metadata file globs - */ - if (this.isRestartServerFile(filePath)) { - return { - reload: true, - rcFile: false, - metaFile: true, - testFile: false, - } - } - - /** - * File is part of metadata file globs, but reload = false - */ - if (this.isMetaFile(filePath)) { - return { - reload: false, - rcFile: false, - metaFile: true, - testFile: false, - } - } - - /** - * File is part of one of the tests suite - */ - if (this.isTestsFile(filePath)) { - return { - reload: false, - rcFile: false, - metaFile: false, - testFile: true, - } - } - - /** - * Out of scope - */ - return { - reload: false, - rcFile: false, - metaFile: false, - testFile: false, - } - } -} diff --git a/src/Test/index.ts b/src/Test/index.ts deleted file mode 100644 index 2c932f7..0000000 --- a/src/Test/index.ts +++ /dev/null @@ -1,414 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { extname } from 'path' -import picomatch from 'picomatch' -import { logger as uiLogger } from '@poppinss/cliui' -import { getWatcherHelpers } from '@adonisjs/require-ts' - -import { Ts } from '../Ts' -import { RcFile } from '../RcFile' -import { Manifest } from '../Manifest' -import { TestProcess } from './process' -import { JapaFlags } from '../Contracts' - -import { ENV_FILES, TESTS_ENTRY_FILE } from '../../config/paths' -import { EnvParser } from '../EnvParser' -import getPort from 'get-port' - -/** - * Exposes the API to watch project for compilition changes and - * run/re-run tests - */ -export class TestsServer { - /** - * A boolean to know if we are watching for filesystem - */ - private watchingFileSystem: boolean = false - - /** - * Boolean to hold the current state of tests. This is avoid - * re-running the tests when one run is in progress - */ - private busy = false - - /** - * Reference to the typescript compiler - */ - private ts = new Ts(this.appRoot, this.logger) - - /** - * Reference to the RCFile - */ - private rcFile = new RcFile(this.appRoot) - - /** - * Manifest instance to generate ace manifest file - */ - private manifest = new Manifest(this.appRoot, this.logger) - - /** - * Require-ts watch helpers - */ - private watchHelpers = getWatcherHelpers(this.appRoot) - - /** - * A method to know if the file is part of the selected suites - * or not - */ - private isTestSuiteFile: (filePath: string) => boolean = picomatch( - this.getFilesForSelectedSuites() - ) - - /** - * Find if the test file part of the applied file filters - */ - private isTestFile = (filePath: string): boolean => { - if (!this.filters['--files']) { - return true - } - - const fileName = filePath.replace(extname(filePath), '') - return !!this.filters['--files'].find((filter) => { - if (filePath.endsWith(filter)) { - return true - } - - return fileName.endsWith(filter) || fileName.endsWith(`${filter}.spec`) - }) - } - - constructor( - private appRoot: string, - private filters: JapaFlags, - private nodeArgs: string[] = [], - private logger: typeof uiLogger = uiLogger - ) {} - - /** - * Clear terminal screen - */ - private clearScreen() { - process.stdout.write('\u001Bc') - } - - /** - * Returns the glob paths for test suites. Returns all if no - * filter is applied. Otherwise only the filtered suites - * are picked. - */ - private getFilesForSelectedSuites() { - return this.rcFile.application.rcFile.tests.suites.reduce((result, suite) => { - if (!suite.files) { - return result - } - - if (!this.filters['--suites'] || this.filters['--suites'].includes(suite.name)) { - result = result.concat(suite.files) - } - - return result - }, [] as string[]) - } - - /** - * Kill current process - */ - private kill() { - process.exit() - } - - /** - * Returns the HOST and the PORT environment variables - * for the HTTP server - */ - private async getEnvironmentVariables() { - const envParser = new EnvParser() - await envParser.parse(this.appRoot) - - const envOptions = envParser.asEnvObject(['PORT', 'TZ', 'HOST']) - const HOST = process.env.HOST || envOptions.HOST || '0.0.0.0' - let PORT = Number(process.env.PORT || envOptions.PORT) - - /** - * Use the port defined inside ".env.test" file or use - * a random port - */ - PORT = await getPort({ - port: !isNaN(PORT) ? [PORT] : [], - host: HOST, - }) - - return { HOST, PORT: String(PORT) } - } - - /** - * Run tests. Use [[watch]] to also watch for file - * changes - */ - public async run(filePath?: string) { - if (this.busy) { - return - } - - this.clearScreen() - const filters = { ...this.filters } - - /** - * Overwrite files filter when a specific file path - * is mentioned - */ - if (filePath) { - filters['--files'] = [filePath.replace(/\\/g, '/')] - } - - this.busy = true - const { hasErrors } = await new TestProcess( - TESTS_ENTRY_FILE, - this.appRoot, - filters, - this.nodeArgs, - this.logger, - await this.getEnvironmentVariables() - ).run() - - this.busy = false - if (!this.watchingFileSystem) { - if (hasErrors) { - process.exitCode = 1 - } - this.kill() - } - } - - /** - * Build and watch for file changes - */ - public async watch(poll = false) { - this.watchingFileSystem = true - - /** - * Clear require-ts cache - */ - this.watchHelpers.clear() - - /** - * Run tests - */ - await this.run() - - /** - * Parse config to find the files excluded inside - * tsconfig file - */ - const config = this.ts.parseConfig() - if (!config) { - this.logger.warning('Cannot start watcher because of errors in the tsconfig file') - return - } - - /** - * Stick file watcher - */ - const watcher = this.ts.tsCompiler.watcher(config, 'raw') - - /** - * Watcher is ready after first compile - */ - watcher.on('watcher:ready', () => { - this.logger.info('watching file system for changes') - }) - - /** - * Source file removed - */ - watcher.on('source:unlink', async ({ absPath, relativePath }) => { - this.watchHelpers.clear(absPath) - - if (this.busy) { - return - } - - this.logger.action('delete').succeeded(relativePath) - - /** - * Generate manifest when filePath is a commands path - */ - if (this.rcFile.isCommandsPath(relativePath)) { - this.manifest.generate() - } - - /** - * Run all tests when any of the source, except the - * test file changes - */ - if (!this.rcFile.isTestsFile(relativePath)) { - await this.run() - } - }) - - /** - * Source file added - */ - watcher.on('source:add', async ({ absPath, relativePath }) => { - this.watchHelpers.clear(absPath) - - if (this.busy) { - return - } - - this.logger.action('add').succeeded(relativePath) - - /** - * Run all tests when any of the source, except the - * test file changes - */ - if (!this.rcFile.isTestsFile(relativePath)) { - await this.run() - return - } - - /** - * Run only the changed file if it part of the test - * suites (respecting filters) - */ - if (this.isTestSuiteFile(relativePath) && this.isTestFile(relativePath)) { - await this.run(relativePath) - } - }) - - /** - * Source file changed - */ - watcher.on('source:change', async ({ absPath, relativePath }) => { - this.watchHelpers.clear(absPath) - - if (this.busy) { - return - } - - this.logger.action('update').succeeded(relativePath) - - /** - * Generate manifest when filePath is a commands path - */ - if (this.rcFile.isCommandsPath(relativePath)) { - this.manifest.generate() - } - - /** - * Run all tests when any of the source, except the - * test file changes - */ - if (!this.rcFile.isTestsFile(relativePath)) { - await this.run() - return - } - - /** - * Run only the changed file if it part of the test - * suites (respecting filters) - */ - if (this.isTestSuiteFile(relativePath) && this.isTestFile(relativePath)) { - await this.run(relativePath) - } - }) - - /** - * New file added - */ - watcher.on('add', async ({ relativePath }) => { - if (this.busy) { - return - } - - if (ENV_FILES.includes(relativePath)) { - this.logger.action('create').succeeded(relativePath) - await this.run() - return - } - - const metaData = this.rcFile.getMetaData(relativePath) - if (!metaData.metaFile) { - return - } - - this.logger.action('create').succeeded(relativePath) - await this.run() - }) - - /** - * File changed - */ - watcher.on('change', async ({ relativePath }) => { - if (this.busy) { - return - } - - if (ENV_FILES.includes(relativePath)) { - this.logger.action('update').succeeded(relativePath) - await this.run() - return - } - - const metaData = this.rcFile.getMetaData(relativePath) - if (!metaData.metaFile) { - return - } - - this.logger.action('update').succeeded(relativePath) - await this.run() - }) - - /** - * File removed - */ - watcher.on('unlink', async ({ relativePath }) => { - if (this.busy) { - return - } - - if (ENV_FILES.includes(relativePath)) { - this.logger.action('delete').succeeded(relativePath) - await this.run() - return - } - - const metaData = this.rcFile.getMetaData(relativePath) - if (!metaData.metaFile) { - return - } - - if (metaData.rcFile) { - this.logger.info('cannot continue after deletion of .adonisrc.json file') - watcher.chokidar.close() - this.kill() - return - } - - this.logger.action('delete').succeeded(relativePath) - await this.run() - }) - - /** - * Start the watcher - */ - watcher.watch(['.'], { - usePolling: poll, - }) - - /** - * Kill when watcher recieves an error - */ - watcher.chokidar.on('error', (error) => { - this.logger.fatal(error) - this.kill() - }) - } -} diff --git a/src/Test/process.ts b/src/Test/process.ts deleted file mode 100644 index 77f39f8..0000000 --- a/src/Test/process.ts +++ /dev/null @@ -1,73 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import execa from 'execa' -import { logger as uiLogger } from '@poppinss/cliui' - -import { JapaFlags } from '../Contracts' - -/** - * Exposes the API to run tests as a child process. - */ -export class TestProcess { - private nodeArgs: string[] - - constructor( - private sourceFile: string, - private projectRoot: string, - private filters: JapaFlags, - nodeArgs: string[] = [], - private logger: typeof uiLogger, - private env: { [key: string]: string } = {} - ) { - this.nodeArgs = nodeArgs.reduce((result, arg) => { - result = result.concat(arg.split(' ')) - return result - }, []) - } - - /** - * Start the HTTP server as a child process. - */ - public async run() { - this.logger.info('running tests...') - const filters = Object.keys(this.filters).reduce((result, filter) => { - const value = this.filters[filter] - - if (filter === '_') { - result.push(...value) - return result - } - - result.push(filter) - if (Array.isArray(value)) { - result.push(value.join(',')) - } else { - result.push(value) - } - - return result - }, []) - - try { - await execa.node(this.sourceFile, filters, { - stdio: 'inherit', - cwd: this.projectRoot, - env: { - FORCE_COLOR: 'true', - ...this.env, - }, - nodeOptions: ['-r', '@adonisjs/assembler/build/register'].concat(this.nodeArgs), - }) - return { hasErrors: false } - } catch { - return { hasErrors: true } - } - } -} diff --git a/src/Ts/index.ts b/src/Ts/index.ts deleted file mode 100644 index 46e32ff..0000000 --- a/src/Ts/index.ts +++ /dev/null @@ -1,66 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { join } from 'path' -import tsStatic from 'typescript' -import { logger as uiLogger } from '@poppinss/cliui' -import { TypescriptCompiler } from '@poppinss/chokidar-ts' -import { resolveFrom } from '@poppinss/utils/build/helpers' - -import { TSCONFIG_FILE_NAME, DEFAULT_BUILD_DIR } from '../../config/paths' - -/** - * Exposes the API to work with the Typescript compiler API - */ -export class Ts { - /** - * Reference to the typescript compiler - */ - public tsCompiler = new TypescriptCompiler( - this.appRoot, - this.tsconfig, - require(resolveFrom(this.appRoot, 'typescript/lib/typescript')) - ) - - constructor( - private appRoot: string, - private logger: typeof uiLogger, - private tsconfig = TSCONFIG_FILE_NAME - ) {} - - /** - * Render ts diagnostics - */ - public renderDiagnostics(diagnostics: tsStatic.Diagnostic[], host: tsStatic.CompilerHost) { - console.error(this.tsCompiler.ts.formatDiagnosticsWithColorAndContext(diagnostics, host)) - } - - /** - * Parses the tsconfig file - */ - public parseConfig(): undefined | tsStatic.ParsedCommandLine { - const { error, config } = this.tsCompiler.configParser().parse() - - if (error) { - this.logger.error(`unable to parse ${this.tsconfig}`) - this.renderDiagnostics([error], this.tsCompiler.ts.createCompilerHost({})) - return - } - - if (config && config.errors.length) { - this.logger.error(`unable to parse ${this.tsconfig}`) - this.renderDiagnostics(config.errors, this.tsCompiler.ts.createCompilerHost(config.options)) - return - } - - config!.options.rootDir = config!.options.rootDir || this.appRoot - config!.options.outDir = config!.options.outDir || join(this.appRoot, DEFAULT_BUILD_DIR) - return config - } -} diff --git a/src/bundler.ts b/src/bundler.ts new file mode 100644 index 0000000..50302c3 --- /dev/null +++ b/src/bundler.ts @@ -0,0 +1,233 @@ +/* + * @adonisjs/assembler + * + * (c) AdonisJS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import fs from 'fs-extra' +import slash from 'slash' +import copyfiles from 'cpy' +import { execa } from 'execa' +import { join, relative } from 'node:path' +import type tsStatic from 'typescript' +import { fileURLToPath } from 'node:url' +import { ConfigParser } from '@poppinss/chokidar-ts' +import { cliui, type Logger } from '@poppinss/cliui' + +import type { BundlerOptions } from './types.js' + +/** + * Instance of CLIUI + */ +const ui = cliui() + +/** + * The bundler class exposes the API to build an AdonisJS project. + */ +export class Bundler { + #cwd: URL + #cwdPath: string + #ts: typeof tsStatic + #logger = ui.logger + #options: BundlerOptions + + /** + * Getting reference to colors library from logger + */ + get #colors() { + return this.#logger.getColors() + } + + constructor(cwd: URL, ts: typeof tsStatic, options: BundlerOptions) { + this.#cwd = cwd + this.#cwdPath = fileURLToPath(this.#cwd) + this.#ts = ts + this.#options = options + } + + #getRelativeName(filePath: string) { + return slash(relative(this.#cwdPath, filePath)) + } + + /** + * Cleans up the build directory + */ + async #cleanupBuildDirectory(outDir: string) { + await fs.remove(outDir) + } + + /** + * Runs tsc command to build the source. + */ + async #runTsc() { + try { + await execa('tsc', [], { + cwd: this.#cwd, + preferLocal: true, + localDir: this.#cwd, + windowsHide: false, + buffer: false, + stdio: 'inherit', + }) + return true + } catch { + return false + } + } + + /** + * Copy files to destination directory + */ + async #copyFiles(files: string[], outDir: string) { + try { + await copyfiles(files, outDir, { cwd: this.#cwdPath }) + } catch (error) { + if (!error.message.includes("the file doesn't exist")) { + throw error + } + } + } + + /** + * Copy meta files to the output directory + */ + async #copyMetaFiles(outDir: string, additionalFilesToCopy: string[]) { + const metaFiles = (this.#options.metaFiles || []) + .map((file) => file.pattern) + .concat(additionalFilesToCopy) + + await this.#copyFiles(metaFiles, outDir) + } + + /** + * Copies .adonisrc.json file to the destination + */ + async #copyAdonisRcFile(outDir: string) { + const existingContents = await fs.readJSON(join(this.#cwdPath, '.adonisrc.json')) + const compiledContents = Object.assign({}, existingContents, { + typescript: false, + lastCompiledAt: new Date().toISOString(), + }) + + await fs.outputJSON(join(outDir, '.adonisrc.json'), compiledContents, { spaces: 2 }) + } + + /** + * Returns the lock file name for a given packages client + */ + #getClientLockFile(client: 'npm' | 'yarn' | 'pnpm') { + switch (client) { + case 'npm': + return 'package-lock.json' + case 'yarn': + return 'yarn.lock' + case 'pnpm': + return 'pnpm-lock.yaml' + } + } + + /** + * Returns the installation command for a given packages client + */ + #getClientInstallCommand(client: 'npm' | 'yarn' | 'pnpm') { + switch (client) { + case 'npm': + return 'npm ci --omit="dev"' + case 'yarn': + return 'yarn install --production' + case 'pnpm': + return 'pnpm i --prod' + } + } + + /** + * Set a custom CLI UI logger + */ + setLogger(logger: Logger) { + this.#logger = logger + return this + } + + /** + * Bundles the application to be run in production + */ + async bundle( + stopOnError: boolean = true, + client: 'npm' | 'yarn' | 'pnpm' = 'npm' + ): Promise { + /** + * Step 1: Parse config file to get the build output directory + */ + const { config } = new ConfigParser(this.#cwd, 'tsconfig.json', this.#ts).parse() + if (!config) { + return false + } + + /** + * Step 2: Cleanup existing build directory (if any) + */ + const outDir = config.options.outDir || fileURLToPath(new URL('build/', this.#cwd)) + this.#logger.info('cleaning up output directory', { suffix: this.#getRelativeName(outDir) }) + await this.#cleanupBuildDirectory(outDir) + + /** + * Step 3: Build typescript source code + */ + this.#logger.info('compiling typescript source', { suffix: 'tsc' }) + const buildCompleted = await this.#runTsc() + await this.#copyFiles(['ace.js'], outDir) + + /** + * Remove incomplete build directory when tsc build + * failed and stopOnError is set to true. + */ + if (!buildCompleted && stopOnError) { + await this.#cleanupBuildDirectory(outDir) + const instructions = ui + .sticker() + .fullScreen() + .drawBorder((borderChar, colors) => colors.red(borderChar)) + + instructions.add( + this.#colors.red('Cannot complete the build process as there are TypeScript errors.') + ) + instructions.add( + this.#colors.red( + 'Use "--ignore-ts-errors" flag to ignore TypeScript errors and continue the build.' + ) + ) + + this.#logger.logError(instructions.prepare()) + return false + } + + /** + * Step 4: Copy meta files to the build directory + */ + const pkgFiles = ['package.json', this.#getClientLockFile(client)] + this.#logger.info('copying meta files to the output directory') + await this.#copyMetaFiles(outDir, pkgFiles) + + /** + * Step 5: Copy .adonisrc.json file to the build directory + */ + this.#logger.info('copying .adonisrc.json file to the output directory') + await this.#copyAdonisRcFile(outDir) + + this.#logger.success('build completed') + this.#logger.log('') + + ui.instructions() + .useRenderer(this.#logger.getRenderer()) + .heading('Run the following commands to start the server in production') + .add(this.#colors.cyan(`cd ${this.#getRelativeName(outDir)}`)) + .add(this.#colors.cyan(this.#getClientInstallCommand(client))) + .add(this.#colors.cyan('node bin/server.js')) + .render() + + return true + } +} diff --git a/src/dev_server.ts b/src/dev_server.ts new file mode 100644 index 0000000..55a0e7a --- /dev/null +++ b/src/dev_server.ts @@ -0,0 +1,309 @@ +/* + * @adonisjs/assembler + * + * (c) AdonisJS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import getPort from 'get-port' +import picomatch from 'picomatch' +import type tsStatic from 'typescript' +import { type ExecaChildProcess } from 'execa' +import { cliui, type Logger } from '@poppinss/cliui' +import { EnvLoader, EnvParser } from '@adonisjs/env' + +import { run } from './run.js' +import { watch } from './watch.js' +import type { DevServerOptions } from './types.js' + +/** + * Instance of CLIUI + */ +const ui = cliui() + +/** + * Exposes the API to start the development. Optionally, the watch API can be + * used to watch for file changes and restart the development server. + * + * The Dev server performs the following actions + * + * - Assigns a random PORT, when PORT inside .env file is in use + * - Uses tsconfig.json file to collect a list of files to watch. + * - Uses metaFiles from .adonisrc.json file to collect a list of files to watch. + * - Restart HTTP server on every file change. + */ +export class DevServer { + #cwd: URL + #logger = ui.logger + #options: DevServerOptions + #isWatching: boolean = false + #scriptFile: string = 'bin/server.js' + #httpServerProcess?: ExecaChildProcess + #isMetaFileWithReloadsEnabled: picomatch.Matcher + #isMetaFileWithReloadsDisabled: picomatch.Matcher + + /** + * Getting reference to colors library from logger + */ + get #colors() { + return this.#logger.getColors() + } + + constructor(cwd: URL, options: DevServerOptions) { + this.#cwd = cwd + this.#options = options + + this.#isMetaFileWithReloadsEnabled = picomatch( + (this.#options.metaFiles || []) + .filter(({ reloadServer }) => reloadServer === true) + .map(({ pattern }) => pattern) + ) + + this.#isMetaFileWithReloadsDisabled = picomatch( + (this.#options.metaFiles || []) + .filter(({ reloadServer }) => reloadServer !== true) + .map(({ pattern }) => pattern) + ) + } + + /** + * Check if file is an .env file + */ + #isDotEnvFile(filePath: string) { + if (filePath === '.env') { + return true + } + + return filePath.includes('.env.') + } + + /** + * Check if file is .adonisrc.json file + */ + #isRcFile(filePath: string) { + return filePath === '.adonisrc.json' + } + + /** + * Inspect if child process message is from AdonisJS HTTP server + */ + #isAdonisJSReadyMessage( + message: unknown + ): message is { isAdonisJS: true; environment: 'web'; port: number; host: string } { + return ( + message !== null && + typeof message === 'object' && + 'isAdonisJS' in message && + 'environment' in message && + message.environment === 'web' + ) + } + + /** + * Conditionally clear the terminal screen + */ + #clearScreen() { + if (this.#options.clearScreen) { + process.stdout.write('\u001Bc') + } + } + + /** + * Returns PORT for starting the HTTP server with option to use + * a random PORT if main PORT is in use. + */ + async #getPort(): Promise { + /** + * Use existing port if exists + */ + if (process.env.PORT) { + return getPort({ port: Number(process.env.PORT) }) + } + + const files = await new EnvLoader(this.#cwd).load() + for (let file of files) { + const envVariables = new EnvParser(file.contents).parse() + if (envVariables.PORT) { + return getPort({ port: Number(envVariables.PORT) }) + } + } + + return getPort({ port: 3333 }) + } + + /** + * Starts the HTTP server + */ + #startHTTPServer(port: string) { + this.#httpServerProcess = run(this.#cwd, { + script: this.#scriptFile, + env: { PORT: port, ...this.#options.env }, + nodeArgs: this.#options.nodeArgs, + scriptArgs: this.#options.scriptArgs, + }) + + this.#httpServerProcess.on('message', (message) => { + if (this.#isAdonisJSReadyMessage(message)) { + ui.sticker() + .useColors(this.#colors) + .useRenderer(this.#logger.getRenderer()) + .add(`Server address: ${this.#colors.cyan(`http://${message.host}:${message.port}`)}`) + .add( + `File system watcher: ${this.#colors.cyan( + `${this.#isWatching ? 'enabled' : 'disabled'}` + )}` + ) + .render() + } + }) + + this.#httpServerProcess.on('error', (error) => { + this.#logger.warning('unable to connect to underlying HTTP server process') + this.#logger.fatal(error) + }) + + this.#httpServerProcess.on('close', (exitCode) => { + this.#logger.warning(`underlying HTTP server closed with status code "${exitCode}"`) + this.#logger.info('watching file system and waiting for application to recover') + }) + } + + /** + * Restart the development server + */ + #restart(port: string) { + if (this.#httpServerProcess) { + this.#httpServerProcess.removeAllListeners() + this.#httpServerProcess.kill('SIGKILL') + } + + this.#startHTTPServer(port) + } + + /** + * Set a custom CLI UI logger + */ + setLogger(logger: Logger) { + this.#logger = logger + return this + } + + /** + * Start the development server + */ + async start() { + this.#clearScreen() + this.#logger.info('starting HTTP server...') + this.#startHTTPServer(String(await this.#getPort())) + } + + /** + * Start the development server in watch mode + */ + async startAndWatch(ts: typeof tsStatic, options?: { poll: boolean }) { + const port = String(await this.#getPort()) + + this.#isWatching = true + this.#clearScreen() + + this.#logger.info('starting HTTP server...') + this.#startHTTPServer(port) + + const output = watch(this.#cwd, ts, options || {}) + if (!output) { + return + } + + /** + * Notify the watcher is ready + */ + output.watcher.on('watcher:ready', () => { + this.#logger.info('watching file system for changes...') + }) + + /** + * Changes in TypeScript source file + */ + output.watcher.on('source:add', ({ relativePath }) => { + this.#clearScreen() + this.#logger.log(`${this.#colors.green('add')} ${relativePath}`) + this.#restart(port) + }) + output.watcher.on('source:change', ({ relativePath }) => { + this.#clearScreen() + this.#logger.log(`${this.#colors.green('update')} ${relativePath}`) + this.#restart(port) + }) + output.watcher.on('source:unlink', ({ relativePath }) => { + this.#clearScreen() + this.#logger.log(`${this.#colors.green('delete')} ${relativePath}`) + this.#restart(port) + }) + + /** + * Changes in other files + */ + output.watcher.on('add', ({ relativePath }) => { + if (this.#isDotEnvFile(relativePath) || this.#isRcFile(relativePath)) { + this.#clearScreen() + this.#logger.log(`${this.#colors.green('add')} ${relativePath}`) + this.#restart(port) + return + } + + if (this.#isMetaFileWithReloadsEnabled(relativePath)) { + this.#clearScreen() + this.#logger.log(`${this.#colors.green('add')} ${relativePath}`) + this.#restart(port) + return + } + + if (this.#isMetaFileWithReloadsDisabled(relativePath)) { + this.#clearScreen() + this.#logger.log(`${this.#colors.green('add')} ${relativePath}`) + } + }) + output.watcher.on('change', ({ relativePath }) => { + if (this.#isDotEnvFile(relativePath) || this.#isRcFile(relativePath)) { + this.#clearScreen() + this.#logger.log(`${this.#colors.green('update')} ${relativePath}`) + this.#restart(port) + return + } + + if (this.#isMetaFileWithReloadsEnabled(relativePath)) { + this.#clearScreen() + this.#logger.log(`${this.#colors.green('update')} ${relativePath}`) + this.#restart(port) + return + } + + if (this.#isMetaFileWithReloadsDisabled(relativePath)) { + this.#clearScreen() + this.#logger.log(`${this.#colors.green('update')} ${relativePath}`) + } + }) + output.watcher.on('unlink', ({ relativePath }) => { + if (this.#isDotEnvFile(relativePath) || this.#isRcFile(relativePath)) { + this.#clearScreen() + this.#logger.log(`${this.#colors.green('delete')} ${relativePath}`) + this.#restart(port) + return + } + + if (this.#isMetaFileWithReloadsEnabled(relativePath)) { + this.#clearScreen() + this.#logger.log(`${this.#colors.green('delete')} ${relativePath}`) + this.#restart(port) + return + } + + if (this.#isMetaFileWithReloadsDisabled(relativePath)) { + this.#clearScreen() + this.#logger.log(`${this.#colors.green('delete')} ${relativePath}`) + } + }) + } +} diff --git a/src/requireHook/index.ts b/src/requireHook/index.ts deleted file mode 100644 index 34b7d71..0000000 --- a/src/requireHook/index.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { register } from '@adonisjs/require-ts' - -/** - * Exports the function to be used for registering require hook - * for AdonisJS applications - */ -export default function registerForAdonis(appRoot: string) { - return register(appRoot, { - cache: true, - transformers: { - after: [ - { - transform: '@adonisjs/assembler/build/src/requireHook/ioc-transformer', - }, - ], - }, - }) -} diff --git a/src/requireHook/ioc-transformer.ts b/src/requireHook/ioc-transformer.ts deleted file mode 100644 index a96ff90..0000000 --- a/src/requireHook/ioc-transformer.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { join } from 'path' -import type tsStatic from 'typescript' -import { rcParser } from '@adonisjs/application' -import { iocTransformer } from '@adonisjs/ioc-transformer' - -/** - * Transformer to transform AdonisJS IoC container import - * statements - */ -export default function (ts: typeof tsStatic, appRoot: string) { - return iocTransformer(ts, rcParser.parse(require(join(appRoot, '.adonisrc.json')))) -} diff --git a/src/run.ts b/src/run.ts new file mode 100644 index 0000000..1c5dc85 --- /dev/null +++ b/src/run.ts @@ -0,0 +1,42 @@ +/* + * @adonisjs/assembler + * + * (c) AdonisJS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { execaNode } from 'execa' +import type { RunOptions } from './types.js' + +/** + * Default set of args to pass in order to run TypeScript + * source + */ +const DEFAULT_NODE_ARGS = [ + // Use ts-node/esm loader. The project must install it + '--loader=ts-node/esm', + // Disable annonying warnings + '--no-warnings', + // Enable expiremental meta resolve for cases where someone uses magic import string + '--experimental-import-meta-resolve', +] + +/** + * Runs a script as a child process and inherits the stdio streams + */ +export function run(cwd: string | URL, options: RunOptions) { + const childProces = execaNode(options.script, options.scriptArgs, { + nodeOptions: DEFAULT_NODE_ARGS.concat(options.nodeArgs), + preferLocal: true, + windowsHide: false, + localDir: cwd, + cwd, + buffer: false, + stdio: 'inherit', + env: options.env, + }) + + return childProces +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..a80d49e --- /dev/null +++ b/src/types.ts @@ -0,0 +1,49 @@ +/* + * @adonisjs/assembler + * + * (c) AdonisJS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Options needed to run a script file + */ +export type RunOptions = { + script: string + scriptArgs: string[] + nodeArgs: string[] + env?: NodeJS.ProcessEnv +} + +/** + * Watcher options + */ +export type WatchOptions = { + poll?: boolean +} + +/** + * Options accepted by the dev server + */ +export type DevServerOptions = { + scriptArgs: string[] + nodeArgs: string[] + clearScreen?: boolean + env?: NodeJS.ProcessEnv + metaFiles?: { + pattern: string + reloadServer: boolean + }[] +} + +/** + * Options accepted by the project bundler + */ +export type BundlerOptions = { + metaFiles?: { + pattern: string + reloadServer: boolean + }[] +} diff --git a/src/watch.ts b/src/watch.ts new file mode 100644 index 0000000..6348318 --- /dev/null +++ b/src/watch.ts @@ -0,0 +1,40 @@ +/* + * @adonisjs/assembler + * + * (c) AdonisJS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import type tsStatic from 'typescript' +import { fileURLToPath } from 'node:url' +import { ConfigParser, Watcher } from '@poppinss/chokidar-ts' + +import type { WatchOptions } from './types.js' + +/** + * Watches the file system using tsconfig file + */ +export function watch(cwd: string | URL, ts: typeof tsStatic, options: WatchOptions) { + /** + * Parsing config to get a list of includes, excludes and initial + * set of files + */ + const { config, error } = new ConfigParser(cwd, 'tsconfig.json', ts).parse() + if (error) { + const compilerHost = ts.createCompilerHost({}) + console.log(ts.formatDiagnosticsWithColorAndContext([error], compilerHost)) + return + } + + if (config!.errors.length) { + const compilerHost = ts.createCompilerHost({}) + console.log(ts.formatDiagnosticsWithColorAndContext(config!.errors, compilerHost)) + return + } + + const watcher = new Watcher(typeof cwd === 'string' ? cwd : fileURLToPath(cwd), config!) + const chokidar = watcher.watch(['.'], { usePolling: options.poll }) + return { watcher, chokidar } +} diff --git a/templates/command.txt b/templates/command.txt deleted file mode 100644 index 48fbeac..0000000 --- a/templates/command.txt +++ /dev/null @@ -1,33 +0,0 @@ -import { BaseCommand } from '@adonisjs/core/build/standalone' - -export default class {{ filename }} extends BaseCommand { - /** - * Command name is used to run the command - */ - public static commandName = '{{#toCommandName}}{{ filename }}{{/toCommandName}}' - - /** - * Command description is displayed in the "help" output - */ - public static description = '' - - public static settings = { - /** - * Set the following value to true, if you want to load the application - * before running the command. Don't forget to call `node ace generate:manifest` - * afterwards. - */ - loadApp: false, - - /** - * Set the following value to true, if you want this command to keep running until - * you manually decide to exit the process. Don't forget to call - * `node ace generate:manifest` afterwards. - */ - stayAlive: false, - } - - public async run() { - this.logger.info('Hello world!') - } -} diff --git a/templates/controller.txt b/templates/controller.txt deleted file mode 100644 index 2d1829a..0000000 --- a/templates/controller.txt +++ /dev/null @@ -1,3 +0,0 @@ -// import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' - -export default class {{ filename }} {} diff --git a/templates/event-listener.txt b/templates/event-listener.txt deleted file mode 100644 index b1ba63a..0000000 --- a/templates/event-listener.txt +++ /dev/null @@ -1,3 +0,0 @@ -import type { EventsList } from '@ioc:Adonis/Core/Event' - -export default class {{ filename }} {} diff --git a/templates/exception.txt b/templates/exception.txt deleted file mode 100644 index 2435d5f..0000000 --- a/templates/exception.txt +++ /dev/null @@ -1,15 +0,0 @@ -import { Exception } from '@adonisjs/core/build/standalone' - -/* -|-------------------------------------------------------------------------- -| Exception -|-------------------------------------------------------------------------- -| -| The Exception class imported from `@adonisjs/core` allows defining -| a status code and error code for every exception. -| -| @example -| new {{ filename }}('message', 500, 'E_RUNTIME_EXCEPTION') -| -*/ -export default class {{ filename }} extends Exception {} diff --git a/templates/middleware.txt b/templates/middleware.txt deleted file mode 100644 index e1adb5f..0000000 --- a/templates/middleware.txt +++ /dev/null @@ -1,8 +0,0 @@ -import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' - -export default class {{ filename }} { - public async handle({}: HttpContextContract, next: () => Promise) { - // code for middleware goes here. ABOVE THE NEXT CALL - await next() - } -} diff --git a/templates/preload-file.txt b/templates/preload-file.txt deleted file mode 100644 index d2cfc89..0000000 --- a/templates/preload-file.txt +++ /dev/null @@ -1,9 +0,0 @@ -/* -|-------------------------------------------------------------------------- -| Preloaded File -|-------------------------------------------------------------------------- -| -| Any code written inside this file will be executed during the application -| boot. -| -*/ diff --git a/templates/provider.txt b/templates/provider.txt deleted file mode 100644 index 4f2447a..0000000 --- a/templates/provider.txt +++ /dev/null @@ -1,40 +0,0 @@ -import type { ApplicationContract } from '@ioc:Adonis/Core/Application' - -/* -|-------------------------------------------------------------------------- -| Provider -|-------------------------------------------------------------------------- -| -| Your application is not ready when this file is loaded by the framework. -| Hence, the top level imports relying on the IoC container will not work. -| You must import them inside the life-cycle methods defined inside -| the provider class. -| -| @example: -| -| public async ready () { -| const Database = this.app.container.resolveBinding('Adonis/Lucid/Database') -| const Event = this.app.container.resolveBinding('Adonis/Core/Event') -| Event.on('db:query', Database.prettyPrint) -| } -| -*/ -export default class {{ filename }} { - constructor(protected app: ApplicationContract) {} - - public register() { - // Register your own bindings - } - - public async boot() { - // All bindings are ready, feel free to use them - } - - public async ready() { - // App is ready - } - - public async shutdown() { - // Cleanup, since app is going down - } -} diff --git a/templates/resource-controller.txt b/templates/resource-controller.txt deleted file mode 100644 index 797f455..0000000 --- a/templates/resource-controller.txt +++ /dev/null @@ -1,17 +0,0 @@ -import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' - -export default class {{ filename }} { - public async index({}: HttpContextContract) {} - - public async create({}: HttpContextContract) {} - - public async store({}: HttpContextContract) {} - - public async show({}: HttpContextContract) {} - - public async edit({}: HttpContextContract) {} - - public async update({}: HttpContextContract) {} - - public async destroy({}: HttpContextContract) {} -} diff --git a/templates/self-handle-exception.txt b/templates/self-handle-exception.txt deleted file mode 100644 index 226f41f..0000000 --- a/templates/self-handle-exception.txt +++ /dev/null @@ -1,32 +0,0 @@ -import { Exception } from '@adonisjs/core/build/standalone' -import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' - -/* -|-------------------------------------------------------------------------- -| Exception -|-------------------------------------------------------------------------- -| -| The Exception class imported from `@adonisjs/core` allows defining -| a status code and error code for every exception. -| -| @example -| new {{ filename }}('message', 500, 'E_RUNTIME_EXCEPTION') -| -*/ -export default class {{ filename }} extends Exception { - /** - * The handle method allows you to self handle the exception and - * return an HTTP response. - * - * This is how it works under the hood. - * - * - You raise this exception - * - The exception goes uncatched/unhandled through out the entire HTTP request cycle. - * - Just before making the response. AdonisJS will call the `handle` method. - * Giving you a chance to convert the exception to response. - * - */ - public async handle(error: this, ctx: HttpContextContract) { - ctx.response.status(error.status || 500).send(error.message) - } -} diff --git a/templates/test-entrypoint.txt b/templates/test-entrypoint.txt deleted file mode 100644 index 93b9d8e..0000000 --- a/templates/test-entrypoint.txt +++ /dev/null @@ -1,45 +0,0 @@ -/* -|-------------------------------------------------------------------------- -| Tests -|-------------------------------------------------------------------------- -| -| The contents in this file boots the AdonisJS application and configures -| the Japa tests runner. -| -| For the most part you will never edit this file. The configuration -| for the tests can be controlled via ".adonisrc.json" and -| "tests/bootstrap.ts" files. -| -*/ - -process.env.NODE_ENV = 'test' - -import 'reflect-metadata' -import sourceMapSupport from 'source-map-support' -import { Ignitor } from '@adonisjs/core/build/standalone' -import { configure, processCliArgs, run, RunnerHooksHandler } from '@japa/runner' - -sourceMapSupport.install({ handleUncaughtExceptions: false }) - -const kernel = new Ignitor(__dirname).kernel('test') - -kernel - .boot() - .then(() => import('./tests/bootstrap')) - .then(({ runnerHooks, ...config }) => { - const app: RunnerHooksHandler[] = [() => kernel.start()] - - configure({ - ...kernel.application.rcFile.tests, - ...processCliArgs(process.argv.slice(2)), - ...config, - ...{ - importer: (filePath) => import(filePath), - setup: app.concat(runnerHooks.setup), - teardown: runnerHooks.teardown, - }, - cwd: kernel.application.appRoot - }) - - run() - }) diff --git a/templates/test.txt b/templates/test.txt deleted file mode 100644 index d83c9fc..0000000 --- a/templates/test.txt +++ /dev/null @@ -1,5 +0,0 @@ -import { test } from '@japa/runner' - -test.group('{{ name }}', () => { - // Write your test here -}) diff --git a/templates/tests-contract.txt b/templates/tests-contract.txt deleted file mode 100644 index adbcbe1..0000000 --- a/templates/tests-contract.txt +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Contract source: https://bit.ly/3DP1ypf - * - * Feel free to let us know via PR, if you find something broken in this contract - * file. - */ - -import '@japa/runner' - -declare module '@japa/runner' { - interface TestContext { - // Extend context - } - - interface Test { - // Extend test - } -} diff --git a/templates/tests/bootstrap.txt b/templates/tests/bootstrap.txt deleted file mode 100644 index d574211..0000000 --- a/templates/tests/bootstrap.txt +++ /dev/null @@ -1,69 +0,0 @@ -/** - * File source: https://bit.ly/3ukaHTz - * - * Feel free to let us know via PR, if you find something broken in this contract - * file. - */ - -import type { Config } from '@japa/runner' -import TestUtils from '@ioc:Adonis/Core/TestUtils' -import { assert, runFailedTests, specReporter, apiClient } from '@japa/preset-adonis' - -/* -|-------------------------------------------------------------------------- -| Japa Plugins -|-------------------------------------------------------------------------- -| -| Japa plugins allows you to add additional features to Japa. By default -| we register the assertion plugin. -| -| Feel free to remove existing plugins or add more. -| -*/ -export const plugins: Required['plugins'] = [assert(), runFailedTests(), apiClient()] - -/* -|-------------------------------------------------------------------------- -| Japa Reporters -|-------------------------------------------------------------------------- -| -| Japa reporters displays/saves the progress of tests as they are executed. -| By default, we register the spec reporter to show a detailed report -| of tests on the terminal. -| -*/ -export const reporters: Required['reporters'] = [specReporter()] - -/* -|-------------------------------------------------------------------------- -| Runner hooks -|-------------------------------------------------------------------------- -| -| Runner hooks are executed after booting the AdonisJS app and -| before the test files are imported. -| -| You can perform actions like starting the HTTP server or running migrations -| within the runner hooks -| -*/ -export const runnerHooks: Pick, 'setup' | 'teardown'> = { - setup: [() => TestUtils.ace().loadCommands()], - teardown: [], -} - -/* -|-------------------------------------------------------------------------- -| Configure individual suites -|-------------------------------------------------------------------------- -| -| The configureSuite method gets called for every test suite registered -| within ".adonisrc.json" file. -| -| You can use this method to configure suites. For example: Only start -| the HTTP server when it is a functional suite. -*/ -export const configureSuite: Required['configureSuite'] = (suite) => { - if (suite.name === 'functional') { - suite.setup(() => TestUtils.httpServer().start()) - } -} diff --git a/templates/tests/functional/hello_world_api.spec.txt b/templates/tests/functional/hello_world_api.spec.txt deleted file mode 100644 index 7fa3df4..0000000 --- a/templates/tests/functional/hello_world_api.spec.txt +++ /dev/null @@ -1,8 +0,0 @@ -import { test } from '@japa/runner' - -test('display welcome page', async ({ client }) => { - const response = await client.get('/') - - response.assertStatus(200) - response.assertBodyContains({ hello: 'world' }) -}) diff --git a/templates/tests/functional/hello_world_slim.spec.txt b/templates/tests/functional/hello_world_slim.spec.txt deleted file mode 100644 index 25377d0..0000000 --- a/templates/tests/functional/hello_world_slim.spec.txt +++ /dev/null @@ -1,8 +0,0 @@ -import { test } from '@japa/runner' - -test('display welcome page', async ({ client }) => { - const response = await client.get('/') - - response.assertStatus(200) - response.assertTextIncludes('Hello world') -}) diff --git a/templates/tests/functional/hello_world_web.spec.txt b/templates/tests/functional/hello_world_web.spec.txt deleted file mode 100644 index e4bbf6d..0000000 --- a/templates/tests/functional/hello_world_web.spec.txt +++ /dev/null @@ -1,8 +0,0 @@ -import { test } from '@japa/runner' - -test('display welcome page', async ({ client }) => { - const response = await client.get('/') - - response.assertStatus(200) - response.assertTextIncludes('

It Works!

') -}) diff --git a/templates/validator.txt b/templates/validator.txt deleted file mode 100644 index 1c775a6..0000000 --- a/templates/validator.txt +++ /dev/null @@ -1,40 +0,0 @@ -import { schema, CustomMessages } from '@ioc:Adonis/Core/Validator' -import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' - -export default class {{ filename }} { - constructor(protected ctx: HttpContextContract) {} - - /* - * Define schema to validate the "shape", "type", "formatting" and "integrity" of data. - * - * For example: - * 1. The username must be of data type string. But then also, it should - * not contain special characters or numbers. - * ``` - * schema.string({}, [ rules.alpha() ]) - * ``` - * - * 2. The email must be of data type string, formatted as a valid - * email. But also, not used by any other user. - * ``` - * schema.string({}, [ - * rules.email(), - * rules.unique({ table: 'users', column: 'email' }), - * ]) - * ``` - */ - public schema = schema.create({}) - - /** - * Custom messages for validation failures. You can make use of dot notation `(.)` - * for targeting nested fields and array expressions `(*)` for targeting all - * children of an array. For example: - * - * { - * 'profile.username.required': 'Username is required', - * 'scores.*.number': 'Define scores as valid numbers' - * } - * - */ - public messages: CustomMessages = {} -} diff --git a/templates/view.txt b/templates/view.txt deleted file mode 100644 index e69de29..0000000 diff --git a/templates/webpack.config.txt b/templates/webpack.config.txt deleted file mode 100644 index 914b586..0000000 --- a/templates/webpack.config.txt +++ /dev/null @@ -1,214 +0,0 @@ -const { join } = require('path') -const Encore = require('@symfony/webpack-encore') - -/* -|-------------------------------------------------------------------------- -| Encore runtime environment -|-------------------------------------------------------------------------- -*/ -if (!Encore.isRuntimeEnvironmentConfigured()) { - Encore.configureRuntimeEnvironment(process.env.NODE_ENV || 'dev') -} - -/* -|-------------------------------------------------------------------------- -| Output path -|-------------------------------------------------------------------------- -| -| The output path for writing the compiled files. It should always -| be inside the public directory, so that AdonisJS can serve it. -| -*/ -Encore.setOutputPath('./public/assets') - -/* -|-------------------------------------------------------------------------- -| Public URI -|-------------------------------------------------------------------------- -| -| The public URI to access the static files. It should always be -| relative from the "public" directory. -| -*/ -Encore.setPublicPath('/assets') - -/* -|-------------------------------------------------------------------------- -| Entrypoints -|-------------------------------------------------------------------------- -| -| Entrypoints are script files that boots your frontend application. Ideally -| a single entrypoint is used by majority of applications. However, feel -| free to add more (if required). -| -| Also, make sure to read the docs on "Assets bundler" to learn more about -| entrypoints. -| -*/ -Encore.addEntry('app', './resources/js/app.js') - -/* -|-------------------------------------------------------------------------- -| Copy assets -|-------------------------------------------------------------------------- -| -| Since the edge templates are not part of the Webpack compile lifecycle, any -| images referenced by it will not be processed by Webpack automatically. Hence -| we must copy them manually. -| -*/ -// Encore.copyFiles({ -// from: './resources/images', -// to: 'images/[path][name].[hash:8].[ext]', -// }) - -/* -|-------------------------------------------------------------------------- -| Split shared code -|-------------------------------------------------------------------------- -| -| Instead of bundling duplicate code in all the bundles, generate a separate -| bundle for the shared code. -| -| https://symfony.com/doc/current/frontend/encore/split-chunks.html -| https://webpack.js.org/plugins/split-chunks-plugin/ -| -*/ -// Encore.splitEntryChunks() - -/* -|-------------------------------------------------------------------------- -| Isolated entrypoints -|-------------------------------------------------------------------------- -| -| Treat each entry point and its dependencies as its own isolated module. -| -*/ -Encore.disableSingleRuntimeChunk() - -/* -|-------------------------------------------------------------------------- -| Cleanup output folder -|-------------------------------------------------------------------------- -| -| It is always nice to cleanup the build output before creating a build. It -| will ensure that all unused files from the previous build are removed. -| -*/ -Encore.cleanupOutputBeforeBuild() - -/* -|-------------------------------------------------------------------------- -| Source maps -|-------------------------------------------------------------------------- -| -| Enable source maps in production -| -*/ -Encore.enableSourceMaps(!Encore.isProduction()) - -/* -|-------------------------------------------------------------------------- -| Assets versioning -|-------------------------------------------------------------------------- -| -| Enable assets versioning to leverage lifetime browser and CDN cache -| -*/ -Encore.enableVersioning(Encore.isProduction()) - -/* -|-------------------------------------------------------------------------- -| Configure dev server -|-------------------------------------------------------------------------- -| -| Here we configure the dev server to enable live reloading for edge templates. -| Remember edge templates are not processed by Webpack and hence we need -| to watch them explicitly and livereload the browser. -| -*/ -Encore.configureDevServerOptions((options) => { - /** - * Normalize "options.static" property to an array - */ - if (!options.static) { - options.static = [] - } else if (!Array.isArray(options.static)) { - options.static = [options.static] - } - - /** - * Enable live reload and add views directory - */ - options.liveReload = true - options.static.push({ - directory: join(__dirname, './resources/views'), - watch: true, - }) -}) - -/* -|-------------------------------------------------------------------------- -| CSS precompilers support -|-------------------------------------------------------------------------- -| -| Uncomment one of the following lines of code to enable support for your -| favorite CSS precompiler -| -*/ -// Encore.enableSassLoader() -// Encore.enableLessLoader() -// Encore.enableStylusLoader() - -/* -|-------------------------------------------------------------------------- -| CSS loaders -|-------------------------------------------------------------------------- -| -| Uncomment one of the following line of code to enable support for -| PostCSS or CSS. -| -*/ -// Encore.enablePostCssLoader() -// Encore.configureCssLoader(() => {}) - -/* -|-------------------------------------------------------------------------- -| Enable Vue loader -|-------------------------------------------------------------------------- -| -| Uncomment the following lines of code to enable support for vue. Also make -| sure to install the required dependencies. -| -*/ -// Encore.enableVueLoader(() => {}, { -// version: 3, -// runtimeCompilerBuild: false, -// useJsx: false -// }) - -/* -|-------------------------------------------------------------------------- -| Configure logging -|-------------------------------------------------------------------------- -| -| To keep the terminal clean from unnecessary info statements , we only -| log warnings and errors. If you want all the logs, you can change -| the level to "info". -| -*/ -const config = Encore.getWebpackConfig() -config.infrastructureLogging = { - level: 'warn', -} -config.stats = 'errors-warnings' - -/* -|-------------------------------------------------------------------------- -| Export config -|-------------------------------------------------------------------------- -| -| Export config for webpack to do its job -| -*/ -module.exports = config diff --git a/test-helpers/index.ts b/test-helpers/index.ts deleted file mode 100644 index 64c26bb..0000000 --- a/test-helpers/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -export function toNewlineArray(contents: string): string[] { - return contents.split(/\r?\n/) -} - -export const info = '[ blue(info) ]' -export const success = '[ green(success) ]' -export const error = '[ red(error) ]' -export const warning = '[ yellow(warn) ]' -export const dimYellow = (value: string) => `dim(yellow(${value}))` diff --git a/test/compiler.spec.ts b/test/compiler.spec.ts deleted file mode 100644 index 95d8c6e..0000000 --- a/test/compiler.spec.ts +++ /dev/null @@ -1,905 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { test } from '@japa/runner' -import execa from 'execa' -import { join } from 'path' -import { Filesystem } from '@poppinss/dev-utils' -import { instantiate } from '@poppinss/cliui/build/api' - -import { Compiler } from '../src/Compiler' -import { success, info, warning, error, dimYellow } from '../test-helpers' - -const ui = instantiate(true) -const fs = new Filesystem(join(__dirname, '__app')) - -test.group('Compiler', (group) => { - group.setup(() => { - ui.logger.useRenderer(ui.testingRenderer) - }) - - group.each.teardown(() => { - ui.testingRenderer.logs = [] - }) - - group.each.teardown(async () => { - await fs.cleanup() - }) - - test('build source files', async ({ assert }) => { - await fs.add( - '.adonisrc.json', - JSON.stringify({ - typescript: true, - metaFiles: ['public/**/*.(js|css)'], - }) - ) - - await fs.add( - 'tsconfig.json', - JSON.stringify({ - include: ['**/*'], - exclude: ['build'], - compilerOptions: {}, - }) - ) - - await fs.add('ace', '') - await fs.add('src/foo.ts', '') - await fs.add('public/styles/main.css', '') - await fs.add('public/scripts/main.js', '') - - const compiler = new Compiler(fs.basePath, [], false, ui.logger) - await compiler.compile() - - const hasFiles = await Promise.all( - [ - 'build/.adonisrc.json', - 'build/ace', - 'build/src/foo.js', - 'build/public/styles/main.css', - 'build/public/scripts/main.js', - ].map((file) => fs.fsExtra.pathExists(join(fs.basePath, file))) - ) - - assert.deepEqual(hasFiles, [true, true, true, true, true]) - - assert.deepEqual(ui.testingRenderer.logs, [ - { - message: `${info} cleaning up ${dimYellow('"./build"')} directory`, - stream: 'stdout', - }, - { - message: `${info} compiling typescript source files`, - stream: 'stdout', - }, - { - message: `${info} copy { ${dimYellow('public/**/*.(js|css),ace => build')} }`, - stream: 'stdout', - }, - { - message: `${info} copy { ${dimYellow('.adonisrc.json => build')} }`, - stream: 'stdout', - }, - { - message: `${success} built successfully`, - stream: 'stdout', - }, - ]) - - assert.isFalse(require(join(fs.basePath, 'build', '.adonisrc.json')).typescript) - }).timeout(0) - - test('build source files with explicit outDir', async ({ assert }) => { - await fs.add( - '.adonisrc.json', - JSON.stringify({ - typescript: true, - metaFiles: ['public/**/*.(js|css)'], - }) - ) - - await fs.add( - 'tsconfig.json', - JSON.stringify({ - include: ['**/*'], - exclude: ['build'], - compilerOptions: { - outDir: 'build', - }, - }) - ) - - await fs.add('ace', '') - await fs.add('src/foo.ts', '') - await fs.add('public/styles/main.css', '') - await fs.add('public/scripts/main.js', '') - - const compiler = new Compiler(fs.basePath, [], false, ui.logger) - await compiler.compile() - - const hasFiles = await Promise.all( - [ - 'build/.adonisrc.json', - 'build/src/foo.js', - 'build/public/styles/main.css', - 'build/public/scripts/main.js', - ].map((file) => fs.fsExtra.pathExists(join(fs.basePath, file))) - ) - - assert.deepEqual(hasFiles, [true, true, true, true]) - assert.deepEqual(ui.testingRenderer.logs, [ - { - message: `${info} cleaning up ${dimYellow('"./build"')} directory`, - stream: 'stdout', - }, - { - message: `${info} compiling typescript source files`, - stream: 'stdout', - }, - { - message: `${info} copy { ${dimYellow('public/**/*.(js|css),ace => build')} }`, - stream: 'stdout', - }, - { - message: `${info} copy { ${dimYellow('.adonisrc.json => build')} }`, - stream: 'stdout', - }, - { - message: `${success} built successfully`, - stream: 'stdout', - }, - ]) - }).timeout(0) - - test('build source files with explicit rootDir', async ({ assert }) => { - await fs.add( - '.adonisrc.json', - JSON.stringify({ - typescript: true, - metaFiles: ['public/**/*.(js|css)'], - }) - ) - - await fs.add( - 'tsconfig.json', - JSON.stringify({ - include: ['**/*'], - exclude: ['build'], - compilerOptions: { - rootDir: './', - outDir: 'build', - }, - }) - ) - - await fs.add('ace', '') - await fs.add('src/foo.ts', '') - await fs.add('public/styles/main.css', '') - await fs.add('public/scripts/main.js', '') - - const compiler = new Compiler(fs.basePath, [], false, ui.logger) - await compiler.compile() - - const hasFiles = await Promise.all( - [ - 'build/.adonisrc.json', - 'build/src/foo.js', - 'build/public/styles/main.css', - 'build/public/scripts/main.js', - ].map((file) => fs.fsExtra.pathExists(join(fs.basePath, file))) - ) - - assert.deepEqual(hasFiles, [true, true, true, true]) - assert.deepEqual(ui.testingRenderer.logs, [ - { - message: `${info} cleaning up ${dimYellow('"./build"')} directory`, - stream: 'stdout', - }, - { - message: `${info} compiling typescript source files`, - stream: 'stdout', - }, - { - message: `${info} copy { ${dimYellow('public/**/*.(js|css),ace => build')} }`, - stream: 'stdout', - }, - { - message: `${info} copy { ${dimYellow('.adonisrc.json => build')} }`, - stream: 'stdout', - }, - { - message: `${success} built successfully`, - stream: 'stdout', - }, - ]) - }).timeout(0) - - test('build source files to nested outDir', async ({ assert }) => { - await fs.add( - '.adonisrc.json', - JSON.stringify({ - typescript: true, - metaFiles: ['public/**/*.(js|css)'], - }) - ) - - await fs.add( - 'tsconfig.json', - JSON.stringify({ - include: ['**/*'], - exclude: ['build'], - compilerOptions: { - rootDir: './', - outDir: 'build/dist', - }, - }) - ) - - await fs.add('ace', '') - await fs.add('src/foo.ts', '') - await fs.add('public/styles/main.css', '') - await fs.add('public/scripts/main.js', '') - - const compiler = new Compiler(fs.basePath, [], false, ui.logger) - await compiler.compile() - - const hasFiles = await Promise.all( - [ - 'build/dist/.adonisrc.json', - 'build/dist/src/foo.js', - 'build/dist/public/styles/main.css', - 'build/dist/public/scripts/main.js', - ].map((file) => fs.fsExtra.pathExists(join(fs.basePath, file))) - ) - - assert.deepEqual(hasFiles, [true, true, true, true]) - assert.deepEqual(ui.testingRenderer.logs, [ - { - message: `${info} cleaning up ${dimYellow('"./build/dist"')} directory`, - stream: 'stdout', - }, - { - message: `${info} compiling typescript source files`, - stream: 'stdout', - }, - { - message: `${info} copy { ${dimYellow('public/**/*.(js|css),ace => build/dist')} }`, - stream: 'stdout', - }, - { - message: `${info} copy { ${dimYellow('.adonisrc.json => build/dist')} }`, - stream: 'stdout', - }, - { - message: `${success} built successfully`, - stream: 'stdout', - }, - ]) - }).timeout(0) - - test('do not build when config has errors', async ({ assert }) => { - await fs.add( - '.adonisrc.json', - JSON.stringify({ - typescript: true, - metaFiles: ['public/**/*.(js|css)'], - }) - ) - - await fs.add( - 'tsconfig.json', - JSON.stringify({ - include: ['**/*'], - exclude: ['build'], - compilerOptions: { - foo: 'bar', - rootDir: './', - outDir: 'build/dist', - }, - }) - ) - - await fs.add('ace', '') - await fs.add('src/foo.ts', "import path from 'path'") - await fs.add('public/styles/main.css', '') - await fs.add('public/scripts/main.js', '') - - const compiler = new Compiler(fs.basePath, [], false, ui.logger) - await compiler.compile() - - const hasFiles = await Promise.all( - [ - 'build/dist/.adonisrc.json', - 'build/dist/src/foo.js', - 'build/dist/public/styles/main.css', - 'build/dist/public/scripts/main.js', - ].map((file) => fs.fsExtra.pathExists(join(fs.basePath, file))) - ) - - assert.deepEqual(hasFiles, [false, false, false, false]) - - assert.deepEqual(ui.testingRenderer.logs, [ - { - message: `${error} unable to parse tsconfig.json`, - stream: 'stderr', - }, - ]) - }).timeout(0) - - test('catch and report typescript errors', async ({ assert }) => { - await fs.add( - '.adonisrc.json', - JSON.stringify({ - typescript: true, - metaFiles: ['public/**/*.(js|css)'], - }) - ) - - await fs.add( - 'tsconfig.json', - JSON.stringify({ - include: ['**/*'], - exclude: ['build'], - compilerOptions: { - rootDir: './', - outDir: 'build/dist', - }, - }) - ) - - await fs.add('ace', '') - await fs.add('src/foo.ts', "import path from 'path'") - await fs.add('public/styles/main.css', '') - await fs.add('public/scripts/main.js', '') - - const compiler = new Compiler(fs.basePath, [], false, ui.logger) - await compiler.compile(false) - - const hasFiles = await Promise.all( - [ - 'build/dist/.adonisrc.json', - 'build/dist/src/foo.js', - 'build/dist/public/styles/main.css', - 'build/dist/public/scripts/main.js', - ].map((file) => fs.fsExtra.pathExists(join(fs.basePath, file))) - ) - - assert.deepEqual(hasFiles, [true, true, true, true]) - - assert.deepEqual(ui.testingRenderer.logs, [ - { - message: `${info} cleaning up ${dimYellow('"./build/dist"')} directory`, - stream: 'stdout', - }, - { - message: `${info} compiling typescript source files`, - stream: 'stdout', - }, - { - message: `${error} typescript compiler errors`, - stream: 'stderr', - }, - { - message: `${info} copy { ${dimYellow('public/**/*.(js|css),ace => build/dist')} }`, - stream: 'stdout', - }, - { - message: `${info} copy { ${dimYellow('.adonisrc.json => build/dist')} }`, - stream: 'stdout', - }, - { - message: `${success} built successfully`, - stream: 'stdout', - }, - ]) - }).timeout(0) - - test('do not continue on error', async ({ assert }) => { - await fs.add( - '.adonisrc.json', - JSON.stringify({ - typescript: true, - metaFiles: ['public/**/*.(js|css)'], - }) - ) - - await fs.add( - 'tsconfig.json', - JSON.stringify({ - include: ['**/*'], - exclude: ['build'], - compilerOptions: { - rootDir: './', - outDir: 'build/dist', - }, - }) - ) - - await fs.add('ace', '') - await fs.add('src/foo.ts', "import path from 'path'") - await fs.add('public/styles/main.css', '') - await fs.add('public/scripts/main.js', '') - - const compiler = new Compiler(fs.basePath, [], false, ui.logger) - await compiler.compile(true) - - const hasFiles = await Promise.all( - [ - 'build/dist/.adonisrc.json', - 'build/dist/src/foo.js', - 'build/dist/public/styles/main.css', - 'build/dist/public/scripts/main.js', - ].map((file) => fs.fsExtra.pathExists(join(fs.basePath, file))) - ) - - assert.deepEqual(hasFiles, [false, false, false, false]) - - assert.deepEqual(ui.testingRenderer.logs, [ - { - message: `${info} cleaning up ${dimYellow('"./build/dist"')} directory`, - stream: 'stdout', - }, - { - message: `${info} compiling typescript source files`, - stream: 'stdout', - }, - { - message: `${error} typescript compiler errors`, - stream: 'stderr', - }, - { - message: '', - stream: 'stderr', - }, - { - message: `bgRed(Cannot complete the build process as there are typescript errors. Use "--ignore-ts-errors" flag to ignore Typescript errors)`, - stream: 'stderr', - }, - { - message: `${info} cleaning up ${dimYellow('"./build/dist"')} directory`, - stream: 'stdout', - }, - ]) - }).timeout(0) - - test('do not emit when noEmitOnError is true', async ({ assert }) => { - await fs.add( - '.adonisrc.json', - JSON.stringify({ - typescript: true, - metaFiles: ['public/**/*.(js|css)'], - }) - ) - - await fs.add( - 'tsconfig.json', - JSON.stringify({ - include: ['**/*'], - exclude: ['build'], - compilerOptions: { - rootDir: './', - outDir: 'build/dist', - noEmitOnError: true, - }, - }) - ) - - await fs.add('ace', '') - await fs.add('src/foo.ts', "import path from 'path'") - await fs.add('public/styles/main.css', '') - await fs.add('public/scripts/main.js', '') - - const compiler = new Compiler(fs.basePath, [], false, ui.logger) - await compiler.compile() - - const hasFiles = await Promise.all( - [ - 'build/dist/.adonisrc.json', - 'build/dist/src/foo.js', - 'build/dist/public/styles/main.css', - 'build/dist/public/scripts/main.js', - ].map((file) => fs.fsExtra.pathExists(join(fs.basePath, file))) - ) - - assert.deepEqual(hasFiles, [false, false, false, false]) - - assert.deepEqual(ui.testingRenderer.logs, [ - { - message: `${info} cleaning up ${dimYellow('"./build/dist"')} directory`, - stream: 'stdout', - }, - { - message: `${info} compiling typescript source files`, - stream: 'stdout', - }, - { - message: `${warning} typescript emit skipped`, - stream: 'stdout', - }, - { - message: `${error} typescript compiler errors`, - stream: 'stderr', - }, - ]) - }).timeout(0) - - test('build for production should copy package files to build folder', async ({ assert }) => { - await fs.add( - '.adonisrc.json', - JSON.stringify({ - typescript: true, - metaFiles: ['public/**/*.(js|css)'], - }) - ) - - await fs.add( - 'package.json', - JSON.stringify({ - name: 'my-dummy-app', - dependencies: { - lodash: 'latest', - }, - }) - ) - - await fs.add( - 'tsconfig.json', - JSON.stringify({ - include: ['**/*'], - exclude: ['build'], - compilerOptions: { - outDir: 'build', - }, - }) - ) - - await fs.add('ace', '') - await fs.add('src/foo.ts', '') - await fs.add('public/styles/main.css', '') - await fs.add('public/scripts/main.js', '') - await execa('npm', ['install'], { - buffer: false, - cwd: fs.basePath, - stdio: 'inherit', - }) - - const compiler = new Compiler(fs.basePath, [], false, ui.logger) - await compiler.compileForProduction(false, 'npm') - - const hasFiles = await Promise.all( - [ - 'build/.adonisrc.json', - 'build/src/foo.js', - 'build/public/styles/main.css', - 'build/public/scripts/main.js', - ].map((file) => fs.fsExtra.pathExists(join(fs.basePath, file))) - ) - - assert.deepEqual(hasFiles, [true, true, true, true]) - assert.deepEqual(ui.testingRenderer.logs, [ - { - message: `${info} cleaning up ${dimYellow('"./build"')} directory`, - stream: 'stdout', - }, - { - message: `${info} compiling typescript source files`, - stream: 'stdout', - }, - { - message: `${info} copy { ${dimYellow( - 'public/**/*.(js|css),ace,package.json,package-lock.json => build' - )} }`, - stream: 'stdout', - }, - { - message: `${info} copy { ${dimYellow('.adonisrc.json => build')} }`, - stream: 'stdout', - }, - { - message: `${success} built successfully`, - stream: 'stdout', - }, - { - message: '', - stream: 'stdout', - }, - ]) - - const hasPackageLock = await fs.fsExtra.pathExists( - join(fs.basePath, 'build', 'package-lock.json') - ) - assert.isTrue(hasPackageLock) - }).timeout(0) - - test('gracefully log error when ace file finishes with non-zero exit code', async ({ - assert, - }) => { - await fs.add( - '.adonisrc.json', - JSON.stringify({ - typescript: true, - metaFiles: ['public/**/*.(js|css)'], - }) - ) - - await fs.add( - 'tsconfig.json', - JSON.stringify({ - include: ['**/*'], - exclude: ['build'], - compilerOptions: {}, - }) - ) - - await fs.add('ace', "console.error('foo');process.exit(1)") - await fs.add('src/foo.ts', '') - await fs.add('public/styles/main.css', '') - await fs.add('public/scripts/main.js', '') - - const compiler = new Compiler(fs.basePath, [], false, ui.logger) - await compiler.compile() - - const hasFiles = await Promise.all( - [ - 'build/.adonisrc.json', - 'build/ace', - 'build/src/foo.js', - 'build/public/styles/main.css', - 'build/public/scripts/main.js', - ].map((file) => fs.fsExtra.pathExists(join(fs.basePath, file))) - ) - - assert.deepEqual(hasFiles, [false, false, false, false, false]) - assert.deepEqual(ui.testingRenderer.logs, [ - { - message: `${info} cleaning up ${dimYellow('"./build"')} directory`, - stream: 'stdout', - }, - { - message: `${info} compiling typescript source files`, - stream: 'stdout', - }, - { - message: `${info} copy { ${dimYellow('public/**/*.(js|css),ace => build')} }`, - stream: 'stdout', - }, - { - message: `${info} copy { ${dimYellow('.adonisrc.json => build')} }`, - stream: 'stdout', - }, - { - message: `${warning} Unable to generate manifest file. Check the following error for more info`, - stream: 'stdout', - }, - { - message: 'foo', - stream: 'stderr', - }, - { - message: `${info} cleaning up ${dimYellow('"./build"')} directory`, - stream: 'stdout', - }, - ]) - - assert.isFalse(require(join(fs.basePath, 'build', '.adonisrc.json')).typescript) - }).timeout(0) - - test('ignore error when any of the meta file is missing', async ({ assert }) => { - await fs.add( - '.adonisrc.json', - JSON.stringify({ - typescript: true, - metaFiles: ['public/css/app.js'], - }) - ) - - await fs.add( - 'tsconfig.json', - JSON.stringify({ - include: ['**/*'], - exclude: ['build'], - compilerOptions: {}, - }) - ) - - await fs.add('src/foo.ts', '') - await fs.add('ace', '') - - const compiler = new Compiler(fs.basePath, [], false, ui.logger) - await compiler.compile() - - const hasFiles = await Promise.all( - ['build/.adonisrc.json', 'build/ace', 'build/src/foo.js', 'build/public/css/app.js'].map( - (file) => fs.fsExtra.pathExists(join(fs.basePath, file)) - ) - ) - - ui.testingRenderer.logs.pop() - - assert.deepEqual(hasFiles, [true, true, true, false]) - assert.deepEqual(ui.testingRenderer.logs, [ - { - message: `${info} cleaning up ${dimYellow('"./build"')} directory`, - stream: 'stdout', - }, - { - message: `${info} compiling typescript source files`, - stream: 'stdout', - }, - { - message: `${info} copy { ${dimYellow('public/css/app.js,ace => build')} }`, - stream: 'stdout', - }, - { - message: `${info} copy { ${dimYellow('.adonisrc.json => build')} }`, - stream: 'stdout', - }, - ]) - - assert.isFalse(require(join(fs.basePath, 'build', '.adonisrc.json')).typescript) - }).timeout(0) - - test('build should support custom tsconfig file', async ({ assert }) => { - await fs.add( - '.adonisrc.json', - JSON.stringify({ - typescript: true, - }) - ) - - await fs.add( - 'package.json', - JSON.stringify({ - name: 'my-dummy-app', - dependencies: {}, - }) - ) - - await fs.add( - 'tsconfig.json', - JSON.stringify({ - include: ['**/*'], - exclude: ['build'], - compilerOptions: { - outDir: 'build', - }, - }) - ) - - await fs.add( - 'tsconfig.production.json', - JSON.stringify({ - extends: './tsconfig.json', - exclude: ['build', 'src/ignored.ts'], - }) - ) - - await fs.add('ace', '') - await fs.add('src/foo.ts', '') - await fs.add('src/ignored.ts', '') - - const compiler = new Compiler(fs.basePath, [], false, ui.logger, 'tsconfig.production.json') - await compiler.compileForProduction(false, 'npm') - - const hasFiles = await Promise.all( - ['build/.adonisrc.json', 'build/src/foo.js', 'build/src/ignored.js'].map((file) => - fs.fsExtra.pathExists(join(fs.basePath, file)) - ) - ) - - assert.deepEqual(hasFiles, [true, true, false]) - }).timeout(0) - - test('typecheck and report typescript errors', async ({ assert }) => { - await fs.add( - '.adonisrc.json', - JSON.stringify({ - typescript: true, - metaFiles: ['public/**/*.(js|css)'], - }) - ) - - await fs.add( - 'tsconfig.json', - JSON.stringify({ - include: ['**/*'], - exclude: ['build'], - compilerOptions: { - rootDir: './', - outDir: 'build/dist', - }, - }) - ) - - await fs.add('ace', '') - await fs.add('src/foo.ts', "import path from 'path'") - await fs.add('public/styles/main.css', '') - await fs.add('public/scripts/main.js', '') - - const compiler = new Compiler(fs.basePath, [], false, ui.logger) - const isValid = await compiler.typeCheck() - - assert.isFalse(isValid) - const hasFiles = await Promise.all( - [ - 'build/dist/.adonisrc.json', - 'build/dist/src/foo.js', - 'build/dist/public/styles/main.css', - 'build/dist/public/scripts/main.js', - ].map((file) => fs.fsExtra.pathExists(join(fs.basePath, file))) - ) - - assert.deepEqual(hasFiles, [false, false, false, false]) - - assert.deepEqual(ui.testingRenderer.logs, [ - { - message: `${info} type checking typescript source files`, - stream: 'stdout', - }, - { - message: `${error} typescript compiler errors`, - stream: 'stderr', - }, - ]) - }).timeout(0) - - test('complete successfully when typechecking has no errors', async ({ assert }) => { - await fs.add( - '.adonisrc.json', - JSON.stringify({ - typescript: true, - metaFiles: ['public/**/*.(js|css)'], - }) - ) - - await fs.add( - 'tsconfig.json', - JSON.stringify({ - include: ['**/*'], - exclude: ['build'], - compilerOptions: { - rootDir: './', - outDir: 'build/dist', - }, - }) - ) - - await fs.add('ace', '') - await fs.add('src/foo.ts', "import 'path'") - await fs.add('public/styles/main.css', '') - await fs.add('public/scripts/main.js', '') - - const compiler = new Compiler(fs.basePath, [], false, ui.logger) - const isValid = await compiler.typeCheck() - - assert.isTrue(isValid) - const hasFiles = await Promise.all( - [ - 'build/dist/.adonisrc.json', - 'build/dist/src/foo.js', - 'build/dist/public/styles/main.css', - 'build/dist/public/scripts/main.js', - ].map((file) => fs.fsExtra.pathExists(join(fs.basePath, file))) - ) - - assert.deepEqual(hasFiles, [false, false, false, false]) - - assert.deepEqual(ui.testingRenderer.logs, [ - { - message: `${info} type checking typescript source files`, - stream: 'stdout', - }, - { - message: `${success} built successfully`, - stream: 'stdout', - }, - ]) - }).timeout(0) -}) diff --git a/test/configure-encore.spec.ts b/test/configure-encore.spec.ts deleted file mode 100644 index c01f59d..0000000 --- a/test/configure-encore.spec.ts +++ /dev/null @@ -1,60 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { test } from '@japa/runner' -import { join } from 'path' -import { Kernel } from '@adonisjs/ace' -import { Filesystem } from '@poppinss/dev-utils' -import { Application } from '@adonisjs/application' - -import Invoke from '../commands/Invoke' - -const fs = new Filesystem(join(__dirname, '__app')) - -test.group('Configure Encore', (group) => { - group.each.teardown(async () => { - await fs.cleanup() - }) - - test('setup encore', async ({ assert }) => { - await fs.add( - 'package.json', - JSON.stringify({ - name: 'sample_app', - }) - ) - - await fs.ensureRoot() - const app = new Application(fs.basePath, 'test', {}) - - const invoke = new Invoke(app, new Kernel(app).mockConsoleOutput()) - invoke.packages = ['encore'] - await invoke.run() - - const envFile = await fs.fsExtra.pathExists(join(fs.basePath, 'webpack.config.js')) - const envExampleFile = await fs.fsExtra.readFile( - join(fs.basePath, 'resources/js/app.js'), - 'utf-8' - ) - - const pkgFile = await fs.get('package.json') - assert.properties(JSON.parse(pkgFile).devDependencies, [ - '@babel/core', - '@babel/preset-env', - '@symfony/webpack-encore', - 'webpack', - 'webpack-cli', - ]) - - assert.isTrue(envFile) - assert.equal(envExampleFile.trim(), '// app entrypoint') - }) - .timeout(0) - .skip(!!process.env.CI) -}) diff --git a/test/configure-tests.spec.ts b/test/configure-tests.spec.ts deleted file mode 100644 index e73b51e..0000000 --- a/test/configure-tests.spec.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { test } from '@japa/runner' -import { join } from 'path' -import { Kernel } from '@adonisjs/ace' -import { Filesystem } from '@poppinss/dev-utils' -import { Application } from '@adonisjs/application' - -import Invoke from '../commands/Invoke' - -const fs = new Filesystem(join(__dirname, '__app')) - -test.group('Configure Tests', (group) => { - group.each.teardown(async () => { - await fs.cleanup() - }) - - test('setup tests', async ({ assert }) => { - await fs.add( - 'package.json', - JSON.stringify({ - name: 'sample_app', - }) - ) - - await fs.ensureRoot() - const app = new Application(fs.basePath, 'test', {}) - - const invoke = new Invoke(app, new Kernel(app).mockConsoleOutput()) - invoke.packages = ['tests'] - await invoke.run() - - assert.isTrue(await fs.fsExtra.pathExists(join(fs.basePath, 'test.ts'))) - assert.isTrue(await fs.fsExtra.pathExists(join(fs.basePath, 'tests/bootstrap.ts'))) - assert.isTrue( - await fs.fsExtra.pathExists(join(fs.basePath, 'tests/functional/hello_world.spec.ts')) - ) - assert.isTrue(await fs.fsExtra.pathExists(join(fs.basePath, 'contracts/tests.ts'))) - }) - .timeout(0) - .skip(!!process.env.CI) -}) diff --git a/test/env-parser.spec.ts b/test/env-parser.spec.ts deleted file mode 100644 index 02efc63..0000000 --- a/test/env-parser.spec.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { test } from '@japa/runner' -import { join } from 'path' -import { Filesystem } from '@poppinss/dev-utils' - -import { EnvParser } from '../src/EnvParser' - -const fs = new Filesystem(join(__dirname, '__app')) - -test.group('EnvParser', (group) => { - group.each.teardown(async () => { - await fs.cleanup() - }) - - test('ignore exception raised when unable to lookup .env file', async () => { - const envParser = new EnvParser() - await envParser.parse(fs.basePath) - }) - - test('get value for a key defined inside .env file', async ({ assert }) => { - await fs.add('.env', 'PORT=3333') - - const envParser = new EnvParser() - await envParser.parse(fs.basePath) - assert.equal(envParser.get('PORT'), '3333') - }) - - test('get an object of values for defined keys', async ({ assert }) => { - await fs.add('.env', ['PORT=3333', 'TZ=Asia/Calcutta'].join('\n')) - - const envParser = new EnvParser() - await envParser.parse(fs.basePath) - assert.deepEqual(envParser.asEnvObject(['PORT', 'TZ', 'HOST']), { - PORT: '3333', - TZ: 'Asia/Calcutta', - }) - }) -}) diff --git a/test/invoke-command.spec.ts b/test/invoke-command.spec.ts deleted file mode 100644 index 1098d52..0000000 --- a/test/invoke-command.spec.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { test } from '@japa/runner' -import { join } from 'path' -import { Kernel } from '@adonisjs/ace' -import { Filesystem } from '@poppinss/dev-utils' -import { Application } from '@adonisjs/application' - -import Invoke from '../commands/Invoke' - -const fs = new Filesystem(join(__dirname, '__app')) - -test.group('Invoke', (group) => { - group.each.teardown(async () => { - await fs.cleanup() - }) - - test('execute instructions defined in package.json file', async ({ assert }) => { - await fs.add( - 'node_modules/@adonisjs/sample/package.json', - JSON.stringify({ - name: '@adonisjs/sample', - adonisjs: { - env: { - PORT: '3333', - }, - }, - }) - ) - - const app = new Application(fs.basePath, 'test', {}) - - const invoke = new Invoke(app, new Kernel(app).mockConsoleOutput()) - invoke.packages = ['@adonisjs/sample'] - await invoke.run() - - const envFile = await fs.fsExtra.readFile(join(fs.basePath, '.env'), 'utf-8') - const envExampleFile = await fs.fsExtra.readFile(join(fs.basePath, '.env.example'), 'utf-8') - - assert.equal(envFile.trim(), 'PORT=3333') - assert.equal(envExampleFile.trim(), 'PORT=3333') - }) -}) diff --git a/test/make-command.spec.ts b/test/make-command.spec.ts deleted file mode 100644 index f2abf7f..0000000 --- a/test/make-command.spec.ts +++ /dev/null @@ -1,118 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { test } from '@japa/runner' -import { join } from 'path' -import { Kernel } from '@adonisjs/ace' -import { readJSONSync } from 'fs-extra' -import { Filesystem } from '@poppinss/dev-utils' -import { Application } from '@adonisjs/application' - -import { toNewlineArray } from '../test-helpers' -import MakeCommand from '../commands/Make/Command' - -const fs = new Filesystem(join(__dirname, '__app')) -const templates = new Filesystem(join(__dirname, '..', 'templates')) - -test.group('Make Command', (group) => { - group.setup(() => { - process.env.ADONIS_ACE_CWD = fs.basePath - }) - - group.teardown(() => { - delete process.env.ADONIS_ACE_CWD - }) - - group.each.teardown(async () => { - await fs.cleanup() - }) - - test('make a command inside the default directory', async ({ assert }) => { - await fs.add('.adonisrc.json', JSON.stringify({})) - - const rcContents = readJSONSync(join(fs.basePath, '.adonisrc.json')) - const app = new Application(fs.basePath, 'test', rcContents) - - const command = new MakeCommand(app, new Kernel(app).mockConsoleOutput()) - command.name = 'greet' - await command.run() - - const GreetCommand = await fs.get('commands/Greet.ts') - const CommandTemplate = await templates.get('command.txt') - assert.deepEqual( - toNewlineArray(GreetCommand), - toNewlineArray( - CommandTemplate.replace('{{ filename }}', 'Greet').replace( - '{{#toCommandName}}{{ filename }}{{/toCommandName}}', - 'greet' - ) - ) - ) - }) - - test('make a command inside a custom directory', async ({ assert }) => { - await fs.add( - '.adonisrc.json', - JSON.stringify({ - directories: { - commands: './foo', - }, - }) - ) - - const rcContents = readJSONSync(join(fs.basePath, '.adonisrc.json')) - const app = new Application(fs.basePath, 'test', rcContents) - - const command = new MakeCommand(app, new Kernel(app).mockConsoleOutput()) - command.name = 'greet' - await command.run() - - const GreetCommand = await fs.get('foo/Greet.ts') - const CommandTemplate = await templates.get('command.txt') - assert.deepEqual( - toNewlineArray(GreetCommand), - toNewlineArray( - CommandTemplate.replace('{{ filename }}', 'Greet').replace( - '{{#toCommandName}}{{ filename }}{{/toCommandName}}', - 'greet' - ) - ) - ) - }) - - test('convert camelcase command path to colon seperated name', async ({ assert }) => { - await fs.add( - '.adonisrc.json', - JSON.stringify({ - directories: { - commands: './foo', - }, - }) - ) - - const rcContents = readJSONSync(join(fs.basePath, '.adonisrc.json')) - const app = new Application(fs.basePath, 'test', rcContents) - - const command = new MakeCommand(app, new Kernel(app).mockConsoleOutput()) - command.name = 'RunInstructions' - await command.run() - - const GreetCommand = await fs.get('foo/RunInstructions.ts') - const CommandTemplate = await templates.get('command.txt') - assert.deepEqual( - toNewlineArray(GreetCommand), - toNewlineArray( - CommandTemplate.replace('{{ filename }}', 'RunInstructions').replace( - '{{#toCommandName}}{{ filename }}{{/toCommandName}}', - 'run:instructions' - ) - ) - ) - }) -}) diff --git a/test/make-controller.spec.ts b/test/make-controller.spec.ts deleted file mode 100644 index f6297ba..0000000 --- a/test/make-controller.spec.ts +++ /dev/null @@ -1,100 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { test } from '@japa/runner' -import { join } from 'path' -import { readJSONSync } from 'fs-extra' -import { Kernel } from '@adonisjs/ace' -import { Filesystem } from '@poppinss/dev-utils' -import { Application } from '@adonisjs/application' - -import { toNewlineArray } from '../test-helpers' -import MakeController from '../commands/Make/Controller' - -const fs = new Filesystem(join(__dirname, '__app')) -const templates = new Filesystem(join(__dirname, '..', 'templates')) - -test.group('Make Controller', (group) => { - group.setup(() => { - process.env.ADONIS_ACE_CWD = fs.basePath - }) - - group.teardown(() => { - delete process.env.ADONIS_ACE_CWD - }) - - group.each.teardown(async () => { - await fs.cleanup() - }) - - test('make a controller inside the default directory', async ({ assert }) => { - await fs.add('.adonisrc.json', JSON.stringify({})) - - const rcContents = readJSONSync(join(fs.basePath, '.adonisrc.json')) - const app = new Application(fs.basePath, 'test', rcContents) - - const controller = new MakeController(app, new Kernel(app).mockConsoleOutput()) - controller.name = 'user' - await controller.run() - - const UsersController = await fs.get('app/Controllers/Http/UsersController.ts') - const ControllerTemplate = await templates.get('controller.txt') - assert.deepEqual( - toNewlineArray(UsersController), - toNewlineArray(ControllerTemplate.replace('{{ filename }}', 'UsersController')) - ) - }) - - test('make a resourceful controller inside the default directory', async ({ assert }) => { - await fs.add('.adonisrc.json', JSON.stringify({})) - - const rcContents = readJSONSync(join(fs.basePath, '.adonisrc.json')) - const app = new Application(fs.basePath, 'test', rcContents) - - const controller = new MakeController(app, new Kernel(app).mockConsoleOutput()) - controller.name = 'user' - controller.resource = true - await controller.run() - - const UsersController = await fs.get('app/Controllers/Http/UsersController.ts') - const ResourceTemplate = await templates.get('resource-controller.txt') - assert.deepEqual( - toNewlineArray(UsersController), - toNewlineArray(ResourceTemplate.replace('{{ filename }}', 'UsersController')) - ) - }) - - test('make a controller inside a custom directory', async ({ assert }) => { - await fs.add( - '.adonisrc.json', - JSON.stringify({ - namespaces: { - httpControllers: 'App/Controllers', - }, - autoloads: { - App: './app', - }, - }) - ) - - const rcContents = readJSONSync(join(fs.basePath, '.adonisrc.json')) - const app = new Application(fs.basePath, 'test', rcContents) - - const controller = new MakeController(app, new Kernel(app).mockConsoleOutput()) - controller.name = 'user' - await controller.run() - - const UsersController = await fs.get('app/Controllers/UsersController.ts') - const ControllerTemplate = await templates.get('controller.txt') - assert.deepEqual( - toNewlineArray(UsersController), - toNewlineArray(ControllerTemplate.replace('{{ filename }}', 'UsersController')) - ) - }) -}) diff --git a/test/make-exception.spec.ts b/test/make-exception.spec.ts deleted file mode 100644 index eb96bd4..0000000 --- a/test/make-exception.spec.ts +++ /dev/null @@ -1,107 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { test } from '@japa/runner' -import { join } from 'path' -import { readJSONSync } from 'fs-extra' -import { Kernel } from '@adonisjs/ace' -import { Filesystem } from '@poppinss/dev-utils' -import { Application } from '@adonisjs/application' - -import { toNewlineArray } from '../test-helpers' -import MakeException from '../commands/Make/Exception' - -const fs = new Filesystem(join(__dirname, '__app')) -const templates = new Filesystem(join(__dirname, '..', 'templates')) - -test.group('Make Exception', (group) => { - group.setup(() => { - process.env.ADONIS_ACE_CWD = fs.basePath - }) - - group.teardown(() => { - delete process.env.ADONIS_ACE_CWD - }) - - group.each.teardown(async () => { - await fs.cleanup() - }) - - test('make an exception class inside the default directory', async ({ assert }) => { - await fs.add('.adonisrc.json', JSON.stringify({})) - - const rcContents = readJSONSync(join(fs.basePath, '.adonisrc.json')) - const app = new Application(fs.basePath, 'test', rcContents) - - const exception = new MakeException(app, new Kernel(app).mockConsoleOutput()) - exception.name = 'user' - await exception.run() - - const UserException = await fs.get('app/Exceptions/UserException.ts') - const ExceptionTemplate = await templates.get('exception.txt') - assert.deepEqual( - toNewlineArray(UserException), - toNewlineArray( - ExceptionTemplate.replace(new RegExp('\\{{ filename }}', 'g'), 'UserException') - ) - ) - }) - - test('make a self-handled exception class inside the default directory', async ({ assert }) => { - await fs.add('.adonisrc.json', JSON.stringify({})) - - const rcContents = readJSONSync(join(fs.basePath, '.adonisrc.json')) - const app = new Application(fs.basePath, 'test', rcContents) - - const exception = new MakeException(app, new Kernel(app).mockConsoleOutput()) - exception.name = 'user' - exception.selfHandle = true - await exception.run() - - const UserException = await fs.get('app/Exceptions/UserException.ts') - const ExceptionTemplate = await templates.get('self-handle-exception.txt') - assert.deepEqual( - toNewlineArray(UserException), - toNewlineArray( - ExceptionTemplate.replace(new RegExp('\\{{ filename }}', 'g'), 'UserException') - ) - ) - }) - - test('make an exception class inside a custom directory', async ({ assert }) => { - await fs.add( - '.adonisrc.json', - JSON.stringify({ - namespaces: { - exceptions: 'App/Exceptions/Custom', - }, - aliases: { - App: './app', - }, - }) - ) - - const rcContents = readJSONSync(join(fs.basePath, '.adonisrc.json')) - const app = new Application(fs.basePath, 'test', rcContents) - - const exception = new MakeException(app, new Kernel(app).mockConsoleOutput()) - exception.name = 'user' - exception.selfHandle = true - await exception.run() - - const UserException = await fs.get('app/Exceptions/Custom/UserException.ts') - const ExceptionTemplate = await templates.get('self-handle-exception.txt') - assert.deepEqual( - toNewlineArray(UserException), - toNewlineArray( - ExceptionTemplate.replace(new RegExp('\\{{ filename }}', 'g'), 'UserException') - ) - ) - }) -}) diff --git a/test/make-listener.spec.ts b/test/make-listener.spec.ts deleted file mode 100644 index 97e33c7..0000000 --- a/test/make-listener.spec.ts +++ /dev/null @@ -1,81 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { test } from '@japa/runner' -import { join } from 'path' -import { readJSONSync } from 'fs-extra' -import { Kernel } from '@adonisjs/ace' -import { Filesystem } from '@poppinss/dev-utils' -import { Application } from '@adonisjs/application' - -import { toNewlineArray } from '../test-helpers' -import MakeListener from '../commands/Make/Listener' - -const fs = new Filesystem(join(__dirname, '__app')) -const templates = new Filesystem(join(__dirname, '..', 'templates')) - -test.group('Make Listener', (group) => { - group.setup(() => { - process.env.ADONIS_ACE_CWD = fs.basePath - }) - - group.teardown(() => { - delete process.env.ADONIS_ACE_CWD - }) - - group.each.teardown(async () => { - await fs.cleanup() - }) - - test('make a listener inside the default directory', async ({ assert }) => { - await fs.add('.adonisrc.json', JSON.stringify({})) - - const rcContents = readJSONSync(join(fs.basePath, '.adonisrc.json')) - const app = new Application(fs.basePath, 'test', rcContents) - - const listener = new MakeListener(app, new Kernel(app).mockConsoleOutput()) - listener.name = 'user' - await listener.run() - - const UserListener = await fs.get('app/Listeners/User.ts') - const ListenerTemplate = await templates.get('event-listener.txt') - assert.deepEqual( - toNewlineArray(UserListener), - toNewlineArray(ListenerTemplate.replace('{{ filename }}', 'User')) - ) - }) - - test('make a listener inside a custom directory', async ({ assert }) => { - await fs.add( - '.adonisrc.json', - JSON.stringify({ - namespaces: { - eventListeners: 'App/Events/Listeners', - }, - aliases: { - App: './app', - }, - }) - ) - - const rcContents = readJSONSync(join(fs.basePath, '.adonisrc.json')) - const app = new Application(fs.basePath, 'test', rcContents) - - const listener = new MakeListener(app, new Kernel(app).mockConsoleOutput()) - listener.name = 'user' - await listener.run() - - const UserListener = await fs.get('app/Events/Listeners/User.ts') - const ListenerTemplate = await templates.get('event-listener.txt') - assert.deepEqual( - toNewlineArray(UserListener), - toNewlineArray(ListenerTemplate.replace('{{ filename }}', 'User')) - ) - }) -}) diff --git a/test/make-middleware.spec.ts b/test/make-middleware.spec.ts deleted file mode 100644 index 4657e84..0000000 --- a/test/make-middleware.spec.ts +++ /dev/null @@ -1,81 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { test } from '@japa/runner' -import { join } from 'path' -import { readJSONSync } from 'fs-extra' -import { Kernel } from '@adonisjs/ace' -import { Filesystem } from '@poppinss/dev-utils' -import { Application } from '@adonisjs/application' - -import { toNewlineArray } from '../test-helpers' -import MakeMiddleware from '../commands/Make/Middleware' - -const fs = new Filesystem(join(__dirname, '__app')) -const templates = new Filesystem(join(__dirname, '..', 'templates')) - -test.group('Make Middleware', (group) => { - group.setup(() => { - process.env.ADONIS_ACE_CWD = fs.basePath - }) - - group.teardown(() => { - delete process.env.ADONIS_ACE_CWD - }) - - group.each.teardown(async () => { - await fs.cleanup() - }) - - test('make a middleware inside the default directory', async ({ assert }) => { - await fs.add('.adonisrc.json', JSON.stringify({})) - - const rcContents = readJSONSync(join(fs.basePath, '.adonisrc.json')) - const app = new Application(fs.basePath, 'test', rcContents) - - const middleware = new MakeMiddleware(app, new Kernel(app).mockConsoleOutput()) - middleware.name = 'spoof_accept' - await middleware.run() - - const SpoofMiddleware = await fs.get('app/Middleware/SpoofAccept.ts') - const MiddlewareTemplate = await templates.get('middleware.txt') - assert.deepEqual( - toNewlineArray(SpoofMiddleware), - toNewlineArray(MiddlewareTemplate.replace('{{ filename }}', 'SpoofAccept')) - ) - }) - - test('make a middleware inside a custom directory', async ({ assert }) => { - await fs.add( - '.adonisrc.json', - JSON.stringify({ - namespaces: { - middleware: 'App/Module/Testing/Middleware', - }, - autoloads: { - App: './app', - }, - }) - ) - - const rcContents = readJSONSync(join(fs.basePath, '.adonisrc.json')) - const app = new Application(fs.basePath, 'test', rcContents) - - const middleware = new MakeMiddleware(app, new Kernel(app).mockConsoleOutput()) - middleware.name = 'spoof_accept' - await middleware.run() - - const SpoofMiddleware = await fs.get('app/Module/Testing/Middleware/SpoofAccept.ts') - const MiddlewareTemplate = await templates.get('middleware.txt') - assert.deepEqual( - toNewlineArray(SpoofMiddleware), - toNewlineArray(MiddlewareTemplate.replace('{{ filename }}', 'SpoofAccept')) - ) - }) -}) diff --git a/test/make-preloaded-file.spec.ts b/test/make-preloaded-file.spec.ts deleted file mode 100644 index 63bde1d..0000000 --- a/test/make-preloaded-file.spec.ts +++ /dev/null @@ -1,174 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { test } from '@japa/runner' -import { join } from 'path' -import { readJSONSync } from 'fs-extra' -import { Kernel } from '@adonisjs/ace' -import { Filesystem } from '@poppinss/dev-utils' -import { Application } from '@adonisjs/application' - -import { toNewlineArray } from '../test-helpers' -import PreloadFile from '../commands/Make/PreloadFile' - -const fs = new Filesystem(join(__dirname, '__app')) -const templates = new Filesystem(join(__dirname, '..', 'templates')) - -test.group('Make Preloaded File', (group) => { - group.setup(() => { - process.env.ADONIS_ACE_CWD = fs.basePath - }) - - group.teardown(() => { - delete process.env.ADONIS_ACE_CWD - }) - - group.each.teardown(async () => { - await fs.cleanup() - }) - - test('make a preload file inside the start directory', async ({ assert }) => { - await fs.add('.adonisrc.json', JSON.stringify({})) - - const rcContents = readJSONSync(join(fs.basePath, '.adonisrc.json')) - const app = new Application(fs.basePath, 'test', rcContents) - - const preloadFile = new PreloadFile(app, new Kernel(app).mockConsoleOutput()) - preloadFile.name = 'viewGlobals' - preloadFile.environment = ['console', 'web'] - await preloadFile.run() - - const viewGlobals = await fs.get('start/viewGlobals.ts') - const preloadTemplate = await templates.get('preload-file.txt') - assert.deepEqual(toNewlineArray(viewGlobals), toNewlineArray(preloadTemplate)) - - const rcRawContents = await fs.get('.adonisrc.json') - assert.deepEqual(JSON.parse(rcRawContents), { - preloads: [ - { - file: './start/viewGlobals', - environment: ['console', 'web'], - }, - ], - }) - }) - - test('make a preload file inside custom directory', async ({ assert }) => { - await fs.add( - '.adonisrc.json', - JSON.stringify({ - directories: { - start: 'foo', - }, - }) - ) - - const rcContents = readJSONSync(join(fs.basePath, '.adonisrc.json')) - const app = new Application(fs.basePath, 'test', rcContents) - - const preloadFile = new PreloadFile(app, new Kernel(app).mockConsoleOutput()) - preloadFile.name = 'viewGlobals' - preloadFile.environment = ['console', 'web'] - await preloadFile.run() - - const viewGlobals = await fs.get('foo/viewGlobals.ts') - const preloadTemplate = await templates.get('preload-file.txt') - assert.deepEqual(toNewlineArray(viewGlobals), toNewlineArray(preloadTemplate)) - - const rcRawContents = await fs.get('.adonisrc.json') - assert.deepEqual(JSON.parse(rcRawContents), { - directories: { start: 'foo' }, - preloads: [ - { - file: './foo/viewGlobals', - environment: ['console', 'web'], - }, - ], - }) - }) - - test('set preload file environment as repl', async ({ assert }) => { - await fs.add('.adonisrc.json', JSON.stringify({})) - - const rcContents = readJSONSync(join(fs.basePath, '.adonisrc.json')) - const app = new Application(fs.basePath, 'test', rcContents) - - const preloadFile = new PreloadFile(app, new Kernel(app).mockConsoleOutput()) - preloadFile.name = 'repl' - preloadFile.environment = ['repl'] - await preloadFile.run() - - const replFile = await fs.get('start/repl.ts') - const preloadTemplate = await templates.get('preload-file.txt') - assert.deepEqual(toNewlineArray(replFile), toNewlineArray(preloadTemplate)) - - const rcRawContents = await fs.get('.adonisrc.json') - assert.deepEqual(JSON.parse(rcRawContents), { - preloads: [ - { - file: './start/repl', - environment: ['repl'], - }, - ], - }) - }) - - test('prompt for environment when not explicitly defined', async ({ assert }) => { - await fs.add('.adonisrc.json', JSON.stringify({})) - - const rcContents = readJSONSync(join(fs.basePath, '.adonisrc.json')) - const app = new Application(fs.basePath, 'test', rcContents) - - const preloadFile = new PreloadFile(app, new Kernel(app).mockConsoleOutput()) - preloadFile.prompt.on('prompt', (question) => { - question.select(2) - }) - - preloadFile.name = 'repl' - await preloadFile.exec() - - const replFile = await fs.get('start/repl.ts') - const preloadTemplate = await templates.get('preload-file.txt') - assert.deepEqual(toNewlineArray(replFile), toNewlineArray(preloadTemplate)) - - const rcRawContents = await fs.get('.adonisrc.json') - assert.deepEqual(JSON.parse(rcRawContents), { - preloads: [ - { - file: './start/repl', - environment: ['repl'], - }, - ], - }) - }) - - test('do not set environment when all is selected', async ({ assert }) => { - await fs.add('.adonisrc.json', JSON.stringify({})) - - const rcContents = readJSONSync(join(fs.basePath, '.adonisrc.json')) - const app = new Application(fs.basePath, 'test', rcContents) - - const preloadFile = new PreloadFile(app, new Kernel(app).mockConsoleOutput()) - preloadFile.prompt.on('prompt', (question) => { - question.select(0) - }) - - preloadFile.name = 'events' - await preloadFile.exec() - - const replFile = await fs.get('start/events.ts') - const preloadTemplate = await templates.get('preload-file.txt') - assert.deepEqual(toNewlineArray(replFile), toNewlineArray(preloadTemplate)) - - const rcRawContents = await fs.get('.adonisrc.json') - assert.deepEqual(JSON.parse(rcRawContents), { - preloads: ['./start/events'], - }) - }) -}) diff --git a/test/make-provider.spec.ts b/test/make-provider.spec.ts deleted file mode 100644 index d979209..0000000 --- a/test/make-provider.spec.ts +++ /dev/null @@ -1,138 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { test } from '@japa/runner' -import { join } from 'path' -import { readJSONSync } from 'fs-extra' -import { Kernel } from '@adonisjs/ace' -import { Filesystem } from '@poppinss/dev-utils' -import { Application } from '@adonisjs/application' - -import { toNewlineArray } from '../test-helpers' -import MakeProvider from '../commands/Make/Provider' - -const fs = new Filesystem(join(__dirname, '__app')) -const templates = new Filesystem(join(__dirname, '..', 'templates')) - -test.group('Make Provider', (group) => { - group.setup(() => { - process.env.ADONIS_ACE_CWD = fs.basePath - }) - - group.teardown(() => { - delete process.env.ADONIS_ACE_CWD - }) - - group.each.teardown(async () => { - await fs.cleanup() - }) - - test('make a provider inside the default directory', async ({ assert }) => { - await fs.add('.adonisrc.json', JSON.stringify({})) - - const rcContents = readJSONSync(join(fs.basePath, '.adonisrc.json')) - const app = new Application(fs.basePath, 'test', rcContents) - - const provider = new MakeProvider(app, new Kernel(app).mockConsoleOutput()) - provider.name = 'app' - await provider.run() - - const AppProvider = await fs.get('providers/AppProvider.ts') - const ProviderTemplate = await templates.get('provider.txt') - assert.deepEqual( - toNewlineArray(AppProvider), - toNewlineArray(ProviderTemplate.replace('{{ filename }}', 'AppProvider')) - ) - - const rcRawContents = await fs.get('.adonisrc.json') - assert.deepEqual(JSON.parse(rcRawContents), { - providers: ['./providers/AppProvider'], - }) - }) - - test('make a provider inside a custom directory', async ({ assert }) => { - await fs.add( - '.adonisrc.json', - JSON.stringify({ - directories: { - providers: 'foo', - }, - }) - ) - - const rcContents = readJSONSync(join(fs.basePath, '.adonisrc.json')) - const app = new Application(fs.basePath, 'test', rcContents) - - const provider = new MakeProvider(app, new Kernel(app).mockConsoleOutput()) - provider.name = 'app' - await provider.run() - - const AppProvider = await fs.get('foo/AppProvider.ts') - const ProviderTemplate = await templates.get('provider.txt') - assert.deepEqual( - toNewlineArray(AppProvider), - toNewlineArray(ProviderTemplate.replace('{{ filename }}', 'AppProvider')) - ) - - const rcRawContents = await fs.get('.adonisrc.json') - assert.deepEqual(JSON.parse(rcRawContents), { - directories: { - providers: 'foo', - }, - providers: ['./foo/AppProvider'], - }) - }) - - test('setup correct path when nested provider is created', async ({ assert }) => { - await fs.add('.adonisrc.json', JSON.stringify({})) - - const rcContents = readJSONSync(join(fs.basePath, '.adonisrc.json')) - const app = new Application(fs.basePath, 'test', rcContents) - - const provider = new MakeProvider(app, new Kernel(app).mockConsoleOutput()) - provider.name = 'auth/app' - await provider.run() - - const AppProvider = await fs.get('providers/auth/AppProvider.ts') - const ProviderTemplate = await templates.get('provider.txt') - assert.deepEqual( - toNewlineArray(AppProvider), - toNewlineArray(ProviderTemplate.replace('{{ filename }}', 'AppProvider')) - ) - - const rcRawContents = await fs.get('.adonisrc.json') - assert.deepEqual(JSON.parse(rcRawContents), { - providers: ['./providers/auth/AppProvider'], - }) - }) - - test('make ace provider', async ({ assert }) => { - await fs.add('.adonisrc.json', JSON.stringify({})) - - const rcContents = readJSONSync(join(fs.basePath, '.adonisrc.json')) - const app = new Application(fs.basePath, 'test', rcContents) - - const provider = new MakeProvider(app, new Kernel(app).mockConsoleOutput()) - provider.name = 'app' - provider.ace = true - await provider.run() - - const AppProvider = await fs.get('providers/AppProvider.ts') - const ProviderTemplate = await templates.get('provider.txt') - assert.deepEqual( - toNewlineArray(AppProvider), - toNewlineArray(ProviderTemplate.replace('{{ filename }}', 'AppProvider')) - ) - - const rcRawContents = await fs.get('.adonisrc.json') - assert.deepEqual(JSON.parse(rcRawContents), { - aceProviders: ['./providers/AppProvider'], - }) - }) -}) diff --git a/test/make-suite.spec.ts b/test/make-suite.spec.ts deleted file mode 100644 index ce28f01..0000000 --- a/test/make-suite.spec.ts +++ /dev/null @@ -1,130 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) AdonisJS - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { test } from '@japa/runner' -import { join } from 'path' -import { Kernel } from '@adonisjs/ace' -import { Filesystem } from '@poppinss/dev-utils' -import { Application } from '@adonisjs/application' -import { files } from '@adonisjs/sink' -import CreateSuite from '../commands/Make/Suite' - -const fs = new Filesystem(join(__dirname, '__app')) - -test.group('Make Suite', (group) => { - group.each.teardown(async () => { - await fs.cleanup() - }) - - test('Should add suite to RcFile and create sample test', async ({ assert }) => { - await fs.ensureRoot() - const app = new Application(fs.basePath, 'test', {}) - const suiteName = 'my-super-suite' - const createSuite = new CreateSuite(app, new Kernel(app).mockConsoleOutput()) - - createSuite.suite = suiteName - await createSuite.run() - - const sampleTestExist = fs.fsExtra.pathExistsSync( - join(fs.basePath, `tests/${suiteName}/test.spec.ts`) - ) - assert.isTrue(sampleTestExist) - - const rcFile = new files.AdonisRcFile(fs.basePath) - assert.deepEqual(rcFile.get('tests.suites'), [ - { - name: suiteName, - files: [`tests/${suiteName}/**/*.spec(.ts|.js)`], - timeout: 60000, - }, - ]) - }) - - test("Shouldn't add suite to RcFile if it already exists", async ({ assert }) => { - await fs.ensureRoot() - const app = new Application(fs.basePath, 'test', {}) - const suiteName = 'my-super-suite' - const createSuite = new CreateSuite(app, new Kernel(app).mockConsoleOutput()) - - createSuite.suite = suiteName - await createSuite.run() - await createSuite.run() - await createSuite.run() - - const rcFile = new files.AdonisRcFile(fs.basePath) - assert.deepEqual(rcFile.get('tests.suites'), [ - { - name: suiteName, - files: [`tests/${suiteName}/**/*.spec(.ts|.js)`], - timeout: 60000, - }, - ]) - }) - - test("Shouldn't add a sample file if specified", async ({ assert }) => { - await fs.ensureRoot() - const app = new Application(fs.basePath, 'test', {}) - - const suiteName = 'my-super-suite' - const createSuite = new CreateSuite(app, new Kernel(app).mockConsoleOutput()) - - createSuite.suite = suiteName - createSuite.withExampleTest = false - await createSuite.run() - - const sampleTestExist = fs.fsExtra.pathExistsSync( - join(fs.basePath, `tests/${suiteName}/test.spec.ts`) - ) - - assert.isFalse(sampleTestExist) - }) - - test('Custom location - {location}') - .with([ - { location: 'tests/unit/**.spec.ts', filePath: 'tests/unit/test.spec.ts' }, - { location: 'tests/a/**/*.spec.ts', filePath: 'tests/a/test.spec.ts' }, - { - location: 'tests/a/b/c/**.spec.ts', - filePath: 'tests/a/b/c/test.spec.ts', - }, - { - location: 'tests/my-tests', - globPattern: 'tests/my-tests/**/*.spec(.ts|.js)', - filePath: 'tests/my-tests/test.spec.ts', - }, - { - location: '', - globPattern: 'tests/my-super-suite/**/*.spec(.ts|.js)', - filePath: 'tests/my-super-suite/test.spec.ts', - }, - ]) - .run(async ({ assert }, { location, filePath, globPattern }) => { - await fs.ensureRoot() - const app = new Application(fs.basePath, 'test', {}) - const suiteName = 'my-super-suite' - const createSuite = new CreateSuite(app, new Kernel(app).mockConsoleOutput()) - - createSuite.suite = suiteName - createSuite.location = location - - await createSuite.run() - - const sampleTestExist = fs.fsExtra.pathExistsSync(join(fs.basePath, filePath)) - assert.isTrue(sampleTestExist) - - const rcFile = new files.AdonisRcFile(fs.basePath) - assert.deepEqual(rcFile.get('tests.suites'), [ - { - name: suiteName, - files: [globPattern || location], - timeout: 60000, - }, - ]) - }) -}) diff --git a/test/make-test.spec.ts b/test/make-test.spec.ts deleted file mode 100644 index ac02116..0000000 --- a/test/make-test.spec.ts +++ /dev/null @@ -1,109 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { test } from '@japa/runner' -import { join } from 'path' -import { readJSONSync } from 'fs-extra' -import { Kernel } from '@adonisjs/ace' -import { Filesystem } from '@poppinss/dev-utils' -import { Application } from '@adonisjs/application' - -import MakeTest from '../commands/Make/Test' -import { toNewlineArray } from '../test-helpers' - -const fs = new Filesystem(join(__dirname, '__app')) -const templates = new Filesystem(join(__dirname, '..', 'templates')) - -test.group('Make Test', (group) => { - group.setup(() => { - process.env.ADONIS_ACE_CWD = fs.basePath - }) - - group.teardown(() => { - delete process.env.ADONIS_ACE_CWD - }) - - group.each.teardown(async () => { - await fs.cleanup() - }) - - test('make a test inside the suite directory', async ({ assert }) => { - await fs.add( - '.adonisrc.json', - JSON.stringify({ - tests: { - suites: [{ name: 'functional', files: ['tests/functional/**/*.spec.ts'] }], - }, - }) - ) - - const rcContents = readJSONSync(join(fs.basePath, '.adonisrc.json')) - const app = new Application(fs.basePath, 'test', rcContents) - - const makeTest = new MakeTest(app, new Kernel(app).mockConsoleOutput()) - makeTest.suite = 'functional' - makeTest.name = 'Users' - await makeTest.run() - - const testFile = await fs.get('tests/functional/user.spec.ts') - const testTemplate = await templates.get('test.txt') - assert.deepEqual( - toNewlineArray(testFile), - toNewlineArray(testTemplate.replace('{{ name }}', 'Users')) - ) - }) - - test('make a test inside nested directory', async ({ assert }) => { - await fs.add( - '.adonisrc.json', - JSON.stringify({ - tests: { - suites: [{ name: 'functional', files: ['tests/functional/**/*.spec.ts'] }], - }, - }) - ) - - const rcContents = readJSONSync(join(fs.basePath, '.adonisrc.json')) - const app = new Application(fs.basePath, 'test', rcContents) - - const makeTest = new MakeTest(app, new Kernel(app).mockConsoleOutput()) - makeTest.suite = 'functional' - makeTest.name = 'users/index' - await makeTest.run() - - const testFile = await fs.get('tests/functional/users/index.spec.ts') - const testTemplate = await templates.get('test.txt') - assert.deepEqual( - toNewlineArray(testFile), - toNewlineArray(testTemplate.replace('{{ name }}', 'Users index')) - ) - }) - - test('return error when suite is not registered', async ({ assert }) => { - await fs.add('.adonisrc.json', JSON.stringify({})) - - const rcContents = readJSONSync(join(fs.basePath, '.adonisrc.json')) - const app = new Application(fs.basePath, 'test', rcContents) - - const makeTest = new MakeTest(app, new Kernel(app).mockConsoleOutput()) - makeTest.suite = 'functional' - makeTest.name = 'Users/index' - await makeTest.run() - - assert.deepEqual(makeTest.ui.testingRenderer.logs, [ - { - stream: 'stderr', - message: - '[ red(error) ] Invalid suite "functional". Make sure the suite is registered inside the .adonisrc.json file', - }, - ]) - - assert.isFalse(await fs.exists('tests/functional/users/index.spec.ts')) - }) -}) diff --git a/test/make-validator.spec.ts b/test/make-validator.spec.ts deleted file mode 100644 index c431de1..0000000 --- a/test/make-validator.spec.ts +++ /dev/null @@ -1,85 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { test } from '@japa/runner' -import { join } from 'path' -import { readJSONSync } from 'fs-extra' -import { Kernel } from '@adonisjs/ace' -import { Filesystem } from '@poppinss/dev-utils' -import { Application } from '@adonisjs/application' - -import { toNewlineArray } from '../test-helpers' -import MakeValidator from '../commands/Make/Validator' - -const fs = new Filesystem(join(__dirname, '__app')) -const templates = new Filesystem(join(__dirname, '..', 'templates')) - -test.group('Make Validator', (group) => { - group.setup(() => { - process.env.ADONIS_ACE_CWD = fs.basePath - }) - - group.teardown(() => { - delete process.env.ADONIS_ACE_CWD - }) - - group.each.teardown(async () => { - await fs.cleanup() - }) - - test('make a model inside the default directory', async ({ assert }) => { - await fs.add('.adonisrc.json', JSON.stringify({})) - - const rcContents = readJSONSync(join(fs.basePath, '.adonisrc.json')) - const app = new Application(fs.basePath, 'test', rcContents) - - const validator = new MakeValidator(app, new Kernel(app).mockConsoleOutput()) - validator.name = 'user' - await validator.run() - - const UserValidator = await fs.get('app/Validators/UserValidator.ts') - const ValidatorTemplate = await templates.get('validator.txt') - assert.deepEqual( - toNewlineArray(UserValidator), - toNewlineArray( - ValidatorTemplate.replace(new RegExp('\\{{ filename }}', 'g'), 'UserValidator') - ) - ) - }) - - test('make a validator inside a custom directory', async ({ assert }) => { - await fs.add( - '.adonisrc.json', - JSON.stringify({ - namespaces: { - validators: 'App', - }, - autoloads: { - App: './app', - }, - }) - ) - - const rcContents = readJSONSync(join(fs.basePath, '.adonisrc.json')) - const app = new Application(fs.basePath, 'test', rcContents) - - const validator = new MakeValidator(app, new Kernel(app).mockConsoleOutput()) - validator.name = 'user' - await validator.run() - - const UserValidator = await fs.get('app/UserValidator.ts') - const ValidatorTemplate = await templates.get('validator.txt') - assert.deepEqual( - toNewlineArray(UserValidator), - toNewlineArray( - ValidatorTemplate.replace(new RegExp('\\{{ filename }}', 'g'), 'UserValidator') - ) - ) - }) -}) diff --git a/test/make-view.spec.ts b/test/make-view.spec.ts deleted file mode 100644 index 27f6f75..0000000 --- a/test/make-view.spec.ts +++ /dev/null @@ -1,82 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { test } from '@japa/runner' -import { join } from 'path' -import { readJSONSync } from 'fs-extra' -import { Kernel } from '@adonisjs/ace' -import { Filesystem } from '@poppinss/dev-utils' -import { Application } from '@adonisjs/application' - -import MakeView from '../commands/Make/View' - -const fs = new Filesystem(join(__dirname, '__app')) - -test.group('Make Command', (group) => { - group.setup(() => { - process.env.ADONIS_ACE_CWD = fs.basePath - }) - - group.teardown(() => { - delete process.env.ADONIS_ACE_CWD - }) - - group.each.teardown(async () => { - await fs.cleanup() - }) - - test('make an empty view inside the default directory', async ({ assert }) => { - await fs.add('.adonisrc.json', JSON.stringify({})) - - const rcContents = readJSONSync(join(fs.basePath, '.adonisrc.json')) - const app = new Application(fs.basePath, 'test', rcContents) - - const view = new MakeView(app, new Kernel(app).mockConsoleOutput()) - view.name = 'welcome' - await view.run() - - const welcomeView = await fs.get('resources/views/welcome.edge') - assert.deepEqual(welcomeView.trim(), '') - }) - - test('make a view inside a nested directory', async ({ assert }) => { - await fs.add('.adonisrc.json', JSON.stringify({})) - - const rcContents = readJSONSync(join(fs.basePath, '.adonisrc.json')) - const app = new Application(fs.basePath, 'test', rcContents) - - const view = new MakeView(app, new Kernel(app).mockConsoleOutput()) - view.name = 'users/welcome' - await view.run() - - const welcomeView = await fs.get('resources/views/users/welcome.edge') - assert.deepEqual(welcomeView.trim(), '') - }) - - test('make an empty view inside custom directory', async ({ assert }) => { - await fs.add( - '.adonisrc.json', - JSON.stringify({ - directories: { - views: 'public/views', - }, - }) - ) - - const rcContents = readJSONSync(join(fs.basePath, '.adonisrc.json')) - const app = new Application(fs.basePath, 'test', rcContents) - - const view = new MakeView(app, new Kernel(app).mockConsoleOutput()) - view.name = 'welcome' - await view.run() - - const welcomeView = await fs.get('public/views/welcome.edge') - assert.deepEqual(welcomeView.trim(), '') - }) -}) diff --git a/test/rc-file.spec.ts b/test/rc-file.spec.ts deleted file mode 100644 index dd86ae4..0000000 --- a/test/rc-file.spec.ts +++ /dev/null @@ -1,242 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) Harminder Virk - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { test } from '@japa/runner' -import { join } from 'path' -import { Filesystem } from '@poppinss/dev-utils' - -import { RcFile } from '../src/RcFile' - -const fs = new Filesystem(join(__dirname, '__app')) - -test.group('RcFile', (group) => { - group.each.teardown(async () => { - await fs.cleanup() - }) - - test('get an array of meta file patterns from the rcfile', async ({ assert }) => { - await fs.add( - '.adonisrc.json', - JSON.stringify({ - metaFiles: ['.env', 'public/**/*.(css|js)'], - }) - ) - - const rcFile = new RcFile(fs.basePath) - assert.deepEqual(rcFile.getMetaFilesGlob(), ['.env', 'public/**/*.(css|js)', 'ace']) - }) - - test('get info about a meta file', async ({ assert }) => { - await fs.add( - '.adonisrc.json', - JSON.stringify({ - metaFiles: [{ pattern: '.env', reloadServer: false }, 'public/**/*.(css|js)'], - }) - ) - - const rcFile = new RcFile(fs.basePath) - assert.deepEqual(rcFile.getMetaData('.env'), { - metaFile: true, - reload: false, - testFile: false, - rcFile: false, - }) - - assert.deepEqual(rcFile.getMetaData('public/foo.js'), { - metaFile: true, - reload: true, - testFile: false, - rcFile: false, - }) - - assert.deepEqual(rcFile.getMetaData('schema/app.js'), { - metaFile: false, - reload: false, - testFile: false, - rcFile: false, - }) - }) - - test('match relative paths against meta files', async ({ assert }) => { - await fs.add( - '.adonisrc.json', - JSON.stringify({ - metaFiles: [{ pattern: '.env', reloadServer: false }, 'public/**/*.(css|js)'], - }) - ) - - const rcFile = new RcFile(fs.basePath) - assert.isTrue(rcFile.isMetaFile('.env')) - assert.isTrue(rcFile.isMetaFile('public/style.css')) - assert.isTrue(rcFile.isMetaFile('public/script.js')) - assert.isFalse(rcFile.isMetaFile('public/script.sass')) - }) - - test('match relative paths against reloadServer meta files', async ({ assert }) => { - await fs.add( - '.adonisrc.json', - JSON.stringify({ - metaFiles: [{ pattern: '.env', reloadServer: false }, 'public/**/*.(css|js)'], - }) - ) - - const rcFile = new RcFile(fs.basePath) - assert.isTrue(rcFile.isRestartServerFile('public/style.css')) - assert.isTrue(rcFile.isRestartServerFile('public/script.js')) - assert.isFalse(rcFile.isRestartServerFile('.env')) - }) - - test('filter .adonisrc.json file from files globs array', async ({ assert }) => { - await fs.add( - '.adonisrc.json', - JSON.stringify({ - metaFiles: [ - '.adonisrc.json', - { pattern: '.env', reloadServer: false }, - 'public/**/*.(css|js)', - ], - }) - ) - - const rcFile = new RcFile(fs.basePath) - assert.deepEqual(rcFile.getMetaFilesGlob(), ['.env', 'public/**/*.(css|js)', 'ace']) - }) - - test('filter ace file from files globs array', async ({ assert }) => { - await fs.add( - '.adonisrc.json', - JSON.stringify({ - metaFiles: ['ace', { pattern: '.env', reloadServer: false }, 'public/**/*.(css|js)'], - }) - ) - - const rcFile = new RcFile(fs.basePath) - assert.deepEqual(rcFile.getMetaFilesGlob(), ['.env', 'public/**/*.(css|js)', 'ace']) - }) - - test('get metadata for files', async ({ assert }) => { - await fs.add( - '.adonisrc.json', - JSON.stringify({ - metaFiles: [ - '.adonisrc.json', - { pattern: '.env', reloadServer: false }, - 'public/**/*.(css|js)', - ], - }) - ) - - const rcFile = new RcFile(fs.basePath) - assert.deepEqual(rcFile.getMetaData('.adonisrc.json'), { - reload: true, - rcFile: true, - testFile: false, - metaFile: true, - }) - - assert.deepEqual(rcFile.getMetaData('public/style.css'), { - reload: true, - rcFile: false, - testFile: false, - metaFile: true, - }) - - assert.deepEqual(rcFile.getMetaData('.env'), { - reload: false, - rcFile: false, - testFile: false, - metaFile: true, - }) - - assert.deepEqual(rcFile.getMetaData('foo/bar.js'), { - reload: false, - rcFile: false, - testFile: false, - metaFile: false, - }) - }) - - test('match sub paths to the defined command path', async ({ assert }) => { - await fs.add( - '.adonisrc.json', - JSON.stringify({ - metaFiles: [], - commands: ['./commands'], - }) - ) - - const rcFile = new RcFile(fs.basePath) - assert.isTrue(rcFile.isCommandsPath('commands/foo.ts')) - }) - - test('match actual path to the defined command path', async ({ assert }) => { - await fs.add( - '.adonisrc.json', - JSON.stringify({ - metaFiles: [], - commands: ['./commands'], - }) - ) - - const rcFile = new RcFile(fs.basePath) - assert.isTrue(rcFile.isCommandsPath('commands.ts')) - }) - - test('do not work when commands refer to path outside the project root', async ({ assert }) => { - await fs.add( - '.adonisrc.json', - JSON.stringify({ - metaFiles: [], - commands: ['../commands'], - }) - ) - - const rcFile = new RcFile(fs.basePath) - assert.isFalse(rcFile.isCommandsPath('commands.ts')) - assert.isFalse(rcFile.isCommandsPath('commands/foo.ts')) - }) - - test('do not work when commands refer to a package', async ({ assert }) => { - await fs.add( - '.adonisrc.json', - JSON.stringify({ - metaFiles: [], - commands: ['@adonisjs/foo'], - }) - ) - - const rcFile = new RcFile(fs.basePath) - assert.isFalse(rcFile.isCommandsPath('@adonisjs/foo.ts')) - assert.isFalse(rcFile.isCommandsPath('@adonisjs/foo/foo.ts')) - }) - - test('read file from the disk by-passing the cache', async ({ assert }) => { - await fs.add( - '.adonisrc.json', - JSON.stringify({ - metaFiles: ['.env', 'public/**/*.(css|js)'], - }) - ) - - const rcFile = new RcFile(fs.basePath) - assert.deepEqual(rcFile.getDiskContents(), { - metaFiles: ['.env', 'public/**/*.(css|js)'], - }) - - await fs.add( - '.adonisrc.json', - JSON.stringify({ - metaFiles: ['.env'], - }) - ) - assert.deepEqual(rcFile.getDiskContents(), { - metaFiles: ['.env'], - }) - }) -}) diff --git a/tests/run.spec.ts b/tests/run.spec.ts new file mode 100644 index 0000000..fd24cf6 --- /dev/null +++ b/tests/run.spec.ts @@ -0,0 +1,124 @@ +/* + * @adonisjs/assembler + * + * (c) AdonisJS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { pEvent } from 'p-event' +import { test } from '@japa/runner' +import { run } from '../src/run.js' + +test.group('Child process', () => { + test('run typescript file as a child process', async ({ fs, assert }) => { + await fs.create( + 'foo.ts', + ` + process.send('ready') + ` + ) + + const childProcess = run(fs.basePath, { script: 'foo.ts', scriptArgs: [], nodeArgs: [] }) + const payload = await pEvent(childProcess, 'message', { rejectionEvents: ['error'] }) + + await pEvent(childProcess, 'close', { rejectionEvents: ['error'] }) + + assert.equal(childProcess.exitCode, 0) + assert.equal(payload, 'ready') + }) + + test('pass arguments to the script', async ({ fs, assert }) => { + await fs.create( + 'foo.ts', + ` + process.send({ args: process.argv.splice(2) }) + ` + ) + + const childProcess = run(fs.basePath, { + script: 'foo.ts', + scriptArgs: ['--watch', '--foo=bar'], + nodeArgs: [], + }) + const payload = await pEvent(childProcess, 'message', { rejectionEvents: ['error'] }) + await pEvent(childProcess, 'close', { rejectionEvents: ['error'] }) + + assert.equal(childProcess.exitCode, 0) + assert.deepEqual(payload, { args: ['--watch', '--foo=bar'] }) + }) + + test('pass arguments to node', async ({ assert, fs }) => { + await fs.create( + 'foo.ts', + ` + process.send({ args: process.execArgv }) + ` + ) + + const childProcess = run(fs.basePath, { + script: 'foo.ts', + scriptArgs: ['--watch', '--foo=bar'], + nodeArgs: ['--throw-deprecation'], + }) + + const payload = await pEvent(childProcess, 'message', { rejectionEvents: ['error'] }) + await pEvent(childProcess, 'close', { rejectionEvents: ['error'] }) + + assert.equal(childProcess.exitCode, 0) + assert.deepEqual(payload, { + args: [ + '--loader=ts-node/esm', + '--no-warnings', + '--experimental-import-meta-resolve', + '--throw-deprecation', + ], + }) + }) + + test('wait for child process to finish', async ({ fs, assert }) => { + await fs.create( + 'foo.ts', + ` + setTimeout(() => {}, 1000) + ` + ) + + const childProcess = run(fs.basePath, { script: 'foo.ts', scriptArgs: [], nodeArgs: [] }) + await pEvent(childProcess, 'close', { rejectionEvents: ['error'] }) + assert.equal(childProcess.exitCode, 0) + }) + + test('get child process exit code', async ({ fs, assert }) => { + await fs.create( + 'foo.ts', + ` + throw new Error('Something went wrong') + ` + ) + + const childProcess = run(fs.basePath, { script: 'foo.ts', scriptArgs: [], nodeArgs: [] }) + + await pEvent(childProcess, 'close', { rejectionEvents: ['error'] }) + assert.equal(childProcess.exitCode, 1) + }) + + test('await and get child process exit code', async ({ fs, assert }) => { + assert.plan(1) + + await fs.create( + 'foo.ts', + ` + throw new Error('Something went wrong') + ` + ) + + const childProcess = run(fs.basePath, { script: 'foo.ts', scriptArgs: [], nodeArgs: [] }) + try { + await childProcess + } catch { + assert.equal(childProcess.exitCode, 1) + } + }) +}) diff --git a/tests/watch.spec.ts b/tests/watch.spec.ts new file mode 100644 index 0000000..b8930b4 --- /dev/null +++ b/tests/watch.spec.ts @@ -0,0 +1,131 @@ +/* + * @adonisjs/assembler + * + * (c) AdonisJS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import ts from 'typescript' +import { test } from '@japa/runner' +import { watch } from '../src/watch.js' + +test.group('Watcher', () => { + test('watch files included by the tsconfig.json', async ({ fs, assert, cleanup }, done) => { + assert.plan(1) + + await fs.create( + 'tsconfig.json', + JSON.stringify({ + include: ['./**/*'], + }) + ) + await fs.create('foo.ts', '') + + const output = watch(fs.baseUrl, ts, {}) + cleanup(() => output!.chokidar.close()) + + output!.watcher.on('source:add', (file) => { + assert.equal(file.relativePath, 'bar.ts') + done() + }) + + await fs.create('bar.ts', '') + }).waitForDone() + + test('emit source:change when file is changed', async ({ fs, assert, cleanup }, done) => { + assert.plan(1) + + await fs.create( + 'tsconfig.json', + JSON.stringify({ + include: ['./**/*'], + }) + ) + await fs.create('foo.ts', '') + + const output = watch(fs.baseUrl, ts, {}) + cleanup(() => output!.chokidar.close()) + + output!.watcher.on('source:change', (file) => { + assert.equal(file.relativePath, 'foo.ts') + done() + }) + + await fs.create('foo.ts', 'hello world') + }).waitForDone() + + test('emit source:unlink when file is deleted', async ({ fs, assert, cleanup }, done) => { + assert.plan(1) + + await fs.create( + 'tsconfig.json', + JSON.stringify({ + include: ['./**/*'], + }) + ) + await fs.create('foo.ts', '') + + const output = watch(fs.baseUrl, ts, {}) + cleanup(() => output!.chokidar.close()) + + output!.watcher.on('source:unlink', (file) => { + assert.equal(file.relativePath, 'foo.ts') + done() + }) + + await fs.remove('foo.ts') + }).waitForDone() + + test('do not emit source:add when file is excluded by tsconfig.json', async ({ + fs, + assert, + cleanup, + }) => { + await fs.create( + 'tsconfig.json', + JSON.stringify({ + include: ['./**/*'], + exclude: ['./baz.ts'], + }) + ) + await fs.create('foo.ts', '') + + const output = watch(fs.baseUrl, ts, {}) + cleanup(() => output!.chokidar.close()) + + output!.watcher.on('source:add', () => { + assert.fail('Never expected to reach here') + }) + + await fs.create('baz.ts', '') + await new Promise((resolve) => setTimeout(resolve, 1000)) + }) + + test('emit add when files other than typescript source files are created', async ({ + fs, + assert, + cleanup, + }, done) => { + assert.plan(1) + + await fs.create( + 'tsconfig.json', + JSON.stringify({ + include: ['./**/*'], + }) + ) + await fs.create('foo.ts', '') + + const output = watch(fs.baseUrl, ts, {}) + cleanup(() => output!.chokidar.close()) + + output!.watcher.on('add', (file) => { + assert.equal(file.relativePath, 'foo.md') + done() + }) + + await fs.create('foo.md', '') + }).waitForDone() +}) diff --git a/tsconfig.json b/tsconfig.json index e0b0db1..0604247 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,11 +1,30 @@ { - "extends": "./node_modules/@adonisjs/mrm-preset/_tsconfig", "compilerOptions": { - "experimentalDecorators": true, - "emitDecoratorMetadata": true, - "skipLibCheck": true + "target": "ESNext", + "module": "NodeNext", + "lib": ["ESNext"], + "noUnusedLocals": true, + "noUnusedParameters": true, + "isolatedModules": true, + "removeComments": true, + "declaration": true, + "rootDir": "./", + "outDir": "./build", + "esModuleInterop": true, + "strictNullChecks": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "strictPropertyInitialization": true, + "noImplicitAny": true, + "strictBindCallApply": true, + "strictFunctionTypes": true, + "noImplicitThis": true, + "skipLibCheck": true, + "types": ["@types/node"] }, - "files": [ - "./node_modules/@adonisjs/application/build/adonis-typings/index.d.ts" - ] + "include": ["./**/*"], + "exclude": ["./node_modules", "./build"], + "ts-node": { + "swc": true + } } From 7bd5569277e340c44cdac5e62c4d20d4094ac550 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Wed, 1 Mar 2023 09:29:20 +0530 Subject: [PATCH 002/131] chore: make @adonisjs/env a production dependency --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 73d60da..19db435 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,6 @@ "author": "virk,adonisjs", "license": "MIT", "devDependencies": { - "@adonisjs/env": "^4.2.0-0", "@commitlint/cli": "^17.4.4", "@commitlint/config-conventional": "^17.4.4", "@japa/assert": "^1.4.1", @@ -64,6 +63,7 @@ "typescript": "^4.9.5" }, "dependencies": { + "@adonisjs/env": "^4.2.0-0", "@poppinss/chokidar-ts": "^4.0.0-0", "@types/fs-extra": "^11.0.1", "@types/picomatch": "^2.3.0", From b8e6dfcc1762275501ba2969b4f3d7506b26e869 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Wed, 1 Mar 2023 09:30:01 +0530 Subject: [PATCH 003/131] chore(release): 6.0.0-0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 19db435..4f7cf59 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@adonisjs/assembler", - "version": "5.9.5", + "version": "6.0.0-0", "description": "Provides utilities to run AdonisJS development server and build project for production", "main": "build/index.js", "type": "module", From fe3b4e570aba9123da52aa55557c24d28acb68c2 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Wed, 1 Mar 2023 12:24:31 +0530 Subject: [PATCH 004/131] refactor: cleanup watcher and child process when something goes wrong --- src/dev_server.ts | 67 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 52 insertions(+), 15 deletions(-) diff --git a/src/dev_server.ts b/src/dev_server.ts index 55a0e7a..6ac00df 100644 --- a/src/dev_server.ts +++ b/src/dev_server.ts @@ -17,6 +17,7 @@ import { EnvLoader, EnvParser } from '@adonisjs/env' import { run } from './run.js' import { watch } from './watch.js' import type { DevServerOptions } from './types.js' +import type { Watcher } from '@poppinss/chokidar-ts' /** * Instance of CLIUI @@ -43,6 +44,9 @@ export class DevServer { #httpServerProcess?: ExecaChildProcess #isMetaFileWithReloadsEnabled: picomatch.Matcher #isMetaFileWithReloadsDisabled: picomatch.Matcher + #watcher?: ReturnType + #onError?: (error: any) => any + #onClose?: (exitCode: number) => any /** * Getting reference to colors library from logger @@ -136,7 +140,7 @@ export class DevServer { /** * Starts the HTTP server */ - #startHTTPServer(port: string) { + #startHTTPServer(port: string, mode: 'blocking' | 'nonblocking') { this.#httpServerProcess = run(this.#cwd, { script: this.#scriptFile, env: { PORT: port, ...this.#options.env }, @@ -159,15 +163,20 @@ export class DevServer { } }) - this.#httpServerProcess.on('error', (error) => { - this.#logger.warning('unable to connect to underlying HTTP server process') - this.#logger.fatal(error) - }) - - this.#httpServerProcess.on('close', (exitCode) => { - this.#logger.warning(`underlying HTTP server closed with status code "${exitCode}"`) - this.#logger.info('watching file system and waiting for application to recover') - }) + this.#httpServerProcess + .then((result) => { + this.#logger.warning(`underlying HTTP server closed with status code "${result.exitCode}"`) + if (mode === 'nonblocking') { + this.#onClose?.(result.exitCode) + this.#watcher?.close() + } + }) + .catch((error) => { + this.#logger.warning('unable to connect to underlying HTTP server process') + this.#logger.fatal(error) + this.#onError?.(error) + this.#watcher?.close() + }) } /** @@ -179,7 +188,7 @@ export class DevServer { this.#httpServerProcess.kill('SIGKILL') } - this.#startHTTPServer(port) + this.#startHTTPServer(port, 'blocking') } /** @@ -190,32 +199,53 @@ export class DevServer { return this } + /** + * Add listener to get notified when dev server is + * closed + */ + onClose(callback: (exitCode: number) => any): this { + this.#onClose = callback + return this + } + + /** + * Add listener to get notified when dev server exists + * with an error + */ + onError(callback: (error: any) => any): this { + this.#onError = callback + return this + } + /** * Start the development server */ async start() { this.#clearScreen() this.#logger.info('starting HTTP server...') - this.#startHTTPServer(String(await this.#getPort())) + this.#startHTTPServer(String(await this.#getPort()), 'nonblocking') } /** * Start the development server in watch mode */ async startAndWatch(ts: typeof tsStatic, options?: { poll: boolean }) { - const port = String(await this.#getPort()) - this.#isWatching = true this.#clearScreen() + const port = String(await this.#getPort()) + this.#logger.info('starting HTTP server...') - this.#startHTTPServer(port) + this.#startHTTPServer(port, 'blocking') const output = watch(this.#cwd, ts, options || {}) if (!output) { + this.#onClose?.(1) return } + this.#watcher = output.chokidar + /** * Notify the watcher is ready */ @@ -223,6 +253,13 @@ export class DevServer { this.#logger.info('watching file system for changes...') }) + output.chokidar.on('error', (error) => { + this.#logger.warning('file system watcher failure') + this.#logger.fatal(error) + this.#onError?.(error) + output.chokidar.close() + }) + /** * Changes in TypeScript source file */ From 6e2e8f40e7b75399bed5ba79a8d40376bbe29637 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Wed, 1 Mar 2023 14:17:27 +0530 Subject: [PATCH 005/131] refactor: display config parser errors during build --- src/bundler.ts | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/bundler.ts b/src/bundler.ts index 50302c3..384fbc5 100644 --- a/src/bundler.ts +++ b/src/bundler.ts @@ -62,9 +62,9 @@ export class Bundler { /** * Runs tsc command to build the source. */ - async #runTsc() { + async #runTsc(outDir: string) { try { - await execa('tsc', [], { + await execa('tsc', ['--outDir', outDir], { cwd: this.#cwd, preferLocal: true, localDir: this.#cwd, @@ -161,7 +161,21 @@ export class Bundler { /** * Step 1: Parse config file to get the build output directory */ - const { config } = new ConfigParser(this.#cwd, 'tsconfig.json', this.#ts).parse() + const { config, error } = new ConfigParser(this.#cwd, 'tsconfig.json', this.#ts).parse() + if (error) { + const compilerHost = this.#ts.createCompilerHost({}) + this.#logger.logError(this.#ts.formatDiagnosticsWithColorAndContext([error], compilerHost)) + return false + } + + if (config!.errors.length) { + const compilerHost = this.#ts.createCompilerHost({}) + this.#logger.logError( + this.#ts.formatDiagnosticsWithColorAndContext(config!.errors, compilerHost) + ) + return false + } + if (!config) { return false } @@ -177,7 +191,7 @@ export class Bundler { * Step 3: Build typescript source code */ this.#logger.info('compiling typescript source', { suffix: 'tsc' }) - const buildCompleted = await this.#runTsc() + const buildCompleted = await this.#runTsc(outDir) await this.#copyFiles(['ace.js'], outDir) /** From d36967f2eb58666efd116bb7cae180269f3c568a Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Wed, 1 Mar 2023 14:30:28 +0530 Subject: [PATCH 006/131] docs(README): update readme file --- README.md | 61 ++++++++++++++++++++++--------------------------------- 1 file changed, 24 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index f2c1227..52c2286 100644 --- a/README.md +++ b/README.md @@ -1,49 +1,36 @@ -
- -
+# @adonisjs/assembler
-
-

Core Commands for building AdonisJS projects

-

- Assembler contains a set of core commands to build and serve the AdonisJS typescript project, along with scaffolding make commands. -

-
+[![gh-workflow-image]][gh-workflow-url] [![npm-image]][npm-url] ![][typescript-image] [![license-image]][license-url] [![synk-image]][synk-url] -
+## Introduction +Assembler exports the API for starting the **AdonisJS development server**, **building project for production** and **running tests** in watch mode. Assembler must be used during development only. + +## Official Documentation +The documentation is available on the official website + +## Contributing +One of the primary goals of AdonisJS is to have a vibrant community of users and contributors who believes in the principles of the framework. + +We encourage you to read the [contribution guide](https://github.com/adonisjs/.github/blob/main/docs/CONTRIBUTING.md) before contributing to the framework. -
- -[![gh-workflow-image]][gh-workflow-url] [![npm-image]][npm-url] [![synk-image]][synk-url] - -
- - - -
- Built with ❤︎ by Harminder Virk -
- -[gh-workflow-image]: https://img.shields.io/github/workflow/status/adonisjs/assembler/test?style=for-the-badge +## Code of Conduct +In order to ensure that the AdonisJS community is welcoming to all, please review and abide by the [Code of Conduct](https://github.com/adonisjs/.github/blob/main/docs/CODE_OF_CONDUCT.md). + +## License +AdonisJS Assembler is open-sourced software licensed under the [MIT license](LICENSE.md). + +[gh-workflow-image]: https://img.shields.io/github/actions/workflow/status/adonisjs/assembler/test.yml?style=for-the-badge [gh-workflow-url]: https://github.com/adonisjs/assembler/actions/workflows/test.yml "Github action" [npm-image]: https://img.shields.io/npm/v/@adonisjs/assembler/latest.svg?style=for-the-badge&logo=npm [npm-url]: https://npmjs.org/package/@adonisjs/assembler/v/latest "npm" +[typescript-image]: https://img.shields.io/badge/Typescript-294E80.svg?style=for-the-badge&logo=typescript + +[license-url]: LICENSE.md +[license-image]: https://img.shields.io/github/license/adonisjs/ace?style=for-the-badge + [synk-image]: https://img.shields.io/snyk/vulnerabilities/github/adonisjs/assembler?label=Synk%20Vulnerabilities&style=for-the-badge [synk-url]: https://snyk.io/test/github/adonisjs/assembler?targetFile=package.json "synk" From 0c04b0ba0d44d99fe45bcd89d9061ea12b140c54 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Wed, 1 Mar 2023 14:31:37 +0530 Subject: [PATCH 007/131] test: increase tests timeout --- bin/test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/test.ts b/bin/test.ts index 0bda2e2..d1ff260 100644 --- a/bin/test.ts +++ b/bin/test.ts @@ -26,6 +26,7 @@ configure({ files: ['tests/**/*.spec.ts'], plugins: [assert(), runFailedTests(), fileSystem({ basePath: TEST_TMP_DIR_PATH })], reporters: [specReporter()], + timeout: 10 * 1000, importer: (filePath: string) => import(pathToFileURL(filePath).href), }, }) From 094c221c29632d388f0cb9032f370f04969b4e8c Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Wed, 1 Mar 2023 14:40:06 +0530 Subject: [PATCH 008/131] ci: add debugging log for failing test --- package.json | 2 +- tests/watch.spec.ts | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 4f7cf59..f34d216 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ }, "scripts": { "pretest": "npm run lint", - "test": "cross-env NODE_DEBUG=adonisjs:assembler c8 npm run vscode:test", + "test": "cross-env NODE_DEBUG=chokidar:ts c8 npm run vscode:test", "lint": "eslint . --ext=.ts", "clean": "del-cli build", "compile": "npm run lint && npm run clean && tsc", diff --git a/tests/watch.spec.ts b/tests/watch.spec.ts index b8930b4..d44237a 100644 --- a/tests/watch.spec.ts +++ b/tests/watch.spec.ts @@ -23,9 +23,13 @@ test.group('Watcher', () => { ) await fs.create('foo.ts', '') - const output = watch(fs.baseUrl, ts, {}) + const output = watch(fs.baseUrl, ts, { poll: true }) cleanup(() => output!.chokidar.close()) + output?.chokidar.on('all', (event, path) => { + console.log('chokidar event', { event, path }) + }) + output!.watcher.on('source:add', (file) => { assert.equal(file.relativePath, 'bar.ts') done() From 8e63062c49c3149de149eeef87722cdafa292738 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Wed, 1 Mar 2023 16:29:29 +0530 Subject: [PATCH 009/131] test: skip watcher tests in CI Cannot get chokidar to work in CI --- tests/watch.spec.ts | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/tests/watch.spec.ts b/tests/watch.spec.ts index d44237a..815b92d 100644 --- a/tests/watch.spec.ts +++ b/tests/watch.spec.ts @@ -23,20 +23,18 @@ test.group('Watcher', () => { ) await fs.create('foo.ts', '') - const output = watch(fs.baseUrl, ts, { poll: true }) + const output = watch(fs.baseUrl, ts, {}) cleanup(() => output!.chokidar.close()) - output?.chokidar.on('all', (event, path) => { - console.log('chokidar event', { event, path }) - }) - output!.watcher.on('source:add', (file) => { assert.equal(file.relativePath, 'bar.ts') done() }) await fs.create('bar.ts', '') - }).waitForDone() + }) + .waitForDone() + .skip(!!process.env.CI, 'Skipping in CI. Cannot get chokidar to work') test('emit source:change when file is changed', async ({ fs, assert, cleanup }, done) => { assert.plan(1) @@ -58,7 +56,9 @@ test.group('Watcher', () => { }) await fs.create('foo.ts', 'hello world') - }).waitForDone() + }) + .waitForDone() + .skip(!!process.env.CI, 'Skipping in CI. Cannot get chokidar to work') test('emit source:unlink when file is deleted', async ({ fs, assert, cleanup }, done) => { assert.plan(1) @@ -80,7 +80,9 @@ test.group('Watcher', () => { }) await fs.remove('foo.ts') - }).waitForDone() + }) + .waitForDone() + .skip(!!process.env.CI, 'Skipping in CI. Cannot get chokidar to work') test('do not emit source:add when file is excluded by tsconfig.json', async ({ fs, @@ -105,7 +107,7 @@ test.group('Watcher', () => { await fs.create('baz.ts', '') await new Promise((resolve) => setTimeout(resolve, 1000)) - }) + }).skip(!!process.env.CI, 'Skipping in CI. Cannot get chokidar to work') test('emit add when files other than typescript source files are created', async ({ fs, @@ -131,5 +133,7 @@ test.group('Watcher', () => { }) await fs.create('foo.md', '') - }).waitForDone() + }) + .waitForDone() + .skip(!!process.env.CI, 'Skipping in CI. Cannot get chokidar to work') }) From 4c101f3d682cef9ae0550e9938cf87e2c522fbff Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Wed, 1 Mar 2023 16:34:14 +0530 Subject: [PATCH 010/131] chore(release): 6.1.0-0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f34d216..dc806e3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@adonisjs/assembler", - "version": "6.0.0-0", + "version": "6.1.0-0", "description": "Provides utilities to run AdonisJS development server and build project for production", "main": "build/index.js", "type": "module", From bb68c2b381ff1c7bb8c8afc567c1c98488efe9a0 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Thu, 2 Mar 2023 11:53:58 +0530 Subject: [PATCH 011/131] feat: run assets bundler when running dev server --- src/dev_server.ts | 55 +++++++++++++++++++++++++++++++++++++++++------ src/run.ts | 23 +++++++++++++++++--- src/types.ts | 11 ++++++++++ tests/run.spec.ts | 14 ++++++------ 4 files changed, 87 insertions(+), 16 deletions(-) diff --git a/src/dev_server.ts b/src/dev_server.ts index 6ac00df..1b24bc0 100644 --- a/src/dev_server.ts +++ b/src/dev_server.ts @@ -11,13 +11,13 @@ import getPort from 'get-port' import picomatch from 'picomatch' import type tsStatic from 'typescript' import { type ExecaChildProcess } from 'execa' +import type { Watcher } from '@poppinss/chokidar-ts' import { cliui, type Logger } from '@poppinss/cliui' import { EnvLoader, EnvParser } from '@adonisjs/env' -import { run } from './run.js' import { watch } from './watch.js' +import { run, runNode } from './run.js' import type { DevServerOptions } from './types.js' -import type { Watcher } from '@poppinss/chokidar-ts' /** * Instance of CLIUI @@ -45,6 +45,7 @@ export class DevServer { #isMetaFileWithReloadsEnabled: picomatch.Matcher #isMetaFileWithReloadsDisabled: picomatch.Matcher #watcher?: ReturnType + #assetsServerProcess?: ExecaChildProcess #onError?: (error: any) => any #onClose?: (exitCode: number) => any @@ -141,7 +142,7 @@ export class DevServer { * Starts the HTTP server */ #startHTTPServer(port: string, mode: 'blocking' | 'nonblocking') { - this.#httpServerProcess = run(this.#cwd, { + this.#httpServerProcess = runNode(this.#cwd, { script: this.#scriptFile, env: { PORT: port, ...this.#options.env }, nodeArgs: this.#options.nodeArgs, @@ -179,6 +180,35 @@ export class DevServer { }) } + /** + * Starts the assets bundler server. The assets bundler server process is + * considered as the secondary process and therefore we do not perform + * any cleanup if it dies. + */ + #startAssetsServer() { + const assetsBundler = this.#options.assets + if (!assetsBundler?.serve) { + return + } + + this.#logger.info(`starting "${assetsBundler.driver}" dev server...`) + this.#assetsServerProcess = run(assetsBundler.cmd, { + script: this.#scriptFile, + scriptArgs: this.#options.scriptArgs, + }) + + this.#assetsServerProcess + .then((result) => { + this.#logger.warning( + `"${assetsBundler.driver}" dev server closed with status code "${result.exitCode}"` + ) + }) + .catch((error) => { + this.#logger.warning(`unable to connect to "${assetsBundler.driver}" dev server`) + this.#logger.fatal(error) + }) + } + /** * Restart the development server */ @@ -224,26 +254,36 @@ export class DevServer { this.#clearScreen() this.#logger.info('starting HTTP server...') this.#startHTTPServer(String(await this.#getPort()), 'nonblocking') + + this.#startAssetsServer() } /** * Start the development server in watch mode */ async startAndWatch(ts: typeof tsStatic, options?: { poll: boolean }) { - this.#isWatching = true - this.#clearScreen() - const port = String(await this.#getPort()) + this.#isWatching = true + this.#clearScreen() this.#logger.info('starting HTTP server...') + this.#startHTTPServer(port, 'blocking') + this.#startAssetsServer() + /** + * Create watcher using tsconfig.json file + */ const output = watch(this.#cwd, ts, options || {}) if (!output) { this.#onClose?.(1) return } + /** + * Storing reference to watcher, so that we can close it + * when HTTP server exists with error + */ this.#watcher = output.chokidar /** @@ -253,6 +293,9 @@ export class DevServer { this.#logger.info('watching file system for changes...') }) + /** + * Cleanup when watcher dies + */ output.chokidar.on('error', (error) => { this.#logger.warning('file system watcher failure') this.#logger.fatal(error) diff --git a/src/run.ts b/src/run.ts index 1c5dc85..f3189b4 100644 --- a/src/run.ts +++ b/src/run.ts @@ -7,7 +7,7 @@ * file that was distributed with this source code. */ -import { execaNode } from 'execa' +import { execaNode, execa } from 'execa' import type { RunOptions } from './types.js' /** @@ -24,9 +24,9 @@ const DEFAULT_NODE_ARGS = [ ] /** - * Runs a script as a child process and inherits the stdio streams + * Runs a Node.js script as a child process and inherits the stdio streams */ -export function run(cwd: string | URL, options: RunOptions) { +export function runNode(cwd: string | URL, options: RunOptions) { const childProces = execaNode(options.script, options.scriptArgs, { nodeOptions: DEFAULT_NODE_ARGS.concat(options.nodeArgs), preferLocal: true, @@ -40,3 +40,20 @@ export function run(cwd: string | URL, options: RunOptions) { return childProces } + +/** + * Runs a script as a child process and inherits the stdio streams + */ +export function run(cwd: string | URL, options: Omit) { + const childProces = execa(options.script, options.scriptArgs, { + preferLocal: true, + windowsHide: false, + localDir: cwd, + cwd, + buffer: false, + stdio: 'inherit', + env: options.env, + }) + + return childProces +} diff --git a/src/types.ts b/src/types.ts index a80d49e..d8ac9a7 100644 --- a/src/types.ts +++ b/src/types.ts @@ -36,6 +36,17 @@ export type DevServerOptions = { pattern: string reloadServer: boolean }[] + assets?: + | { + serve: false + driver?: string + cmd?: string + } + | { + serve: true + driver: string + cmd: string + } } /** diff --git a/tests/run.spec.ts b/tests/run.spec.ts index fd24cf6..516eb6d 100644 --- a/tests/run.spec.ts +++ b/tests/run.spec.ts @@ -9,7 +9,7 @@ import { pEvent } from 'p-event' import { test } from '@japa/runner' -import { run } from '../src/run.js' +import { run, runNode } from '../src/run.js' test.group('Child process', () => { test('run typescript file as a child process', async ({ fs, assert }) => { @@ -20,7 +20,7 @@ test.group('Child process', () => { ` ) - const childProcess = run(fs.basePath, { script: 'foo.ts', scriptArgs: [], nodeArgs: [] }) + const childProcess = runNode(fs.basePath, { script: 'foo.ts', scriptArgs: [], nodeArgs: [] }) const payload = await pEvent(childProcess, 'message', { rejectionEvents: ['error'] }) await pEvent(childProcess, 'close', { rejectionEvents: ['error'] }) @@ -37,7 +37,7 @@ test.group('Child process', () => { ` ) - const childProcess = run(fs.basePath, { + const childProcess = runNode(fs.basePath, { script: 'foo.ts', scriptArgs: ['--watch', '--foo=bar'], nodeArgs: [], @@ -57,7 +57,7 @@ test.group('Child process', () => { ` ) - const childProcess = run(fs.basePath, { + const childProcess = runNode(fs.basePath, { script: 'foo.ts', scriptArgs: ['--watch', '--foo=bar'], nodeArgs: ['--throw-deprecation'], @@ -85,7 +85,7 @@ test.group('Child process', () => { ` ) - const childProcess = run(fs.basePath, { script: 'foo.ts', scriptArgs: [], nodeArgs: [] }) + const childProcess = runNode(fs.basePath, { script: 'foo.ts', scriptArgs: [], nodeArgs: [] }) await pEvent(childProcess, 'close', { rejectionEvents: ['error'] }) assert.equal(childProcess.exitCode, 0) }) @@ -98,7 +98,7 @@ test.group('Child process', () => { ` ) - const childProcess = run(fs.basePath, { script: 'foo.ts', scriptArgs: [], nodeArgs: [] }) + const childProcess = runNode(fs.basePath, { script: 'foo.ts', scriptArgs: [], nodeArgs: [] }) await pEvent(childProcess, 'close', { rejectionEvents: ['error'] }) assert.equal(childProcess.exitCode, 1) @@ -114,7 +114,7 @@ test.group('Child process', () => { ` ) - const childProcess = run(fs.basePath, { script: 'foo.ts', scriptArgs: [], nodeArgs: [] }) + const childProcess = runNode(fs.basePath, { script: 'foo.ts', scriptArgs: [], nodeArgs: [] }) try { await childProcess } catch { From 3d738bc3ea54d616bf22103782d58a18fd0ecb8d Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Thu, 2 Mar 2023 11:59:54 +0530 Subject: [PATCH 012/131] refactor: remove unused imports --- tests/run.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/run.spec.ts b/tests/run.spec.ts index 516eb6d..06f0ef9 100644 --- a/tests/run.spec.ts +++ b/tests/run.spec.ts @@ -9,7 +9,7 @@ import { pEvent } from 'p-event' import { test } from '@japa/runner' -import { run, runNode } from '../src/run.js' +import { runNode } from '../src/run.js' test.group('Child process', () => { test('run typescript file as a child process', async ({ fs, assert }) => { From a62f756cae6be41c8a59a128bc0e0da5418b62ff Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Thu, 2 Mar 2023 12:08:07 +0530 Subject: [PATCH 013/131] chore: update dependencies --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index dc806e3..7c292f4 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "@poppinss/cliui": "^6.1.1-0", "@poppinss/dev-utils": "^2.0.3", "@swc/core": "^1.3.36", - "@types/node": "^18.14.1", + "@types/node": "^18.14.3", "c8": "^7.13.0", "copyfiles": "^2.4.1", "cross-env": "^7.0.3", @@ -64,7 +64,7 @@ }, "dependencies": { "@adonisjs/env": "^4.2.0-0", - "@poppinss/chokidar-ts": "^4.0.0-0", + "@poppinss/chokidar-ts": "^4.1.0-0", "@types/fs-extra": "^11.0.1", "@types/picomatch": "^2.3.0", "cpy": "^9.0.1", From 5b184fade1c26119693919e628949641de988496 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Thu, 2 Mar 2023 12:09:18 +0530 Subject: [PATCH 014/131] chore: add typescript as a peer dependency --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index 7c292f4..f7230bc 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,9 @@ "picomatch": "^2.3.1", "slash": "^5.0.0" }, + "peerDependencies": { + "typescript": "^4.0.0 || ^5.0.0" + }, "repository": { "type": "git", "url": "git+ssh://git@github.com/adonisjs/assembler.git" From 7774a0facdee031f6b99223190b7a9a68da7f66c Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Thu, 2 Mar 2023 12:16:50 +0530 Subject: [PATCH 015/131] test: fix flaky tests --- bin/test.ts | 2 +- tests/watch.spec.ts | 20 +++++++++++++++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/bin/test.ts b/bin/test.ts index d1ff260..196870b 100644 --- a/bin/test.ts +++ b/bin/test.ts @@ -26,7 +26,7 @@ configure({ files: ['tests/**/*.spec.ts'], plugins: [assert(), runFailedTests(), fileSystem({ basePath: TEST_TMP_DIR_PATH })], reporters: [specReporter()], - timeout: 10 * 1000, + timeout: 5 * 1000, importer: (filePath: string) => import(pathToFileURL(filePath).href), }, }) diff --git a/tests/watch.spec.ts b/tests/watch.spec.ts index 815b92d..f3975f8 100644 --- a/tests/watch.spec.ts +++ b/tests/watch.spec.ts @@ -31,7 +31,9 @@ test.group('Watcher', () => { done() }) - await fs.create('bar.ts', '') + output!.watcher.on('watcher:ready', async () => { + await fs.create('bar.ts', '') + }) }) .waitForDone() .skip(!!process.env.CI, 'Skipping in CI. Cannot get chokidar to work') @@ -55,7 +57,9 @@ test.group('Watcher', () => { done() }) - await fs.create('foo.ts', 'hello world') + output!.watcher.on('watcher:ready', async () => { + await fs.create('foo.ts', 'hello world') + }) }) .waitForDone() .skip(!!process.env.CI, 'Skipping in CI. Cannot get chokidar to work') @@ -79,7 +83,9 @@ test.group('Watcher', () => { done() }) - await fs.remove('foo.ts') + output!.watcher.on('watcher:ready', async () => { + await fs.remove('foo.ts') + }) }) .waitForDone() .skip(!!process.env.CI, 'Skipping in CI. Cannot get chokidar to work') @@ -105,7 +111,9 @@ test.group('Watcher', () => { assert.fail('Never expected to reach here') }) - await fs.create('baz.ts', '') + output!.watcher.on('watcher:ready', async () => { + await fs.create('baz.ts', '') + }) await new Promise((resolve) => setTimeout(resolve, 1000)) }).skip(!!process.env.CI, 'Skipping in CI. Cannot get chokidar to work') @@ -132,7 +140,9 @@ test.group('Watcher', () => { done() }) - await fs.create('foo.md', '') + output!.watcher.on('watcher:ready', async () => { + await fs.create('foo.md', '') + }) }) .waitForDone() .skip(!!process.env.CI, 'Skipping in CI. Cannot get chokidar to work') From 8ce240635f7529570275e2272be3269398ed93b6 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Thu, 2 Mar 2023 12:18:49 +0530 Subject: [PATCH 016/131] chore(release): 6.1.1-0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f7230bc..2a24bfd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@adonisjs/assembler", - "version": "6.1.0-0", + "version": "6.1.1-0", "description": "Provides utilities to run AdonisJS development server and build project for production", "main": "build/index.js", "type": "module", From caabca89136f2a04c03fc8a904ebd083bdd0af8f Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Thu, 2 Mar 2023 13:34:00 +0530 Subject: [PATCH 017/131] fix: install missing dependencies --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 2a24bfd..96b33f5 100644 --- a/package.json +++ b/package.json @@ -42,8 +42,6 @@ "@japa/run-failed-tests": "^1.1.1", "@japa/runner": "^2.5.1", "@japa/spec-reporter": "^1.3.3", - "@poppinss/cliui": "^6.1.1-0", - "@poppinss/dev-utils": "^2.0.3", "@swc/core": "^1.3.36", "@types/node": "^18.14.3", "c8": "^7.13.0", @@ -65,6 +63,7 @@ "dependencies": { "@adonisjs/env": "^4.2.0-0", "@poppinss/chokidar-ts": "^4.1.0-0", + "@poppinss/cliui": "^6.1.1-0", "@types/fs-extra": "^11.0.1", "@types/picomatch": "^2.3.0", "cpy": "^9.0.1", From b8519bed6623b804b6eef29047edf85ece3dd8e5 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Thu, 2 Mar 2023 13:39:10 +0530 Subject: [PATCH 018/131] chore: remove unused dependencies --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 96b33f5..c2f3906 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,6 @@ "@swc/core": "^1.3.36", "@types/node": "^18.14.3", "c8": "^7.13.0", - "copyfiles": "^2.4.1", "cross-env": "^7.0.3", "del-cli": "^5.0.0", "eslint": "^8.34.0", From af69a6f0910c1756244854cbf5a4d099521fc617 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Thu, 2 Mar 2023 13:41:25 +0530 Subject: [PATCH 019/131] chore(release): 6.1.2-0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c2f3906..171532f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@adonisjs/assembler", - "version": "6.1.1-0", + "version": "6.1.2-0", "description": "Provides utilities to run AdonisJS development server and build project for production", "main": "build/index.js", "type": "module", From d7b89443967e3e467637d80af04b07dcfa8fc58f Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Fri, 3 Mar 2023 08:32:40 +0530 Subject: [PATCH 020/131] feat: build assets when running build command --- src/bundler.ts | 71 ++++++++++++++++++++++--------------- src/dev_server.ts | 85 +++++++++++++++++++++++++++++++++++++++++++-- src/parse_config.ts | 32 +++++++++++++++++ src/run.ts | 22 +++++++----- src/types.ts | 47 +++++++++++++++---------- src/watch.ts | 19 +++------- 6 files changed, 204 insertions(+), 72 deletions(-) create mode 100644 src/parse_config.ts diff --git a/src/bundler.ts b/src/bundler.ts index 384fbc5..e8026b8 100644 --- a/src/bundler.ts +++ b/src/bundler.ts @@ -10,13 +10,13 @@ import fs from 'fs-extra' import slash from 'slash' import copyfiles from 'cpy' -import { execa } from 'execa' -import { join, relative } from 'node:path' import type tsStatic from 'typescript' import { fileURLToPath } from 'node:url' -import { ConfigParser } from '@poppinss/chokidar-ts' +import { join, relative } from 'node:path' import { cliui, type Logger } from '@poppinss/cliui' +import { run } from './run.js' +import { parseConfig } from './parse_config.js' import type { BundlerOptions } from './types.js' /** @@ -59,18 +59,37 @@ export class Bundler { await fs.remove(outDir) } + /** + * Runs assets bundler command to build assets. + */ + async #buildAssets(): Promise { + const assetsBundler = this.#options.assets + if (!assetsBundler?.serve) { + return true + } + + try { + this.#logger.info('compiling frontend assets', { suffix: assetsBundler.cmd }) + await run(this.#cwd, { + stdio: 'inherit', + script: assetsBundler.cmd, + scriptArgs: [], + }) + return true + } catch { + return false + } + } + /** * Runs tsc command to build the source. */ - async #runTsc(outDir: string) { + async #runTsc(outDir: string): Promise { try { - await execa('tsc', ['--outDir', outDir], { - cwd: this.#cwd, - preferLocal: true, - localDir: this.#cwd, - windowsHide: false, - buffer: false, + await run(this.#cwd, { stdio: 'inherit', + script: 'tsc', + scriptArgs: ['--outDir', outDir], }) return true } catch { @@ -161,21 +180,7 @@ export class Bundler { /** * Step 1: Parse config file to get the build output directory */ - const { config, error } = new ConfigParser(this.#cwd, 'tsconfig.json', this.#ts).parse() - if (error) { - const compilerHost = this.#ts.createCompilerHost({}) - this.#logger.logError(this.#ts.formatDiagnosticsWithColorAndContext([error], compilerHost)) - return false - } - - if (config!.errors.length) { - const compilerHost = this.#ts.createCompilerHost({}) - this.#logger.logError( - this.#ts.formatDiagnosticsWithColorAndContext(config!.errors, compilerHost) - ) - return false - } - + const config = parseConfig(this.#cwd, this.#ts) if (!config) { return false } @@ -188,7 +193,14 @@ export class Bundler { await this.#cleanupBuildDirectory(outDir) /** - * Step 3: Build typescript source code + * Step 3: Build frontend assets + */ + if (!(await this.#buildAssets())) { + return false + } + + /** + * Step 4: Build typescript source code */ this.#logger.info('compiling typescript source', { suffix: 'tsc' }) const buildCompleted = await this.#runTsc(outDir) @@ -219,14 +231,14 @@ export class Bundler { } /** - * Step 4: Copy meta files to the build directory + * Step 5: Copy meta files to the build directory */ const pkgFiles = ['package.json', this.#getClientLockFile(client)] this.#logger.info('copying meta files to the output directory') await this.#copyMetaFiles(outDir, pkgFiles) /** - * Step 5: Copy .adonisrc.json file to the build directory + * Step 6: Copy .adonisrc.json file to the build directory */ this.#logger.info('copying .adonisrc.json file to the output directory') await this.#copyAdonisRcFile(outDir) @@ -234,6 +246,9 @@ export class Bundler { this.#logger.success('build completed') this.#logger.log('') + /** + * Next steps + */ ui.instructions() .useRenderer(this.#logger.getRenderer()) .heading('Run the following commands to start the server in production') diff --git a/src/dev_server.ts b/src/dev_server.ts index 1b24bc0..631cb88 100644 --- a/src/dev_server.ts +++ b/src/dev_server.ts @@ -115,6 +115,62 @@ export class DevServer { } } + /** + * Logs messages from vite dev server stdout and stderr + */ + #logViteDevServerMessage(data: Buffer) { + const dataString = data.toString() + const lines = dataString.split('\n') + + /** + * Logging VITE ready in message with proper + * spaces and newlines + */ + if (dataString.includes('ready in')) { + console.log('') + console.log(dataString.trim()) + return + } + + /** + * Put a wrapper around vite network address log + */ + if (dataString.includes('Local') && dataString.includes('Network')) { + const sticker = ui.sticker().useColors(this.#colors).useRenderer(this.#logger.getRenderer()) + + lines.forEach((line: string) => { + if (line.trim()) { + sticker.add(line) + } + }) + + sticker.render() + return + } + + /** + * Log rest of the lines + */ + lines.forEach((line: string) => { + if (line.trim()) { + console.log(line) + } + }) + } + + /** + * Logs messages from assets dev server stdout and stderr + */ + #logAssetsDevServerMessage(data: Buffer) { + const dataString = data.toString() + const lines = dataString.split('\n') + lines.forEach((line: string) => { + if (line.trim()) { + console.log(line) + } + }) + } + /** * Returns PORT for starting the HTTP server with option to use * a random PORT if main PORT is in use. @@ -192,11 +248,36 @@ export class DevServer { } this.#logger.info(`starting "${assetsBundler.driver}" dev server...`) - this.#assetsServerProcess = run(assetsBundler.cmd, { - script: this.#scriptFile, + this.#assetsServerProcess = run(this.#cwd, { + script: assetsBundler.cmd, + + /** + * We do not inherit the stdio for vite and encore, because they then + * own the stdin and interrupts the `Ctrl + C`. + */ + stdio: 'pipe', scriptArgs: this.#options.scriptArgs, }) + /** + * Log child process messages + */ + this.#assetsServerProcess.stdout?.on('data', (data) => { + if (assetsBundler.driver === 'vite') { + this.#logViteDevServerMessage(data) + } else { + this.#logAssetsDevServerMessage(data) + } + }) + + this.#assetsServerProcess.stderr?.on('data', (data) => { + if (assetsBundler.driver === 'vite') { + this.#logViteDevServerMessage(data) + } else { + this.#logAssetsDevServerMessage(data) + } + }) + this.#assetsServerProcess .then((result) => { this.#logger.warning( diff --git a/src/parse_config.ts b/src/parse_config.ts new file mode 100644 index 0000000..31afe14 --- /dev/null +++ b/src/parse_config.ts @@ -0,0 +1,32 @@ +/* + * @adonisjs/assembler + * + * (c) AdonisJS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import type tsStatic from 'typescript' +import { ConfigParser } from '@poppinss/chokidar-ts' + +/** + * Parses tsconfig.json and prints errors using typescript compiler + * host + */ +export function parseConfig(cwd: string | URL, ts: typeof tsStatic) { + const { config, error } = new ConfigParser(cwd, 'tsconfig.json', ts).parse() + if (error) { + const compilerHost = ts.createCompilerHost({}) + console.log(ts.formatDiagnosticsWithColorAndContext([error], compilerHost)) + return + } + + if (config!.errors.length) { + const compilerHost = ts.createCompilerHost({}) + console.log(ts.formatDiagnosticsWithColorAndContext(config!.errors, compilerHost)) + return + } + + return config +} diff --git a/src/run.ts b/src/run.ts index f3189b4..db658d2 100644 --- a/src/run.ts +++ b/src/run.ts @@ -27,33 +27,39 @@ const DEFAULT_NODE_ARGS = [ * Runs a Node.js script as a child process and inherits the stdio streams */ export function runNode(cwd: string | URL, options: RunOptions) { - const childProces = execaNode(options.script, options.scriptArgs, { + const childProcess = execaNode(options.script, options.scriptArgs, { nodeOptions: DEFAULT_NODE_ARGS.concat(options.nodeArgs), preferLocal: true, windowsHide: false, localDir: cwd, cwd, buffer: false, - stdio: 'inherit', - env: options.env, + stdio: options.stdio || 'inherit', + env: { + ...(options.stdio === 'pipe' ? { FORCE_COLOR: 'true' } : {}), + ...options.env, + }, }) - return childProces + return childProcess } /** * Runs a script as a child process and inherits the stdio streams */ export function run(cwd: string | URL, options: Omit) { - const childProces = execa(options.script, options.scriptArgs, { + const childProcess = execa(options.script, options.scriptArgs, { preferLocal: true, windowsHide: false, localDir: cwd, cwd, buffer: false, - stdio: 'inherit', - env: options.env, + stdio: options.stdio || 'inherit', + env: { + ...(options.stdio === 'pipe' ? { FORCE_COLOR: 'true' } : {}), + ...options.env, + }, }) - return childProces + return childProcess } diff --git a/src/types.ts b/src/types.ts index d8ac9a7..a3019cd 100644 --- a/src/types.ts +++ b/src/types.ts @@ -14,6 +14,7 @@ export type RunOptions = { script: string scriptArgs: string[] nodeArgs: string[] + stdio?: 'pipe' | 'inherit' env?: NodeJS.ProcessEnv } @@ -24,6 +25,29 @@ export type WatchOptions = { poll?: boolean } +/** + * Meta file config defined in ".adonisrc.json" file + */ +export type MetaFile = { + pattern: string + reloadServer: boolean +} + +/** + * Options accepted by assets bundler + */ +export type AssetsBundlerOptions = + | { + serve: false + driver?: string + cmd?: string + } + | { + serve: true + driver: string + cmd: string + } + /** * Options accepted by the dev server */ @@ -32,29 +56,14 @@ export type DevServerOptions = { nodeArgs: string[] clearScreen?: boolean env?: NodeJS.ProcessEnv - metaFiles?: { - pattern: string - reloadServer: boolean - }[] - assets?: - | { - serve: false - driver?: string - cmd?: string - } - | { - serve: true - driver: string - cmd: string - } + metaFiles?: MetaFile[] + assets?: AssetsBundlerOptions } /** * Options accepted by the project bundler */ export type BundlerOptions = { - metaFiles?: { - pattern: string - reloadServer: boolean - }[] + metaFiles?: MetaFile[] + assets?: AssetsBundlerOptions } diff --git a/src/watch.ts b/src/watch.ts index 6348318..cc0d9df 100644 --- a/src/watch.ts +++ b/src/watch.ts @@ -9,28 +9,17 @@ import type tsStatic from 'typescript' import { fileURLToPath } from 'node:url' -import { ConfigParser, Watcher } from '@poppinss/chokidar-ts' +import { Watcher } from '@poppinss/chokidar-ts' import type { WatchOptions } from './types.js' +import { parseConfig } from './parse_config.js' /** * Watches the file system using tsconfig file */ export function watch(cwd: string | URL, ts: typeof tsStatic, options: WatchOptions) { - /** - * Parsing config to get a list of includes, excludes and initial - * set of files - */ - const { config, error } = new ConfigParser(cwd, 'tsconfig.json', ts).parse() - if (error) { - const compilerHost = ts.createCompilerHost({}) - console.log(ts.formatDiagnosticsWithColorAndContext([error], compilerHost)) - return - } - - if (config!.errors.length) { - const compilerHost = ts.createCompilerHost({}) - console.log(ts.formatDiagnosticsWithColorAndContext(config!.errors, compilerHost)) + const config = parseConfig(cwd, ts) + if (!config) { return } From dc696c6d1b121e5039bfaf115e1fcb364ca3c251 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Fri, 3 Mar 2023 08:37:21 +0530 Subject: [PATCH 021/131] chore(release): 6.1.3-0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 171532f..c25950a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@adonisjs/assembler", - "version": "6.1.2-0", + "version": "6.1.3-0", "description": "Provides utilities to run AdonisJS development server and build project for production", "main": "build/index.js", "type": "module", From 3124ee4a34ac76c8eb745ab52dce5d8f7174050d Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Sat, 4 Mar 2023 09:05:48 +0530 Subject: [PATCH 022/131] chore: update dependencies --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c25950a..3e3730d 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "@japa/runner": "^2.5.1", "@japa/spec-reporter": "^1.3.3", "@swc/core": "^1.3.36", - "@types/node": "^18.14.3", + "@types/node": "^18.14.6", "c8": "^7.13.0", "cross-env": "^7.0.3", "del-cli": "^5.0.0", From e8bd63e2901effb489cac57db66086092d3baf2e Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Sat, 11 Mar 2023 13:22:00 +0530 Subject: [PATCH 023/131] chore: update dependencies --- package.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 3e3730d..3172963 100644 --- a/package.json +++ b/package.json @@ -42,16 +42,16 @@ "@japa/run-failed-tests": "^1.1.1", "@japa/runner": "^2.5.1", "@japa/spec-reporter": "^1.3.3", - "@swc/core": "^1.3.36", - "@types/node": "^18.14.6", + "@swc/core": "^1.3.39", + "@types/node": "^18.15.0", "c8": "^7.13.0", "cross-env": "^7.0.3", "del-cli": "^5.0.0", - "eslint": "^8.34.0", - "eslint-config-prettier": "^8.6.0", + "eslint": "^8.36.0", + "eslint-config-prettier": "^8.7.0", "eslint-plugin-adonis": "^3.0.3", "eslint-plugin-prettier": "^4.2.1", - "github-label-sync": "^2.2.0", + "github-label-sync": "^2.3.1", "husky": "^8.0.3", "np": "^7.6.3", "p-event": "^5.0.1", @@ -61,8 +61,8 @@ }, "dependencies": { "@adonisjs/env": "^4.2.0-0", - "@poppinss/chokidar-ts": "^4.1.0-0", - "@poppinss/cliui": "^6.1.1-0", + "@poppinss/chokidar-ts": "^4.1.0-1", + "@poppinss/cliui": "^6.1.1-1", "@types/fs-extra": "^11.0.1", "@types/picomatch": "^2.3.0", "cpy": "^9.0.1", From e300d078ec6cb6c898d16b70518f6996c4f4b1e6 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Sat, 11 Mar 2023 13:28:13 +0530 Subject: [PATCH 024/131] chore(release): 6.1.3-1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3172963..17791c5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@adonisjs/assembler", - "version": "6.1.3-0", + "version": "6.1.3-1", "description": "Provides utilities to run AdonisJS development server and build project for production", "main": "build/index.js", "type": "module", From 857d51fe51a3c57eb5b798804e1d46e160e649fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Sun, 12 Mar 2023 17:11:20 +0100 Subject: [PATCH 025/131] refactor: use builtin `fs/promises` instead of `fs-extra` (#58) --- package.json | 2 -- src/bundler.ts | 14 ++++++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 17791c5..ebdfdcd 100644 --- a/package.json +++ b/package.json @@ -63,11 +63,9 @@ "@adonisjs/env": "^4.2.0-0", "@poppinss/chokidar-ts": "^4.1.0-1", "@poppinss/cliui": "^6.1.1-1", - "@types/fs-extra": "^11.0.1", "@types/picomatch": "^2.3.0", "cpy": "^9.0.1", "execa": "^7.0.0", - "fs-extra": "^11.1.0", "get-port": "^6.1.2", "picomatch": "^2.3.1", "slash": "^5.0.0" diff --git a/src/bundler.ts b/src/bundler.ts index e8026b8..23f8206 100644 --- a/src/bundler.ts +++ b/src/bundler.ts @@ -7,10 +7,10 @@ * file that was distributed with this source code. */ -import fs from 'fs-extra' import slash from 'slash' import copyfiles from 'cpy' import type tsStatic from 'typescript' +import fs from 'node:fs/promises' import { fileURLToPath } from 'node:url' import { join, relative } from 'node:path' import { cliui, type Logger } from '@poppinss/cliui' @@ -56,7 +56,7 @@ export class Bundler { * Cleans up the build directory */ async #cleanupBuildDirectory(outDir: string) { - await fs.remove(outDir) + await fs.rm(outDir, { recursive: true, force: true, maxRetries: 5 }) } /** @@ -125,13 +125,19 @@ export class Bundler { * Copies .adonisrc.json file to the destination */ async #copyAdonisRcFile(outDir: string) { - const existingContents = await fs.readJSON(join(this.#cwdPath, '.adonisrc.json')) + const existingContents = JSON.parse( + await fs.readFile(join(this.#cwdPath, '.adonisrc.json'), 'utf-8') + ) const compiledContents = Object.assign({}, existingContents, { typescript: false, lastCompiledAt: new Date().toISOString(), }) - await fs.outputJSON(join(outDir, '.adonisrc.json'), compiledContents, { spaces: 2 }) + await fs.mkdir(outDir, { recursive: true }) + await fs.writeFile( + join(outDir, '.adonisrc.json'), + JSON.stringify(compiledContents, null, 2) + '\n' + ) } /** From 08001e6331a709043743ffec7ceb7d28409523c1 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Wed, 15 Mar 2023 10:00:53 +0530 Subject: [PATCH 026/131] chore(release): 6.1.3-2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ebdfdcd..7b35331 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@adonisjs/assembler", - "version": "6.1.3-1", + "version": "6.1.3-2", "description": "Provides utilities to run AdonisJS development server and build project for production", "main": "build/index.js", "type": "module", From 4254dda3af849943470ea8264aa18ed511d84d28 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Wed, 15 Mar 2023 10:07:53 +0530 Subject: [PATCH 027/131] chore: force update @adonisjs/env package --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7b35331..e044dc9 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "typescript": "^4.9.5" }, "dependencies": { - "@adonisjs/env": "^4.2.0-0", + "@adonisjs/env": "^4.2.0-1", "@poppinss/chokidar-ts": "^4.1.0-1", "@poppinss/cliui": "^6.1.1-1", "@types/picomatch": "^2.3.0", From 912adab739e43b579d235e072f2bfe56beec1282 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Wed, 15 Mar 2023 10:09:46 +0530 Subject: [PATCH 028/131] chore(release): 6.1.3-3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e044dc9..c49d4b4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@adonisjs/assembler", - "version": "6.1.3-2", + "version": "6.1.3-3", "description": "Provides utilities to run AdonisJS development server and build project for production", "main": "build/index.js", "type": "module", From f41eb9f2d7995fc9eceb6382b56a544fee459c75 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Mon, 27 Mar 2023 14:49:48 +0530 Subject: [PATCH 029/131] chore: update dependencies --- package.json | 20 ++++++++++---------- src/bundler.ts | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index c49d4b4..e36f15c 100644 --- a/package.json +++ b/package.json @@ -35,34 +35,34 @@ "author": "virk,adonisjs", "license": "MIT", "devDependencies": { - "@commitlint/cli": "^17.4.4", + "@commitlint/cli": "^17.5.0", "@commitlint/config-conventional": "^17.4.4", "@japa/assert": "^1.4.1", "@japa/file-system": "^1.0.1", "@japa/run-failed-tests": "^1.1.1", "@japa/runner": "^2.5.1", "@japa/spec-reporter": "^1.3.3", - "@swc/core": "^1.3.39", - "@types/node": "^18.15.0", + "@swc/core": "^1.3.42", + "@types/node": "^18.15.10", "c8": "^7.13.0", "cross-env": "^7.0.3", "del-cli": "^5.0.0", "eslint": "^8.36.0", - "eslint-config-prettier": "^8.7.0", + "eslint-config-prettier": "^8.8.0", "eslint-plugin-adonis": "^3.0.3", "eslint-plugin-prettier": "^4.2.1", "github-label-sync": "^2.3.1", "husky": "^8.0.3", - "np": "^7.6.3", + "np": "^7.6.4", "p-event": "^5.0.1", - "prettier": "^2.8.4", + "prettier": "^2.8.7", "ts-node": "^10.9.1", - "typescript": "^4.9.5" + "typescript": "^5.0.2" }, "dependencies": { - "@adonisjs/env": "^4.2.0-1", - "@poppinss/chokidar-ts": "^4.1.0-1", - "@poppinss/cliui": "^6.1.1-1", + "@adonisjs/env": "^4.2.0-2", + "@poppinss/chokidar-ts": "^4.1.0-2", + "@poppinss/cliui": "^6.1.1-2", "@types/picomatch": "^2.3.0", "cpy": "^9.0.1", "execa": "^7.0.0", diff --git a/src/bundler.ts b/src/bundler.ts index 23f8206..653f6dd 100644 --- a/src/bundler.ts +++ b/src/bundler.ts @@ -9,8 +9,8 @@ import slash from 'slash' import copyfiles from 'cpy' -import type tsStatic from 'typescript' import fs from 'node:fs/promises' +import type tsStatic from 'typescript' import { fileURLToPath } from 'node:url' import { join, relative } from 'node:path' import { cliui, type Logger } from '@poppinss/cliui' From 1618a9ba8da8dc80fa9a93976510c21d7cbeb082 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Mon, 27 Mar 2023 14:52:07 +0530 Subject: [PATCH 030/131] chore: publish source and generate delcaration map --- package.json | 3 +++ tsconfig.json | 1 + 2 files changed, 4 insertions(+) diff --git a/package.json b/package.json index e36f15c..335e335 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,11 @@ "main": "build/index.js", "type": "module", "files": [ + "src", + "index.ts", "build/src", "build/index.d.ts", + "build/index.d.ts.map", "build/index.js" ], "exports": { diff --git a/tsconfig.json b/tsconfig.json index 0604247..55296b4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,6 +19,7 @@ "strictBindCallApply": true, "strictFunctionTypes": true, "noImplicitThis": true, + "declarationMap": true, "skipLibCheck": true, "types": ["@types/node"] }, From ebdf9734ea69e31622500c899a80df0d19616fbf Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Mon, 27 Mar 2023 14:57:32 +0530 Subject: [PATCH 031/131] docs(README): fix snyk label name --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 52c2286..2bbe91c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@
-[![gh-workflow-image]][gh-workflow-url] [![npm-image]][npm-url] ![][typescript-image] [![license-image]][license-url] [![synk-image]][synk-url] +[![gh-workflow-image]][gh-workflow-url] [![npm-image]][npm-url] ![][typescript-image] [![license-image]][license-url] [![snyk-image]][snyk-url] ## Introduction Assembler exports the API for starting the **AdonisJS development server**, **building project for production** and **running tests** in watch mode. Assembler must be used during development only. @@ -32,5 +32,5 @@ AdonisJS Assembler is open-sourced software licensed under the [MIT license](LIC [license-url]: LICENSE.md [license-image]: https://img.shields.io/github/license/adonisjs/ace?style=for-the-badge -[synk-image]: https://img.shields.io/snyk/vulnerabilities/github/adonisjs/assembler?label=Synk%20Vulnerabilities&style=for-the-badge -[synk-url]: https://snyk.io/test/github/adonisjs/assembler?targetFile=package.json "synk" +[snyk-image]: https://img.shields.io/snyk/vulnerabilities/github/adonisjs/assembler?label=snyk%20Vulnerabilities&style=for-the-badge +[snyk-url]: https://snyk.io/test/github/adonisjs/assembler?targetFile=package.json "snyk" From a08a3c1ac202c1d11919dda1aeba737a8a200449 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Mon, 27 Mar 2023 14:57:42 +0530 Subject: [PATCH 032/131] docs: update License file --- LICENSE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE.md b/LICENSE.md index 59a3cfa..381426b 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ # The MIT License -Copyright (c) 2023 AdonisJS Framework +Copyright (c) 2023 Harminder Virk Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: From 798a75da672f83874abe6a5137408c49f01e9824 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Mon, 27 Mar 2023 15:00:47 +0530 Subject: [PATCH 033/131] chore(release): 6.1.3-4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 335e335..4e0e9f8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@adonisjs/assembler", - "version": "6.1.3-3", + "version": "6.1.3-4", "description": "Provides utilities to run AdonisJS development server and build project for production", "main": "build/index.js", "type": "module", From c2fec3d74fb034c2cd5936f714985c83ed09fb3f Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Sat, 15 Apr 2023 11:48:58 +0530 Subject: [PATCH 034/131] chore: update dependencies --- package.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 4e0e9f8..b61521a 100644 --- a/package.json +++ b/package.json @@ -38,29 +38,29 @@ "author": "virk,adonisjs", "license": "MIT", "devDependencies": { - "@commitlint/cli": "^17.5.0", - "@commitlint/config-conventional": "^17.4.4", + "@commitlint/cli": "^17.6.1", + "@commitlint/config-conventional": "^17.6.1", "@japa/assert": "^1.4.1", "@japa/file-system": "^1.0.1", "@japa/run-failed-tests": "^1.1.1", "@japa/runner": "^2.5.1", "@japa/spec-reporter": "^1.3.3", - "@swc/core": "^1.3.42", - "@types/node": "^18.15.10", + "@swc/core": "^1.3.50", + "@types/node": "^18.15.11", "c8": "^7.13.0", "cross-env": "^7.0.3", "del-cli": "^5.0.0", - "eslint": "^8.36.0", + "eslint": "^8.38.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-adonis": "^3.0.3", "eslint-plugin-prettier": "^4.2.1", "github-label-sync": "^2.3.1", "husky": "^8.0.3", - "np": "^7.6.4", + "np": "^7.7.0", "p-event": "^5.0.1", "prettier": "^2.8.7", "ts-node": "^10.9.1", - "typescript": "^5.0.2" + "typescript": "^5.0.4" }, "dependencies": { "@adonisjs/env": "^4.2.0-2", From f0c28961cbe1af78a409842da5a11abd49548709 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Sat, 15 Apr 2023 16:15:36 +0530 Subject: [PATCH 035/131] feat: add TestRunner service --- index.ts | 1 + src/assets_dev_server.ts | 182 +++++++++++++++++++++ src/bundler.ts | 3 +- src/dev_server.ts | 316 +++++++++--------------------------- src/helpers.ts | 162 +++++++++++++++++++ src/parse_config.ts | 32 ---- src/run.ts | 65 -------- src/test_runner.ts | 338 +++++++++++++++++++++++++++++++++++++++ src/types.ts | 41 +++++ src/watch.ts | 29 ---- tests/run.spec.ts | 2 +- tests/watch.spec.ts | 2 +- 12 files changed, 803 insertions(+), 370 deletions(-) create mode 100644 src/assets_dev_server.ts create mode 100644 src/helpers.ts delete mode 100644 src/parse_config.ts delete mode 100644 src/run.ts create mode 100644 src/test_runner.ts delete mode 100644 src/watch.ts diff --git a/index.ts b/index.ts index c255cae..47b5f4d 100644 --- a/index.ts +++ b/index.ts @@ -9,3 +9,4 @@ export { Bundler } from './src/bundler.js' export { DevServer } from './src/dev_server.js' +export { TestRunner } from './src/test_runner.js' diff --git a/src/assets_dev_server.ts b/src/assets_dev_server.ts new file mode 100644 index 0000000..3131882 --- /dev/null +++ b/src/assets_dev_server.ts @@ -0,0 +1,182 @@ +/* + * @adonisjs/core + * + * (c) AdonisJS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import type { ExecaChildProcess } from 'execa' +import { type Logger, cliui } from '@poppinss/cliui' + +import { run } from './helpers.js' +import type { AssetsBundlerOptions } from './types.js' + +/** + * Instance of CLIUI + */ +const ui = cliui() + +/** + * Exposes the API to start the development server for processing assets during + * development. + * + * - Here we are running the assets dev server in a child process. + * - Piping the output from the child process and reformatting it before writing it to + * process streams. + * + * AssetsDevServer is agnostic and can run any assets dev server. Be it Vite or Encore or + * even Webpack directly. + */ +export class AssetsDevServer { + #cwd: URL + #logger = ui.logger + #options?: AssetsBundlerOptions + #devServer?: ExecaChildProcess + + /** + * Getting reference to colors library from logger + */ + get #colors() { + return this.#logger.getColors() + } + + constructor(cwd: URL, options?: AssetsBundlerOptions) { + this.#cwd = cwd + this.#options = options + } + + /** + * Logs messages from vite dev server stdout and stderr + */ + #logViteDevServerMessage(data: Buffer) { + const dataString = data.toString() + const lines = dataString.split('\n') + + /** + * Logging VITE ready in message with proper + * spaces and newlines + */ + if (dataString.includes('ready in')) { + console.log('') + console.log(dataString.trim()) + return + } + + /** + * Put a wrapper around vite network address log + */ + if (dataString.includes('Local') && dataString.includes('Network')) { + const sticker = ui.sticker().useColors(this.#colors).useRenderer(this.#logger.getRenderer()) + + lines.forEach((line: string) => { + if (line.trim()) { + sticker.add(line) + } + }) + + sticker.render() + return + } + + /** + * Log rest of the lines + */ + lines.forEach((line: string) => { + if (line.trim()) { + console.log(line) + } + }) + } + + /** + * Logs messages from assets dev server stdout and stderr + */ + #logAssetsDevServerMessage(data: Buffer) { + const dataString = data.toString() + const lines = dataString.split('\n') + lines.forEach((line: string) => { + if (line.trim()) { + console.log(line) + } + }) + } + + /** + * Set a custom CLI UI logger + */ + setLogger(logger: Logger) { + this.#logger = logger + return this + } + + /** + * Starts the assets bundler server. The assets bundler server process is + * considered as the secondary process and therefore we do not perform + * any cleanup if it dies. + */ + start() { + if (!this.#options?.serve) { + return + } + + this.#logger.info(`starting "${this.#options.driver}" dev server...`) + + /** + * Create child process + */ + this.#devServer = run(this.#cwd, { + script: this.#options.cmd, + + /** + * We do not inherit the stdio for vite and encore, because in + * inherit mode they own the stdin and interrupts the + * `Ctrl + C` command. + */ + stdio: 'pipe', + scriptArgs: this.#options.args, + }) + + /** + * Log child process messages + */ + this.#devServer.stdout?.on('data', (data) => { + if (this.#options!.driver === 'vite') { + this.#logViteDevServerMessage(data) + } else { + this.#logAssetsDevServerMessage(data) + } + }) + + this.#devServer.stderr?.on('data', (data) => { + if (this.#options!.driver === 'vite') { + this.#logViteDevServerMessage(data) + } else { + this.#logAssetsDevServerMessage(data) + } + }) + + this.#devServer + .then((result) => { + this.#logger.warning( + `"${this.#options!.driver}" dev server closed with status code "${result.exitCode}"` + ) + }) + .catch((error) => { + this.#logger.warning(`unable to connect to "${this.#options!.driver}" dev server`) + this.#logger.fatal(error) + }) + } + + /** + * Stop the dev server + */ + stop() { + if (this.#devServer) { + this.#devServer.removeAllListeners() + this.#devServer.kill('SIGKILL') + this.#devServer = undefined + } + } +} diff --git a/src/bundler.ts b/src/bundler.ts index 653f6dd..a4116e9 100644 --- a/src/bundler.ts +++ b/src/bundler.ts @@ -15,8 +15,7 @@ import { fileURLToPath } from 'node:url' import { join, relative } from 'node:path' import { cliui, type Logger } from '@poppinss/cliui' -import { run } from './run.js' -import { parseConfig } from './parse_config.js' +import { run, parseConfig } from './helpers.js' import type { BundlerOptions } from './types.js' /** diff --git a/src/dev_server.ts b/src/dev_server.ts index 631cb88..f9712d5 100644 --- a/src/dev_server.ts +++ b/src/dev_server.ts @@ -7,17 +7,15 @@ * file that was distributed with this source code. */ -import getPort from 'get-port' import picomatch from 'picomatch' import type tsStatic from 'typescript' import { type ExecaChildProcess } from 'execa' -import type { Watcher } from '@poppinss/chokidar-ts' import { cliui, type Logger } from '@poppinss/cliui' -import { EnvLoader, EnvParser } from '@adonisjs/env' +import type { Watcher } from '@poppinss/chokidar-ts' -import { watch } from './watch.js' -import { run, runNode } from './run.js' import type { DevServerOptions } from './types.js' +import { AssetsDevServer } from './assets_dev_server.js' +import { getPort, isDotEnvFile, isRcFile, runNode, watch } from './helpers.js' /** * Instance of CLIUI @@ -41,14 +39,16 @@ export class DevServer { #options: DevServerOptions #isWatching: boolean = false #scriptFile: string = 'bin/server.js' - #httpServerProcess?: ExecaChildProcess #isMetaFileWithReloadsEnabled: picomatch.Matcher #isMetaFileWithReloadsDisabled: picomatch.Matcher - #watcher?: ReturnType - #assetsServerProcess?: ExecaChildProcess + #onError?: (error: any) => any #onClose?: (exitCode: number) => any + #httpServer?: ExecaChildProcess + #watcher?: ReturnType + #assetsServer?: AssetsDevServer + /** * Getting reference to colors library from logger */ @@ -73,24 +73,6 @@ export class DevServer { ) } - /** - * Check if file is an .env file - */ - #isDotEnvFile(filePath: string) { - if (filePath === '.env') { - return true - } - - return filePath.includes('.env.') - } - - /** - * Check if file is .adonisrc.json file - */ - #isRcFile(filePath: string) { - return filePath === '.adonisrc.json' - } - /** * Inspect if child process message is from AdonisJS HTTP server */ @@ -115,97 +97,18 @@ export class DevServer { } } - /** - * Logs messages from vite dev server stdout and stderr - */ - #logViteDevServerMessage(data: Buffer) { - const dataString = data.toString() - const lines = dataString.split('\n') - - /** - * Logging VITE ready in message with proper - * spaces and newlines - */ - if (dataString.includes('ready in')) { - console.log('') - console.log(dataString.trim()) - return - } - - /** - * Put a wrapper around vite network address log - */ - if (dataString.includes('Local') && dataString.includes('Network')) { - const sticker = ui.sticker().useColors(this.#colors).useRenderer(this.#logger.getRenderer()) - - lines.forEach((line: string) => { - if (line.trim()) { - sticker.add(line) - } - }) - - sticker.render() - return - } - - /** - * Log rest of the lines - */ - lines.forEach((line: string) => { - if (line.trim()) { - console.log(line) - } - }) - } - - /** - * Logs messages from assets dev server stdout and stderr - */ - #logAssetsDevServerMessage(data: Buffer) { - const dataString = data.toString() - const lines = dataString.split('\n') - lines.forEach((line: string) => { - if (line.trim()) { - console.log(line) - } - }) - } - - /** - * Returns PORT for starting the HTTP server with option to use - * a random PORT if main PORT is in use. - */ - async #getPort(): Promise { - /** - * Use existing port if exists - */ - if (process.env.PORT) { - return getPort({ port: Number(process.env.PORT) }) - } - - const files = await new EnvLoader(this.#cwd).load() - for (let file of files) { - const envVariables = new EnvParser(file.contents).parse() - if (envVariables.PORT) { - return getPort({ port: Number(envVariables.PORT) }) - } - } - - return getPort({ port: 3333 }) - } - /** * Starts the HTTP server */ #startHTTPServer(port: string, mode: 'blocking' | 'nonblocking') { - this.#httpServerProcess = runNode(this.#cwd, { + this.#httpServer = runNode(this.#cwd, { script: this.#scriptFile, env: { PORT: port, ...this.#options.env }, nodeArgs: this.#options.nodeArgs, scriptArgs: this.#options.scriptArgs, }) - this.#httpServerProcess.on('message', (message) => { + this.#httpServer.on('message', (message) => { if (this.#isAdonisJSReadyMessage(message)) { ui.sticker() .useColors(this.#colors) @@ -220,12 +123,13 @@ export class DevServer { } }) - this.#httpServerProcess + this.#httpServer .then((result) => { this.#logger.warning(`underlying HTTP server closed with status code "${result.exitCode}"`) if (mode === 'nonblocking') { this.#onClose?.(result.exitCode) this.#watcher?.close() + this.#assetsServer?.stop() } }) .catch((error) => { @@ -233,73 +137,61 @@ export class DevServer { this.#logger.fatal(error) this.#onError?.(error) this.#watcher?.close() + this.#assetsServer?.stop() }) } /** - * Starts the assets bundler server. The assets bundler server process is - * considered as the secondary process and therefore we do not perform - * any cleanup if it dies. + * Starts the assets server */ #startAssetsServer() { - const assetsBundler = this.#options.assets - if (!assetsBundler?.serve) { - return - } + this.#assetsServer = new AssetsDevServer(this.#cwd, this.#options.assets) + this.#assetsServer.start() + } - this.#logger.info(`starting "${assetsBundler.driver}" dev server...`) - this.#assetsServerProcess = run(this.#cwd, { - script: assetsBundler.cmd, + /** + * Restarts the HTTP server + */ + #restartHTTPServer(port: string) { + if (this.#httpServer) { + this.#httpServer.removeAllListeners() + this.#httpServer.kill('SIGKILL') + } - /** - * We do not inherit the stdio for vite and encore, because they then - * own the stdin and interrupts the `Ctrl + C`. - */ - stdio: 'pipe', - scriptArgs: this.#options.scriptArgs, - }) + this.#startHTTPServer(port, 'blocking') + } - /** - * Log child process messages - */ - this.#assetsServerProcess.stdout?.on('data', (data) => { - if (assetsBundler.driver === 'vite') { - this.#logViteDevServerMessage(data) - } else { - this.#logAssetsDevServerMessage(data) - } - }) + /** + * Handles a non TypeScript file change + */ + #handleFileChange(action: string, port: string, relativePath: string) { + if (isDotEnvFile(relativePath) || isRcFile(relativePath)) { + this.#clearScreen() + this.#logger.log(`${this.#colors.green(action)} ${relativePath}`) + this.#restartHTTPServer(port) + return + } - this.#assetsServerProcess.stderr?.on('data', (data) => { - if (assetsBundler.driver === 'vite') { - this.#logViteDevServerMessage(data) - } else { - this.#logAssetsDevServerMessage(data) - } - }) + if (this.#isMetaFileWithReloadsEnabled(relativePath)) { + this.#clearScreen() + this.#logger.log(`${this.#colors.green(action)} ${relativePath}`) + this.#restartHTTPServer(port) + return + } - this.#assetsServerProcess - .then((result) => { - this.#logger.warning( - `"${assetsBundler.driver}" dev server closed with status code "${result.exitCode}"` - ) - }) - .catch((error) => { - this.#logger.warning(`unable to connect to "${assetsBundler.driver}" dev server`) - this.#logger.fatal(error) - }) + if (this.#isMetaFileWithReloadsDisabled(relativePath)) { + this.#clearScreen() + this.#logger.log(`${this.#colors.green(action)} ${relativePath}`) + } } /** - * Restart the development server + * Handles TypeScript source file change */ - #restart(port: string) { - if (this.#httpServerProcess) { - this.#httpServerProcess.removeAllListeners() - this.#httpServerProcess.kill('SIGKILL') - } - - this.#startHTTPServer(port, 'blocking') + #handleSourceFileChange(action: string, port: string, relativePath: string) { + this.#clearScreen() + this.#logger.log(`${this.#colors.green(action)} ${relativePath}`) + this.#restartHTTPServer(port) } /** @@ -307,6 +199,7 @@ export class DevServer { */ setLogger(logger: Logger) { this.#logger = logger + this.#assetsServer?.setLogger(logger) return this } @@ -334,8 +227,7 @@ export class DevServer { async start() { this.#clearScreen() this.#logger.info('starting HTTP server...') - this.#startHTTPServer(String(await this.#getPort()), 'nonblocking') - + this.#startHTTPServer(String(await getPort(this.#cwd)), 'nonblocking') this.#startAssetsServer() } @@ -343,13 +235,14 @@ export class DevServer { * Start the development server in watch mode */ async startAndWatch(ts: typeof tsStatic, options?: { poll: boolean }) { - const port = String(await this.#getPort()) + const port = String(await getPort(this.#cwd)) this.#isWatching = true this.#clearScreen() - this.#logger.info('starting HTTP server...') + this.#logger.info('starting HTTP server...') this.#startHTTPServer(port, 'blocking') + this.#startAssetsServer() /** @@ -387,84 +280,27 @@ export class DevServer { /** * Changes in TypeScript source file */ - output.watcher.on('source:add', ({ relativePath }) => { - this.#clearScreen() - this.#logger.log(`${this.#colors.green('add')} ${relativePath}`) - this.#restart(port) - }) - output.watcher.on('source:change', ({ relativePath }) => { - this.#clearScreen() - this.#logger.log(`${this.#colors.green('update')} ${relativePath}`) - this.#restart(port) - }) - output.watcher.on('source:unlink', ({ relativePath }) => { - this.#clearScreen() - this.#logger.log(`${this.#colors.green('delete')} ${relativePath}`) - this.#restart(port) - }) + output.watcher.on('source:add', ({ relativePath }) => + this.#handleSourceFileChange('add', port, relativePath) + ) + output.watcher.on('source:change', ({ relativePath }) => + this.#handleSourceFileChange('update', port, relativePath) + ) + output.watcher.on('source:unlink', ({ relativePath }) => + this.#handleSourceFileChange('delete', port, relativePath) + ) /** - * Changes in other files + * Changes in non-TypeScript source files */ - output.watcher.on('add', ({ relativePath }) => { - if (this.#isDotEnvFile(relativePath) || this.#isRcFile(relativePath)) { - this.#clearScreen() - this.#logger.log(`${this.#colors.green('add')} ${relativePath}`) - this.#restart(port) - return - } - - if (this.#isMetaFileWithReloadsEnabled(relativePath)) { - this.#clearScreen() - this.#logger.log(`${this.#colors.green('add')} ${relativePath}`) - this.#restart(port) - return - } - - if (this.#isMetaFileWithReloadsDisabled(relativePath)) { - this.#clearScreen() - this.#logger.log(`${this.#colors.green('add')} ${relativePath}`) - } - }) - output.watcher.on('change', ({ relativePath }) => { - if (this.#isDotEnvFile(relativePath) || this.#isRcFile(relativePath)) { - this.#clearScreen() - this.#logger.log(`${this.#colors.green('update')} ${relativePath}`) - this.#restart(port) - return - } - - if (this.#isMetaFileWithReloadsEnabled(relativePath)) { - this.#clearScreen() - this.#logger.log(`${this.#colors.green('update')} ${relativePath}`) - this.#restart(port) - return - } - - if (this.#isMetaFileWithReloadsDisabled(relativePath)) { - this.#clearScreen() - this.#logger.log(`${this.#colors.green('update')} ${relativePath}`) - } - }) - output.watcher.on('unlink', ({ relativePath }) => { - if (this.#isDotEnvFile(relativePath) || this.#isRcFile(relativePath)) { - this.#clearScreen() - this.#logger.log(`${this.#colors.green('delete')} ${relativePath}`) - this.#restart(port) - return - } - - if (this.#isMetaFileWithReloadsEnabled(relativePath)) { - this.#clearScreen() - this.#logger.log(`${this.#colors.green('delete')} ${relativePath}`) - this.#restart(port) - return - } - - if (this.#isMetaFileWithReloadsDisabled(relativePath)) { - this.#clearScreen() - this.#logger.log(`${this.#colors.green('delete')} ${relativePath}`) - } - }) + output.watcher.on('add', ({ relativePath }) => + this.#handleFileChange('add', port, relativePath) + ) + output.watcher.on('change', ({ relativePath }) => + this.#handleFileChange('update', port, relativePath) + ) + output.watcher.on('unlink', ({ relativePath }) => + this.#handleFileChange('delete', port, relativePath) + ) } } diff --git a/src/helpers.ts b/src/helpers.ts new file mode 100644 index 0000000..2b76759 --- /dev/null +++ b/src/helpers.ts @@ -0,0 +1,162 @@ +/* + * @adonisjs/assembler + * + * (c) AdonisJS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import getRandomPort from 'get-port' +import type tsStatic from 'typescript' +import { fileURLToPath } from 'node:url' +import { execaNode, execa } from 'execa' +import { EnvLoader, EnvParser } from '@adonisjs/env' +import { ConfigParser, Watcher } from '@poppinss/chokidar-ts' + +import type { RunOptions, WatchOptions } from './types.js' + +/** + * Default set of args to pass in order to run TypeScript + * source. Used by "run" and "runNode" scripts + */ +const DEFAULT_NODE_ARGS = [ + // Use ts-node/esm loader. The project must install it + '--loader=ts-node/esm', + // Disable annonying warnings + '--no-warnings', + // Enable expiremental meta resolve for cases where someone uses magic import string + '--experimental-import-meta-resolve', +] + +/** + * Parses tsconfig.json and prints errors using typescript compiler + * host + */ +export function parseConfig(cwd: string | URL, ts: typeof tsStatic) { + const { config, error } = new ConfigParser(cwd, 'tsconfig.json', ts).parse() + if (error) { + const compilerHost = ts.createCompilerHost({}) + console.log(ts.formatDiagnosticsWithColorAndContext([error], compilerHost)) + return + } + + if (config!.errors.length) { + const compilerHost = ts.createCompilerHost({}) + console.log(ts.formatDiagnosticsWithColorAndContext(config!.errors, compilerHost)) + return + } + + return config +} + +/** + * Runs a Node.js script as a child process and inherits the stdio streams + */ +export function runNode(cwd: string | URL, options: RunOptions) { + const childProcess = execaNode(options.script, options.scriptArgs, { + nodeOptions: DEFAULT_NODE_ARGS.concat(options.nodeArgs), + preferLocal: true, + windowsHide: false, + localDir: cwd, + cwd, + buffer: false, + stdio: options.stdio || 'inherit', + env: { + ...(options.stdio === 'pipe' ? { FORCE_COLOR: 'true' } : {}), + ...options.env, + }, + }) + + return childProcess +} + +/** + * Runs a script as a child process and inherits the stdio streams + */ +export function run(cwd: string | URL, options: Omit) { + const childProcess = execa(options.script, options.scriptArgs, { + preferLocal: true, + windowsHide: false, + localDir: cwd, + cwd, + buffer: false, + stdio: options.stdio || 'inherit', + env: { + ...(options.stdio === 'pipe' ? { FORCE_COLOR: 'true' } : {}), + ...options.env, + }, + }) + + return childProcess +} + +/** + * Watches the file system using tsconfig file + */ +export function watch(cwd: string | URL, ts: typeof tsStatic, options: WatchOptions) { + const config = parseConfig(cwd, ts) + if (!config) { + return + } + + const watcher = new Watcher(typeof cwd === 'string' ? cwd : fileURLToPath(cwd), config!) + const chokidar = watcher.watch(['.'], { usePolling: options.poll }) + return { watcher, chokidar } +} + +/** + * Check if file is an .env file + */ +export function isDotEnvFile(filePath: string) { + if (filePath === '.env') { + return true + } + + return filePath.includes('.env.') +} + +/** + * Check if file is .adonisrc.json file + */ +export function isRcFile(filePath: string) { + return filePath === '.adonisrc.json' +} + +/** + * Returns the port to use after inspect the dot-env files inside + * a given directory. + * + * A random port is used when the specified port is in use. Following + * is the logic for finding a specified port. + * + * - The "process.env.PORT" value is used if exists. + * - The dot-env files are loaded using the "EnvLoader" and the PORT + * value is by iterating over all the loaded files. The iteration + * stops after first find. + */ +export async function getPort(cwd: URL): Promise { + /** + * Use existing port if exists + */ + if (process.env.PORT) { + return getRandomPort({ port: Number(process.env.PORT) }) + } + + /** + * Loop over files and use the port from their contents. Stops + * after first match + */ + const files = await new EnvLoader(cwd).load() + for (let file of files) { + const envVariables = new EnvParser(file.contents).parse() + if (envVariables.PORT) { + return getRandomPort({ port: Number(envVariables.PORT) }) + } + } + + /** + * Use 3333 as the port + */ + return getRandomPort({ port: 3333 }) +} diff --git a/src/parse_config.ts b/src/parse_config.ts deleted file mode 100644 index 31afe14..0000000 --- a/src/parse_config.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) AdonisJS - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import type tsStatic from 'typescript' -import { ConfigParser } from '@poppinss/chokidar-ts' - -/** - * Parses tsconfig.json and prints errors using typescript compiler - * host - */ -export function parseConfig(cwd: string | URL, ts: typeof tsStatic) { - const { config, error } = new ConfigParser(cwd, 'tsconfig.json', ts).parse() - if (error) { - const compilerHost = ts.createCompilerHost({}) - console.log(ts.formatDiagnosticsWithColorAndContext([error], compilerHost)) - return - } - - if (config!.errors.length) { - const compilerHost = ts.createCompilerHost({}) - console.log(ts.formatDiagnosticsWithColorAndContext(config!.errors, compilerHost)) - return - } - - return config -} diff --git a/src/run.ts b/src/run.ts deleted file mode 100644 index db658d2..0000000 --- a/src/run.ts +++ /dev/null @@ -1,65 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) AdonisJS - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { execaNode, execa } from 'execa' -import type { RunOptions } from './types.js' - -/** - * Default set of args to pass in order to run TypeScript - * source - */ -const DEFAULT_NODE_ARGS = [ - // Use ts-node/esm loader. The project must install it - '--loader=ts-node/esm', - // Disable annonying warnings - '--no-warnings', - // Enable expiremental meta resolve for cases where someone uses magic import string - '--experimental-import-meta-resolve', -] - -/** - * Runs a Node.js script as a child process and inherits the stdio streams - */ -export function runNode(cwd: string | URL, options: RunOptions) { - const childProcess = execaNode(options.script, options.scriptArgs, { - nodeOptions: DEFAULT_NODE_ARGS.concat(options.nodeArgs), - preferLocal: true, - windowsHide: false, - localDir: cwd, - cwd, - buffer: false, - stdio: options.stdio || 'inherit', - env: { - ...(options.stdio === 'pipe' ? { FORCE_COLOR: 'true' } : {}), - ...options.env, - }, - }) - - return childProcess -} - -/** - * Runs a script as a child process and inherits the stdio streams - */ -export function run(cwd: string | URL, options: Omit) { - const childProcess = execa(options.script, options.scriptArgs, { - preferLocal: true, - windowsHide: false, - localDir: cwd, - cwd, - buffer: false, - stdio: options.stdio || 'inherit', - env: { - ...(options.stdio === 'pipe' ? { FORCE_COLOR: 'true' } : {}), - ...options.env, - }, - }) - - return childProcess -} diff --git a/src/test_runner.ts b/src/test_runner.ts new file mode 100644 index 0000000..642c42d --- /dev/null +++ b/src/test_runner.ts @@ -0,0 +1,338 @@ +/* + * @adonisjs/assembler + * + * (c) AdonisJS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import picomatch from 'picomatch' +import type tsStatic from 'typescript' +import { type ExecaChildProcess } from 'execa' +import { cliui, type Logger } from '@poppinss/cliui' +import type { Watcher } from '@poppinss/chokidar-ts' + +import type { TestRunnerOptions } from './types.js' +import { AssetsDevServer } from './assets_dev_server.js' +import { getPort, isDotEnvFile, runNode, watch } from './helpers.js' + +/** + * Instance of CLIUI + */ +const ui = cliui() + +/** + * Exposes the API to start the development. Optionally, the watch API can be + * used to watch for file changes and restart the development server. + * + * The Dev server performs the following actions + * + * - Assigns a random PORT, when PORT inside .env file is in use + * - Uses tsconfig.json file to collect a list of files to watch. + * - Uses metaFiles from .adonisrc.json file to collect a list of files to watch. + * - Restart HTTP server on every file change. + */ +export class TestRunner { + #cwd: URL + #logger = ui.logger + #options: TestRunnerOptions + #scriptFile: string = 'bin/test.js' + #isMetaFile: picomatch.Matcher + #isTestFile: picomatch.Matcher + + /** + * In watch mode, after a file is changed, we wait for the current + * set of tests to finish before triggering a re-run. Therefore, + * we use this flag to know if we are already busy in running + * tests and ignore file-changes. + */ + #isBusy: boolean = false + + #onError?: (error: any) => any + #onClose?: (exitCode: number) => any + + #testScript?: ExecaChildProcess + #watcher?: ReturnType + #assetsServer?: AssetsDevServer + + /** + * Getting reference to colors library from logger + */ + get #colors() { + return this.#logger.getColors() + } + + constructor(cwd: URL, options: TestRunnerOptions) { + this.#cwd = cwd + this.#options = options + this.#isMetaFile = picomatch((this.#options.metaFiles || []).map(({ pattern }) => pattern)) + this.#isTestFile = picomatch( + this.#options.suites + .filter((suite) => { + if (this.#options.filters.suites) { + this.#options.filters.suites.includes(suite.name) + } + + return true + }) + .map((suite) => suite.files) + .flat(1) + ) + } + + /** + * Converts all known filters to CLI args. + * + * The following code snippet may seem like repetitive code. But, it + * is done intentionally to have visibility around how each filter + * is converted to an arg. + */ + #convertFiltersToArgs(filters: TestRunnerOptions['filters']): string[] { + const args: string[] = [] + + if (filters.suites) { + args.push(...filters.suites) + } + + if (filters.files) { + args.push('--files') + args.push(filters.files.join(',')) + } + + if (filters.groups) { + args.push('--groups') + args.push(filters.groups.join(',')) + } + + if (filters.tags) { + args.push('--tags') + args.push(filters.tags.join(',')) + } + + if (filters.ignoreTags) { + args.push('--ignore-tags') + args.push(filters.ignoreTags.join(',')) + } + + if (filters.tests) { + args.push('--ignore-tests') + args.push(filters.tests.join(',')) + } + + if (filters.match) { + args.push('--match') + args.push(filters.match.join(',')) + } + + return args + } + + /** + * Conditionally clear the terminal screen + */ + #clearScreen() { + if (this.#options.clearScreen) { + process.stdout.write('\u001Bc') + } + } + + /** + * Runs tests + */ + #runTests(port: string, filtersArgs: string[], mode: 'blocking' | 'nonblocking') { + this.#isBusy = true + + this.#testScript = runNode(this.#cwd, { + script: this.#scriptFile, + env: { PORT: port, ...this.#options.env }, + nodeArgs: this.#options.nodeArgs, + scriptArgs: filtersArgs.concat(this.#options.scriptArgs), + }) + + this.#testScript + .then((result) => { + if (mode === 'nonblocking') { + this.#onClose?.(result.exitCode) + this.#watcher?.close() + this.#assetsServer?.stop() + } + }) + .catch((error) => { + this.#logger.warning(`unable to run test script "${this.#scriptFile}"`) + this.#logger.fatal(error) + this.#onError?.(error) + this.#watcher?.close() + this.#assetsServer?.stop() + }) + .finally(() => { + this.#isBusy = false + }) + } + + /** + * Starts the assets server + */ + #startAssetsServer() { + this.#assetsServer = new AssetsDevServer(this.#cwd, this.#options.assets) + this.#assetsServer.start() + } + + /** + * Handles a non TypeScript file change + */ + #handleFileChange(action: string, port: string, filters: string[], relativePath: string) { + if (this.#isBusy) { + return + } + + if (isDotEnvFile(relativePath) || this.#isMetaFile(relativePath)) { + this.#clearScreen() + this.#logger.log(`${this.#colors.green(action)} ${relativePath}`) + this.#runTests(port, filters, 'blocking') + } + } + + /** + * Handles TypeScript source file change + */ + #handleSourceFileChange(action: string, port: string, filters: string[], relativePath: string) { + if (this.#isBusy) { + return + } + + this.#clearScreen() + this.#logger.log(`${this.#colors.green(action)} ${relativePath}`) + + /** + * If changed file is a test file after considering the initial filters, + * then only run that file + */ + if (this.#isTestFile(relativePath)) { + this.#runTests( + port, + this.#convertFiltersToArgs({ + ...this.#options.filters, + files: [relativePath.replace(/\\/g, '/')], + }), + 'blocking' + ) + return + } + + this.#runTests(port, filters, 'blocking') + } + + /** + * Set a custom CLI UI logger + */ + setLogger(logger: Logger) { + this.#logger = logger + this.#assetsServer?.setLogger(logger) + return this + } + + /** + * Add listener to get notified when dev server is + * closed + */ + onClose(callback: (exitCode: number) => any): this { + this.#onClose = callback + return this + } + + /** + * Add listener to get notified when dev server exists + * with an error + */ + onError(callback: (error: any) => any): this { + this.#onError = callback + return this + } + + /** + * Runs tests + */ + async run() { + const port = String(await getPort(this.#cwd)) + const initialFilters = this.#convertFiltersToArgs(this.#options.filters) + + this.#clearScreen() + this.#startAssetsServer() + + this.#logger.info('booting application to run tests...') + this.#runTests(port, initialFilters, 'nonblocking') + } + + /** + * Run tests in watch mode + */ + async runAndWatch(ts: typeof tsStatic, options?: { poll: boolean }) { + const port = String(await getPort(this.#cwd)) + const initialFilters = this.#convertFiltersToArgs(this.#options.filters) + + this.#clearScreen() + this.#startAssetsServer() + + this.#logger.info('booting application to run tests...') + this.#runTests(port, initialFilters, 'blocking') + + /** + * Create watcher using tsconfig.json file + */ + const output = watch(this.#cwd, ts, options || {}) + if (!output) { + this.#onClose?.(1) + return + } + + /** + * Storing reference to watcher, so that we can close it + * when HTTP server exists with error + */ + this.#watcher = output.chokidar + + /** + * Notify the watcher is ready + */ + output.watcher.on('watcher:ready', () => { + this.#logger.info('watching file system for changes...') + }) + + /** + * Cleanup when watcher dies + */ + output.chokidar.on('error', (error) => { + this.#logger.warning('file system watcher failure') + this.#logger.fatal(error) + this.#onError?.(error) + output.chokidar.close() + }) + + /** + * Changes in TypeScript source file + */ + output.watcher.on('source:add', ({ relativePath }) => + this.#handleSourceFileChange('add', port, initialFilters, relativePath) + ) + output.watcher.on('source:change', ({ relativePath }) => + this.#handleSourceFileChange('update', port, initialFilters, relativePath) + ) + output.watcher.on('source:unlink', ({ relativePath }) => + this.#handleSourceFileChange('delete', port, initialFilters, relativePath) + ) + + /** + * Changes in non-TypeScript source files + */ + output.watcher.on('add', ({ relativePath }) => + this.#handleFileChange('add', port, initialFilters, relativePath) + ) + output.watcher.on('change', ({ relativePath }) => + this.#handleFileChange('update', port, initialFilters, relativePath) + ) + output.watcher.on('unlink', ({ relativePath }) => + this.#handleFileChange('delete', port, initialFilters, relativePath) + ) + } +} diff --git a/src/types.ts b/src/types.ts index a3019cd..e08d64d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -33,17 +33,27 @@ export type MetaFile = { reloadServer: boolean } +/** + * Test suite defined in ".adonisrc.json" file + */ +export type Suite = { + files: string[] + name: string +} + /** * Options accepted by assets bundler */ export type AssetsBundlerOptions = | { serve: false + args?: string[] driver?: string cmd?: string } | { serve: true + args: string[] driver: string cmd: string } @@ -60,6 +70,37 @@ export type DevServerOptions = { assets?: AssetsBundlerOptions } +/** + * Options accepted by the test runner + */ +export type TestRunnerOptions = { + /** + * Filter arguments are provided as a key-value + * pair, so that we can mutate them (if needed) + */ + filters: Partial<{ + tests: string[] + suites: string[] + groups: string[] + files: string[] + match: string[] + tags: string[] + ignoreTags: string[] + }> + + /** + * All other tags are provided as a collection of + * arguments + */ + scriptArgs: string[] + nodeArgs: string[] + clearScreen?: boolean + env?: NodeJS.ProcessEnv + metaFiles?: MetaFile[] + assets?: AssetsBundlerOptions + suites: Suite[] +} + /** * Options accepted by the project bundler */ diff --git a/src/watch.ts b/src/watch.ts deleted file mode 100644 index cc0d9df..0000000 --- a/src/watch.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * @adonisjs/assembler - * - * (c) AdonisJS - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import type tsStatic from 'typescript' -import { fileURLToPath } from 'node:url' -import { Watcher } from '@poppinss/chokidar-ts' - -import type { WatchOptions } from './types.js' -import { parseConfig } from './parse_config.js' - -/** - * Watches the file system using tsconfig file - */ -export function watch(cwd: string | URL, ts: typeof tsStatic, options: WatchOptions) { - const config = parseConfig(cwd, ts) - if (!config) { - return - } - - const watcher = new Watcher(typeof cwd === 'string' ? cwd : fileURLToPath(cwd), config!) - const chokidar = watcher.watch(['.'], { usePolling: options.poll }) - return { watcher, chokidar } -} diff --git a/tests/run.spec.ts b/tests/run.spec.ts index 06f0ef9..b551244 100644 --- a/tests/run.spec.ts +++ b/tests/run.spec.ts @@ -9,7 +9,7 @@ import { pEvent } from 'p-event' import { test } from '@japa/runner' -import { runNode } from '../src/run.js' +import { runNode } from '../src/helpers.js' test.group('Child process', () => { test('run typescript file as a child process', async ({ fs, assert }) => { diff --git a/tests/watch.spec.ts b/tests/watch.spec.ts index f3975f8..559d67c 100644 --- a/tests/watch.spec.ts +++ b/tests/watch.spec.ts @@ -9,7 +9,7 @@ import ts from 'typescript' import { test } from '@japa/runner' -import { watch } from '../src/watch.js' +import { watch } from '../src/helpers.js' test.group('Watcher', () => { test('watch files included by the tsconfig.json', async ({ fs, assert, cleanup }, done) => { From b1b70c9886fed9cce15d9f5dfce42797b7f91b30 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Mon, 17 Apr 2023 11:02:04 +0530 Subject: [PATCH 036/131] chore: update dependencies --- package.json | 4 ++-- src/test_runner.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index b61521a..f0d2e61 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "@japa/run-failed-tests": "^1.1.1", "@japa/runner": "^2.5.1", "@japa/spec-reporter": "^1.3.3", - "@swc/core": "^1.3.50", + "@swc/core": "^1.3.51", "@types/node": "^18.15.11", "c8": "^7.13.0", "cross-env": "^7.0.3", @@ -64,7 +64,7 @@ }, "dependencies": { "@adonisjs/env": "^4.2.0-2", - "@poppinss/chokidar-ts": "^4.1.0-2", + "@poppinss/chokidar-ts": "^4.1.0-3", "@poppinss/cliui": "^6.1.1-2", "@types/picomatch": "^2.3.0", "cpy": "^9.0.1", diff --git a/src/test_runner.ts b/src/test_runner.ts index 642c42d..9216a53 100644 --- a/src/test_runner.ts +++ b/src/test_runner.ts @@ -213,7 +213,7 @@ export class TestRunner { port, this.#convertFiltersToArgs({ ...this.#options.filters, - files: [relativePath.replace(/\\/g, '/')], + files: [relativePath], }), 'blocking' ) From dcbaeeb7d70cb1d6584deb537c507af64deb2517 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Mon, 17 Apr 2023 11:30:10 +0530 Subject: [PATCH 037/131] chore(release): 6.1.3-5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f0d2e61..06aa26b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@adonisjs/assembler", - "version": "6.1.3-4", + "version": "6.1.3-5", "description": "Provides utilities to run AdonisJS development server and build project for production", "main": "build/index.js", "type": "module", From d7dfd7519f8d2c3c75eef8a8adf64acfa27faf46 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Mon, 17 Apr 2023 12:40:08 +0530 Subject: [PATCH 038/131] fix: assign logger to assets server when creating an instance of it --- src/dev_server.ts | 1 + src/test_runner.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/dev_server.ts b/src/dev_server.ts index f9712d5..2ad682b 100644 --- a/src/dev_server.ts +++ b/src/dev_server.ts @@ -146,6 +146,7 @@ export class DevServer { */ #startAssetsServer() { this.#assetsServer = new AssetsDevServer(this.#cwd, this.#options.assets) + this.#assetsServer.setLogger(this.#logger) this.#assetsServer.start() } diff --git a/src/test_runner.ts b/src/test_runner.ts index 9216a53..01c9eec 100644 --- a/src/test_runner.ts +++ b/src/test_runner.ts @@ -175,6 +175,7 @@ export class TestRunner { */ #startAssetsServer() { this.#assetsServer = new AssetsDevServer(this.#cwd, this.#options.assets) + this.#assetsServer.setLogger(this.#logger) this.#assetsServer.start() } From 126d406a207b4b4799175a0bee65cabcbcd56e64 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Mon, 17 Apr 2023 12:41:59 +0530 Subject: [PATCH 039/131] chore(release): 6.1.3-6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 06aa26b..424b1fe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@adonisjs/assembler", - "version": "6.1.3-5", + "version": "6.1.3-6", "description": "Provides utilities to run AdonisJS development server and build project for production", "main": "build/index.js", "type": "module", From 822593457856c86c0ea53039c3d5c6088223bde5 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Tue, 4 Jul 2023 15:21:44 +0530 Subject: [PATCH 040/131] chore: update dependencies --- package.json | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index 424b1fe..65c500c 100644 --- a/package.json +++ b/package.json @@ -38,40 +38,40 @@ "author": "virk,adonisjs", "license": "MIT", "devDependencies": { - "@commitlint/cli": "^17.6.1", - "@commitlint/config-conventional": "^17.6.1", + "@commitlint/cli": "^17.6.6", + "@commitlint/config-conventional": "^17.6.6", "@japa/assert": "^1.4.1", - "@japa/file-system": "^1.0.1", + "@japa/file-system": "^1.1.0", "@japa/run-failed-tests": "^1.1.1", "@japa/runner": "^2.5.1", "@japa/spec-reporter": "^1.3.3", - "@swc/core": "^1.3.51", - "@types/node": "^18.15.11", - "c8": "^7.13.0", + "@swc/core": "^1.3.67", + "@types/node": "^20.3.3", + "@types/picomatch": "^2.3.0", + "c8": "^8.0.0", "cross-env": "^7.0.3", "del-cli": "^5.0.0", - "eslint": "^8.38.0", + "eslint": "^8.44.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-adonis": "^3.0.3", "eslint-plugin-prettier": "^4.2.1", "github-label-sync": "^2.3.1", "husky": "^8.0.3", - "np": "^7.7.0", - "p-event": "^5.0.1", - "prettier": "^2.8.7", + "np": "^8.0.4", + "p-event": "^6.0.0", + "prettier": "^2.8.8", "ts-node": "^10.9.1", - "typescript": "^5.0.4" + "typescript": "^5.1.6" }, "dependencies": { - "@adonisjs/env": "^4.2.0-2", - "@poppinss/chokidar-ts": "^4.1.0-3", - "@poppinss/cliui": "^6.1.1-2", - "@types/picomatch": "^2.3.0", - "cpy": "^9.0.1", + "@adonisjs/env": "^4.2.0-3", + "@poppinss/chokidar-ts": "^4.1.0-4", + "@poppinss/cliui": "^6.1.1-3", + "cpy": "^10.1.0", "execa": "^7.0.0", - "get-port": "^6.1.2", + "get-port": "^7.0.0", "picomatch": "^2.3.1", - "slash": "^5.0.0" + "slash": "^5.1.0" }, "peerDependencies": { "typescript": "^4.0.0 || ^5.0.0" From 004ac1ca761bf60d638b5bb335b13fc726a86a90 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Tue, 4 Jul 2023 15:25:41 +0530 Subject: [PATCH 041/131] chore: upgrade japa to v3 --- bin/test.ts | 18 ++++++------------ package.json | 8 +++----- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/bin/test.ts b/bin/test.ts index 196870b..7c443c2 100644 --- a/bin/test.ts +++ b/bin/test.ts @@ -1,9 +1,7 @@ import { assert } from '@japa/assert' +import { fileURLToPath } from 'node:url' import { fileSystem } from '@japa/file-system' -import { specReporter } from '@japa/spec-reporter' -import { runFailedTests } from '@japa/run-failed-tests' -import { fileURLToPath, pathToFileURL } from 'node:url' -import { processCliArgs, configure, run } from '@japa/runner' +import { processCLIArgs, configure, run } from '@japa/runner' const TEST_TMP_DIR_PATH = fileURLToPath(new URL('../tmp', import.meta.url)) @@ -20,15 +18,11 @@ const TEST_TMP_DIR_PATH = fileURLToPath(new URL('../tmp', import.meta.url)) | | Please consult japa.dev/runner-config for the config docs. */ +processCLIArgs(process.argv.slice(2)) configure({ - ...processCliArgs(process.argv.slice(2)), - ...{ - files: ['tests/**/*.spec.ts'], - plugins: [assert(), runFailedTests(), fileSystem({ basePath: TEST_TMP_DIR_PATH })], - reporters: [specReporter()], - timeout: 5 * 1000, - importer: (filePath: string) => import(pathToFileURL(filePath).href), - }, + files: ['tests/**/*.spec.ts'], + plugins: [assert(), fileSystem({ basePath: TEST_TMP_DIR_PATH })], + timeout: 5 * 1000, }) /* diff --git a/package.json b/package.json index 65c500c..e627bee 100644 --- a/package.json +++ b/package.json @@ -40,11 +40,9 @@ "devDependencies": { "@commitlint/cli": "^17.6.6", "@commitlint/config-conventional": "^17.6.6", - "@japa/assert": "^1.4.1", - "@japa/file-system": "^1.1.0", - "@japa/run-failed-tests": "^1.1.1", - "@japa/runner": "^2.5.1", - "@japa/spec-reporter": "^1.3.3", + "@japa/assert": "^2.0.0-1", + "@japa/file-system": "^2.0.0-1", + "@japa/runner": "^3.0.0-3", "@swc/core": "^1.3.67", "@types/node": "^20.3.3", "@types/picomatch": "^2.3.0", From c3e2b471006c74791f10138c7b6b8d97ca94cc88 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Tue, 4 Jul 2023 15:26:52 +0530 Subject: [PATCH 042/131] chore: use @adonisjs/tooling presets for tooling config --- .github/workflows/checks.yml | 14 ++++++++++++ .github/workflows/test.yml | 7 ------ package.json | 43 ++++++++---------------------------- tsconfig.json | 30 +++---------------------- 4 files changed, 26 insertions(+), 68 deletions(-) create mode 100644 .github/workflows/checks.yml delete mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml new file mode 100644 index 0000000..c27fb04 --- /dev/null +++ b/.github/workflows/checks.yml @@ -0,0 +1,14 @@ +name: checks +on: + - push + - pull_request + +jobs: + test: + uses: adonisjs/.github/.github/workflows/test.yml@main + + lint: + uses: adonisjs/.github/.github/workflows/lint.yml@main + + typecheck: + uses: adonisjs/.github/.github/workflows/typecheck.yml@main diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 2d9bc9e..0000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,7 +0,0 @@ -name: test -on: - - push - - pull_request -jobs: - test: - uses: adonisjs/.github/.github/workflows/test.yml@main diff --git a/package.json b/package.json index e627bee..5acb412 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "test": "cross-env NODE_DEBUG=chokidar:ts c8 npm run vscode:test", "lint": "eslint . --ext=.ts", "clean": "del-cli build", + "typecheck": "tsc --noEmit", "compile": "npm run lint && npm run clean && tsc", "build": "npm run compile", "release": "np", @@ -38,6 +39,9 @@ "author": "virk,adonisjs", "license": "MIT", "devDependencies": { + "@adonisjs/eslint-config": "^1.1.7", + "@adonisjs/prettier-config": "^1.1.7", + "@adonisjs/tsconfig": "^1.1.7", "@commitlint/cli": "^17.6.6", "@commitlint/config-conventional": "^17.6.6", "@japa/assert": "^2.0.0-1", @@ -50,9 +54,6 @@ "cross-env": "^7.0.3", "del-cli": "^5.0.0", "eslint": "^8.44.0", - "eslint-config-prettier": "^8.8.0", - "eslint-plugin-adonis": "^3.0.3", - "eslint-plugin-prettier": "^4.2.1", "github-label-sync": "^2.3.1", "husky": "^8.0.3", "np": "^8.0.4", @@ -82,36 +83,6 @@ "url": "https://github.com/adonisjs/assembler/issues" }, "homepage": "https://github.com/adonisjs/assembler#readme", - "eslintConfig": { - "extends": [ - "plugin:adonis/typescriptPackage", - "prettier" - ], - "plugins": [ - "prettier" - ], - "rules": { - "prettier/prettier": [ - "error", - { - "endOfLine": "auto" - } - ] - } - }, - "eslintIgnore": [ - "build" - ], - "prettier": { - "trailingComma": "es5", - "semi": false, - "singleQuote": true, - "useTabs": false, - "quoteProps": "consistent", - "bracketSpacing": true, - "arrowParens": "always", - "printWidth": 100 - }, "commitlint": { "extends": [ "@commitlint/config-conventional" @@ -137,5 +108,9 @@ "build/**", "examples/**" ] - } + }, + "eslintConfig": { + "extends": "@adonisjs/eslint-config/package" + }, + "prettier": "@adonisjs/prettier-config" } diff --git a/tsconfig.json b/tsconfig.json index 55296b4..2039043 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,31 +1,7 @@ { + "extends": "@adonisjs/tsconfig/tsconfig.package.json", "compilerOptions": { - "target": "ESNext", - "module": "NodeNext", - "lib": ["ESNext"], - "noUnusedLocals": true, - "noUnusedParameters": true, - "isolatedModules": true, - "removeComments": true, - "declaration": true, "rootDir": "./", - "outDir": "./build", - "esModuleInterop": true, - "strictNullChecks": true, - "allowSyntheticDefaultImports": true, - "forceConsistentCasingInFileNames": true, - "strictPropertyInitialization": true, - "noImplicitAny": true, - "strictBindCallApply": true, - "strictFunctionTypes": true, - "noImplicitThis": true, - "declarationMap": true, - "skipLibCheck": true, - "types": ["@types/node"] - }, - "include": ["./**/*"], - "exclude": ["./node_modules", "./build"], - "ts-node": { - "swc": true + "outDir": "./build" } -} +} \ No newline at end of file From 343ca838377eba74360d0e53f601d807f116d028 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Tue, 4 Jul 2023 15:27:04 +0530 Subject: [PATCH 043/131] chore: do not publish source files --- package.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/package.json b/package.json index 5acb412..f34b889 100644 --- a/package.json +++ b/package.json @@ -5,11 +5,8 @@ "main": "build/index.js", "type": "module", "files": [ - "src", - "index.ts", "build/src", "build/index.d.ts", - "build/index.d.ts.map", "build/index.js" ], "exports": { From 18d78e24f3fb0b8f728e1e4741d2fd43f42a19cf Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Tue, 4 Jul 2023 15:27:16 +0530 Subject: [PATCH 044/131] chore: add engines to package.json file --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index f34b889..e67e19f 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,9 @@ ".": "./build/index.js", "./types": "./build/src/types.js" }, + "engines": { + "node": ">=18.16.0" + }, "scripts": { "pretest": "npm run lint", "test": "cross-env NODE_DEBUG=chokidar:ts c8 npm run vscode:test", From 083db02a66edc43dcf86f305c7b04103a8d7280b Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Tue, 4 Jul 2023 15:29:24 +0530 Subject: [PATCH 045/131] refactor: remove test filters.match property --- src/test_runner.ts | 5 ----- src/types.ts | 1 - 2 files changed, 6 deletions(-) diff --git a/src/test_runner.ts b/src/test_runner.ts index 01c9eec..34f7fb0 100644 --- a/src/test_runner.ts +++ b/src/test_runner.ts @@ -120,11 +120,6 @@ export class TestRunner { args.push(filters.tests.join(',')) } - if (filters.match) { - args.push('--match') - args.push(filters.match.join(',')) - } - return args } diff --git a/src/types.ts b/src/types.ts index e08d64d..70b2964 100644 --- a/src/types.ts +++ b/src/types.ts @@ -83,7 +83,6 @@ export type TestRunnerOptions = { suites: string[] groups: string[] files: string[] - match: string[] tags: string[] ignoreTags: string[] }> From dbda3b7a84eba231386a28ca8cf00907143a8359 Mon Sep 17 00:00:00 2001 From: Julien Ripouteau Date: Tue, 4 Jul 2023 12:47:50 +0200 Subject: [PATCH 046/131] fix: metafiles copying (#60) --- package.json | 6 +++--- src/bundler.ts | 2 +- tests/bundler.spec.ts | 44 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 tests/bundler.spec.ts diff --git a/package.json b/package.json index e67e19f..8c40a97 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ }, "scripts": { "pretest": "npm run lint", - "test": "cross-env NODE_DEBUG=chokidar:ts c8 npm run vscode:test", + "test": "cross-env NODE_DEBUG=chokidar:ts c8 npm run quick:test", "lint": "eslint . --ext=.ts", "clean": "del-cli build", "typecheck": "tsc --noEmit", @@ -29,7 +29,7 @@ "sync-labels": "github-label-sync --labels .github/labels.json adonisjs/assembler", "format": "prettier --write .", "prepublishOnly": "npm run build", - "vscode:test": "node --loader=ts-node/esm bin/test.ts" + "quick:test": "node --loader=ts-node/esm bin/test.ts" }, "keywords": [ "adonisjs", @@ -66,7 +66,7 @@ "@adonisjs/env": "^4.2.0-3", "@poppinss/chokidar-ts": "^4.1.0-4", "@poppinss/cliui": "^6.1.1-3", - "cpy": "^10.1.0", + "cpy": "^8.1.2", "execa": "^7.0.0", "get-port": "^7.0.0", "picomatch": "^2.3.1", diff --git a/src/bundler.ts b/src/bundler.ts index a4116e9..30abad1 100644 --- a/src/bundler.ts +++ b/src/bundler.ts @@ -101,7 +101,7 @@ export class Bundler { */ async #copyFiles(files: string[], outDir: string) { try { - await copyfiles(files, outDir, { cwd: this.#cwdPath }) + await copyfiles(files, outDir, { parents: true, cwd: this.#cwdPath }) } catch (error) { if (!error.message.includes("the file doesn't exist")) { throw error diff --git a/tests/bundler.spec.ts b/tests/bundler.spec.ts new file mode 100644 index 0000000..928b836 --- /dev/null +++ b/tests/bundler.spec.ts @@ -0,0 +1,44 @@ +import { test } from '@japa/runner' +import { Bundler } from '../index.js' +import ts from 'typescript' + +test.group('Bundler', () => { + test('should copy metafiles to the build directory', async ({ assert, fs }) => { + await Promise.all([ + fs.create( + 'tsconfig.json', + JSON.stringify({ compilerOptions: { outDir: 'build', skipLibCheck: true } }) + ), + fs.create('.adonisrc.json', '{}'), + fs.create('package.json', '{}'), + fs.create('package-lock.json', '{}'), + + fs.create('resources/js/app.ts', ''), + fs.create('resources/views/app.edge', ''), + fs.create('resources/views/foo.edge', ''), + fs.create('resources/views/nested/bar.edge', ''), + fs.create('resources/views/nested/baz.edge', ''), + ]) + + const bundler = new Bundler(fs.baseUrl, ts, { + metaFiles: [ + { + pattern: 'resources/views/**/*.edge', + reloadServer: false, + }, + ], + }) + + await bundler.bundle(true, 'npm') + + await Promise.all([ + assert.fileExists('./build/resources/views/app.edge'), + assert.fileExists('./build/resources/views/foo.edge'), + assert.fileExists('./build/resources/views/nested/bar.edge'), + assert.fileExists('./build/resources/views/nested/baz.edge'), + assert.fileExists('./build/.adonisrc.json'), + assert.fileExists('./build/package.json'), + assert.fileExists('./build/package-lock.json'), + ]) + }) +}) From 3713c8c3a9bbfab72681ae33d5eee831ccc2a34e Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Tue, 4 Jul 2023 16:20:05 +0530 Subject: [PATCH 047/131] chore(release): 6.1.3-7 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8c40a97..3722425 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@adonisjs/assembler", - "version": "6.1.3-6", + "version": "6.1.3-7", "description": "Provides utilities to run AdonisJS development server and build project for production", "main": "build/index.js", "type": "module", From 50a97b8937a79ad45d7f1475d8108c7a45c02b2c Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Wed, 5 Jul 2023 11:06:59 +0530 Subject: [PATCH 048/131] fix: do not kill tests watcher when test fails --- src/test_runner.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/test_runner.ts b/src/test_runner.ts index 34f7fb0..b22d5b4 100644 --- a/src/test_runner.ts +++ b/src/test_runner.ts @@ -154,11 +154,18 @@ export class TestRunner { } }) .catch((error) => { - this.#logger.warning(`unable to run test script "${this.#scriptFile}"`) - this.#logger.fatal(error) - this.#onError?.(error) - this.#watcher?.close() - this.#assetsServer?.stop() + /** + * Since the tests runner set the `process.exitCode = 1`, execa will + * throw an exception. However, we should ignore it and keep the + * watcher on. + */ + if (mode === 'nonblocking') { + this.#logger.warning(`unable to run test script "${this.#scriptFile}"`) + this.#logger.fatal(error) + this.#onError?.(error) + this.#watcher?.close() + this.#assetsServer?.stop() + } }) .finally(() => { this.#isBusy = false From c0501e378f7909b8c5189042a9d9ab1481f69918 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Wed, 5 Jul 2023 11:52:13 +0530 Subject: [PATCH 049/131] refactor: do not rewrite fatal error --- src/test_runner.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/test_runner.ts b/src/test_runner.ts index b22d5b4..05f8993 100644 --- a/src/test_runner.ts +++ b/src/test_runner.ts @@ -160,8 +160,6 @@ export class TestRunner { * watcher on. */ if (mode === 'nonblocking') { - this.#logger.warning(`unable to run test script "${this.#scriptFile}"`) - this.#logger.fatal(error) this.#onError?.(error) this.#watcher?.close() this.#assetsServer?.stop() From b9a70bda901095b3d83cafda627c77ecd3ff5ffa Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Wed, 5 Jul 2023 12:19:56 +0530 Subject: [PATCH 050/131] refactor: do not reprint child process errors --- src/dev_server.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/dev_server.ts b/src/dev_server.ts index 2ad682b..075b397 100644 --- a/src/dev_server.ts +++ b/src/dev_server.ts @@ -133,8 +133,6 @@ export class DevServer { } }) .catch((error) => { - this.#logger.warning('unable to connect to underlying HTTP server process') - this.#logger.fatal(error) this.#onError?.(error) this.#watcher?.close() this.#assetsServer?.stop() From 318e9f73e1975bd87567ca4a5c4df3109c37ee2b Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Wed, 5 Jul 2023 12:24:15 +0530 Subject: [PATCH 051/131] chore(release): 6.1.3-8 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3722425..8d62610 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@adonisjs/assembler", - "version": "6.1.3-7", + "version": "6.1.3-8", "description": "Provides utilities to run AdonisJS development server and build project for production", "main": "build/index.js", "type": "module", From 8e7652d457fbe101e2d949c628d83f615c6e94e1 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Wed, 5 Jul 2023 14:16:42 +0530 Subject: [PATCH 052/131] feat: expose methods to close test runner and dev server --- package.json | 5 ++++- src/dev_server.ts | 21 +++++++++++++++++---- src/test_runner.ts | 44 ++++++++++++++++++++++++++++++-------------- 3 files changed, 51 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index 8d62610..4261064 100644 --- a/package.json +++ b/package.json @@ -106,7 +106,10 @@ "exclude": [ "tests/**", "build/**", - "examples/**" + "examples/**", + "src/dev_server.ts", + "src/test_runner.ts", + "src/assets_dev_server.ts" ] }, "eslintConfig": { diff --git a/src/dev_server.ts b/src/dev_server.ts index 075b397..6007799 100644 --- a/src/dev_server.ts +++ b/src/dev_server.ts @@ -125,7 +125,6 @@ export class DevServer { this.#httpServer .then((result) => { - this.#logger.warning(`underlying HTTP server closed with status code "${result.exitCode}"`) if (mode === 'nonblocking') { this.#onClose?.(result.exitCode) this.#watcher?.close() @@ -133,9 +132,11 @@ export class DevServer { } }) .catch((error) => { - this.#onError?.(error) - this.#watcher?.close() - this.#assetsServer?.stop() + if (mode === 'nonblocking') { + this.#onError?.(error) + this.#watcher?.close() + this.#assetsServer?.stop() + } }) } @@ -220,6 +221,18 @@ export class DevServer { return this } + /** + * Close watchers and running child processes + */ + async close() { + await this.#watcher?.close() + this.#assetsServer?.stop() + if (this.#httpServer) { + this.#httpServer.removeAllListeners() + this.#httpServer.kill('SIGKILL') + } + } + /** * Start the development server */ diff --git a/src/test_runner.ts b/src/test_runner.ts index 05f8993..e223352 100644 --- a/src/test_runner.ts +++ b/src/test_runner.ts @@ -149,20 +149,13 @@ export class TestRunner { .then((result) => { if (mode === 'nonblocking') { this.#onClose?.(result.exitCode) - this.#watcher?.close() - this.#assetsServer?.stop() + this.close() } }) .catch((error) => { - /** - * Since the tests runner set the `process.exitCode = 1`, execa will - * throw an exception. However, we should ignore it and keep the - * watcher on. - */ if (mode === 'nonblocking') { this.#onError?.(error) - this.#watcher?.close() - this.#assetsServer?.stop() + this.close() } }) .finally(() => { @@ -170,6 +163,18 @@ export class TestRunner { }) } + /** + * Restarts the HTTP server + */ + #rerunTests(port: string, filtersArgs: string[]) { + if (this.#testScript) { + this.#testScript.removeAllListeners() + this.#testScript.kill('SIGKILL') + } + + this.#runTests(port, filtersArgs, 'blocking') + } + /** * Starts the assets server */ @@ -190,7 +195,7 @@ export class TestRunner { if (isDotEnvFile(relativePath) || this.#isMetaFile(relativePath)) { this.#clearScreen() this.#logger.log(`${this.#colors.green(action)} ${relativePath}`) - this.#runTests(port, filters, 'blocking') + this.#rerunTests(port, filters) } } @@ -210,18 +215,17 @@ export class TestRunner { * then only run that file */ if (this.#isTestFile(relativePath)) { - this.#runTests( + this.#rerunTests( port, this.#convertFiltersToArgs({ ...this.#options.filters, files: [relativePath], - }), - 'blocking' + }) ) return } - this.#runTests(port, filters, 'blocking') + this.#rerunTests(port, filters) } /** @@ -251,6 +255,18 @@ export class TestRunner { return this } + /** + * Close watchers and running child processes + */ + async close() { + await this.#watcher?.close() + this.#assetsServer?.stop() + if (this.#testScript) { + this.#testScript.removeAllListeners() + this.#testScript.kill('SIGKILL') + } + } + /** * Runs tests */ From 301cf2bc68a4e5c253b95c4a00696d0fe1bfcc30 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Wed, 5 Jul 2023 15:37:42 +0530 Subject: [PATCH 053/131] chore(release): 6.1.3-9 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4261064..5458a32 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@adonisjs/assembler", - "version": "6.1.3-8", + "version": "6.1.3-9", "description": "Provides utilities to run AdonisJS development server and build project for production", "main": "build/index.js", "type": "module", From 91642848f824321eaa872cbf2fc7b849a0142428 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Sat, 8 Jul 2023 15:31:58 +0530 Subject: [PATCH 054/131] chore: update dependencies --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5458a32..7ea8da8 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "@japa/file-system": "^2.0.0-1", "@japa/runner": "^3.0.0-3", "@swc/core": "^1.3.67", - "@types/node": "^20.3.3", + "@types/node": "^20.4.1", "@types/picomatch": "^2.3.0", "c8": "^8.0.0", "cross-env": "^7.0.3", From b83078c4761d0f5144fff631599a7506e891064a Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Sat, 8 Jul 2023 15:44:31 +0530 Subject: [PATCH 055/131] feat: accept rest of japa flags as config options --- src/test_runner.ts | 86 ++++++++++++++++++++++++++++++++-------------- src/types.ts | 5 +++ 2 files changed, 66 insertions(+), 25 deletions(-) diff --git a/src/test_runner.ts b/src/test_runner.ts index e223352..d770ef4 100644 --- a/src/test_runner.ts +++ b/src/test_runner.ts @@ -40,6 +40,8 @@ export class TestRunner { #scriptFile: string = 'bin/test.js' #isMetaFile: picomatch.Matcher #isTestFile: picomatch.Matcher + #scriptArgs: string[] + #initialFiltersArgs: string[] /** * In watch mode, after a file is changed, we wait for the current @@ -79,6 +81,37 @@ export class TestRunner { .map((suite) => suite.files) .flat(1) ) + + this.#scriptArgs = this.#convertOptionsToArgs() + this.#initialFiltersArgs = this.#convertFiltersToArgs(this.#options.filters) + } + + /** + * Converts options to CLI args + */ + #convertOptionsToArgs() { + const args: string[] = [] + + if (this.#options.reporters) { + args.push('--reporters') + args.push(this.#options.reporters.join(',')) + } + + if (this.#options.timeout !== undefined) { + args.push('--timeout') + args.push(String(this.#options.timeout)) + } + + if (this.#options.failed) { + args.push('--failed') + } + + if (this.#options.retries !== undefined) { + args.push('--timeout') + args.push(String(this.#options.retries)) + } + + return args } /** @@ -135,14 +168,22 @@ export class TestRunner { /** * Runs tests */ - #runTests(port: string, filtersArgs: string[], mode: 'blocking' | 'nonblocking') { + #runTests( + port: string, + mode: 'blocking' | 'nonblocking', + filters?: TestRunnerOptions['filters'] + ) { this.#isBusy = true + const scriptArgs = filters + ? this.#convertFiltersToArgs(filters).concat(this.#scriptArgs) + : this.#initialFiltersArgs.concat(this.#scriptArgs) + this.#testScript = runNode(this.#cwd, { script: this.#scriptFile, env: { PORT: port, ...this.#options.env }, nodeArgs: this.#options.nodeArgs, - scriptArgs: filtersArgs.concat(this.#options.scriptArgs), + scriptArgs, }) this.#testScript @@ -166,13 +207,13 @@ export class TestRunner { /** * Restarts the HTTP server */ - #rerunTests(port: string, filtersArgs: string[]) { + #rerunTests(port: string, filters?: TestRunnerOptions['filters']) { if (this.#testScript) { this.#testScript.removeAllListeners() this.#testScript.kill('SIGKILL') } - this.#runTests(port, filtersArgs, 'blocking') + this.#runTests(port, 'blocking', filters) } /** @@ -187,7 +228,7 @@ export class TestRunner { /** * Handles a non TypeScript file change */ - #handleFileChange(action: string, port: string, filters: string[], relativePath: string) { + #handleFileChange(action: string, port: string, relativePath: string) { if (this.#isBusy) { return } @@ -195,14 +236,14 @@ export class TestRunner { if (isDotEnvFile(relativePath) || this.#isMetaFile(relativePath)) { this.#clearScreen() this.#logger.log(`${this.#colors.green(action)} ${relativePath}`) - this.#rerunTests(port, filters) + this.#rerunTests(port) } } /** * Handles TypeScript source file change */ - #handleSourceFileChange(action: string, port: string, filters: string[], relativePath: string) { + #handleSourceFileChange(action: string, port: string, relativePath: string) { if (this.#isBusy) { return } @@ -215,17 +256,14 @@ export class TestRunner { * then only run that file */ if (this.#isTestFile(relativePath)) { - this.#rerunTests( - port, - this.#convertFiltersToArgs({ - ...this.#options.filters, - files: [relativePath], - }) - ) + this.#rerunTests(port, { + ...this.#options.filters, + files: [relativePath], + }) return } - this.#rerunTests(port, filters) + this.#rerunTests(port) } /** @@ -272,13 +310,12 @@ export class TestRunner { */ async run() { const port = String(await getPort(this.#cwd)) - const initialFilters = this.#convertFiltersToArgs(this.#options.filters) this.#clearScreen() this.#startAssetsServer() this.#logger.info('booting application to run tests...') - this.#runTests(port, initialFilters, 'nonblocking') + this.#runTests(port, 'nonblocking') } /** @@ -286,13 +323,12 @@ export class TestRunner { */ async runAndWatch(ts: typeof tsStatic, options?: { poll: boolean }) { const port = String(await getPort(this.#cwd)) - const initialFilters = this.#convertFiltersToArgs(this.#options.filters) this.#clearScreen() this.#startAssetsServer() this.#logger.info('booting application to run tests...') - this.#runTests(port, initialFilters, 'blocking') + this.#runTests(port, 'blocking') /** * Create watcher using tsconfig.json file @@ -330,26 +366,26 @@ export class TestRunner { * Changes in TypeScript source file */ output.watcher.on('source:add', ({ relativePath }) => - this.#handleSourceFileChange('add', port, initialFilters, relativePath) + this.#handleSourceFileChange('add', port, relativePath) ) output.watcher.on('source:change', ({ relativePath }) => - this.#handleSourceFileChange('update', port, initialFilters, relativePath) + this.#handleSourceFileChange('update', port, relativePath) ) output.watcher.on('source:unlink', ({ relativePath }) => - this.#handleSourceFileChange('delete', port, initialFilters, relativePath) + this.#handleSourceFileChange('delete', port, relativePath) ) /** * Changes in non-TypeScript source files */ output.watcher.on('add', ({ relativePath }) => - this.#handleFileChange('add', port, initialFilters, relativePath) + this.#handleFileChange('add', port, relativePath) ) output.watcher.on('change', ({ relativePath }) => - this.#handleFileChange('update', port, initialFilters, relativePath) + this.#handleFileChange('update', port, relativePath) ) output.watcher.on('unlink', ({ relativePath }) => - this.#handleFileChange('delete', port, initialFilters, relativePath) + this.#handleFileChange('delete', port, relativePath) ) } } diff --git a/src/types.ts b/src/types.ts index 70b2964..88a442f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -87,6 +87,11 @@ export type TestRunnerOptions = { ignoreTags: string[] }> + reporters?: string[] + timeout?: number + retries?: number + failed?: boolean + /** * All other tags are provided as a collection of * arguments From 54850a91644f91995af08cd17ae67b02ed2f1fd6 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Sat, 8 Jul 2023 15:54:46 +0530 Subject: [PATCH 056/131] refactor: allow test suite files to be a string or an array --- src/test_runner.ts | 2 +- src/types.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test_runner.ts b/src/test_runner.ts index d770ef4..8336702 100644 --- a/src/test_runner.ts +++ b/src/test_runner.ts @@ -73,7 +73,7 @@ export class TestRunner { this.#options.suites .filter((suite) => { if (this.#options.filters.suites) { - this.#options.filters.suites.includes(suite.name) + return this.#options.filters.suites.includes(suite.name) } return true diff --git a/src/types.ts b/src/types.ts index 88a442f..b9a9003 100644 --- a/src/types.ts +++ b/src/types.ts @@ -37,7 +37,7 @@ export type MetaFile = { * Test suite defined in ".adonisrc.json" file */ export type Suite = { - files: string[] + files: string | string[] name: string } From a368d03a1da64bb6a33bf6bc617fb3ccd8c83bc8 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Sat, 8 Jul 2023 15:57:19 +0530 Subject: [PATCH 057/131] chore(release): 6.1.3-10 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7ea8da8..45704e7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@adonisjs/assembler", - "version": "6.1.3-9", + "version": "6.1.3-10", "description": "Provides utilities to run AdonisJS development server and build project for production", "main": "build/index.js", "type": "module", From 5ac081fc81588c55fc3060ec97694ae5c8604dac Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Sat, 8 Jul 2023 16:12:19 +0530 Subject: [PATCH 058/131] fix: incorrect conversion of options to flags --- src/test_runner.ts | 9 ++------- src/types.ts | 1 - 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/test_runner.ts b/src/test_runner.ts index 8336702..010ac1c 100644 --- a/src/test_runner.ts +++ b/src/test_runner.ts @@ -107,7 +107,7 @@ export class TestRunner { } if (this.#options.retries !== undefined) { - args.push('--timeout') + args.push('--retries') args.push(String(this.#options.retries)) } @@ -143,13 +143,8 @@ export class TestRunner { args.push(filters.tags.join(',')) } - if (filters.ignoreTags) { - args.push('--ignore-tags') - args.push(filters.ignoreTags.join(',')) - } - if (filters.tests) { - args.push('--ignore-tests') + args.push('--tests') args.push(filters.tests.join(',')) } diff --git a/src/types.ts b/src/types.ts index b9a9003..ba7e2f5 100644 --- a/src/types.ts +++ b/src/types.ts @@ -84,7 +84,6 @@ export type TestRunnerOptions = { groups: string[] files: string[] tags: string[] - ignoreTags: string[] }> reporters?: string[] From 1a51b74a8d44bdc95a3665eaaa06dc669a3d5ed6 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Sat, 8 Jul 2023 16:16:27 +0530 Subject: [PATCH 059/131] chore(release): 6.1.3-11 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 45704e7..f196e0f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@adonisjs/assembler", - "version": "6.1.3-10", + "version": "6.1.3-11", "description": "Provides utilities to run AdonisJS development server and build project for production", "main": "build/index.js", "type": "module", From 58b6f40680703436ce407415e246e8f1898446b5 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Sat, 8 Jul 2023 16:21:22 +0530 Subject: [PATCH 060/131] fix: concat options.scriptArgs to args passed to Japa --- src/test_runner.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test_runner.ts b/src/test_runner.ts index 010ac1c..a799880 100644 --- a/src/test_runner.ts +++ b/src/test_runner.ts @@ -82,7 +82,7 @@ export class TestRunner { .flat(1) ) - this.#scriptArgs = this.#convertOptionsToArgs() + this.#scriptArgs = this.#convertOptionsToArgs().concat(this.#options.scriptArgs) this.#initialFiltersArgs = this.#convertFiltersToArgs(this.#options.filters) } From c7f4753725b6d75931f405086c7ae8f4d23250a0 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Sat, 8 Jul 2023 17:01:59 +0530 Subject: [PATCH 061/131] chore(release): 6.1.3-12 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f196e0f..bc67c9b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@adonisjs/assembler", - "version": "6.1.3-11", + "version": "6.1.3-12", "description": "Provides utilities to run AdonisJS development server and build project for production", "main": "build/index.js", "type": "module", From 2207c83ef7dfc1bfa82064e8db65bf7109a297b4 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Mon, 10 Jul 2023 11:59:26 +0530 Subject: [PATCH 062/131] refactor: use latest version of cpy --- src/bundler.ts | 5 ++--- src/debug.ts | 12 ++++++++++++ src/helpers.ts | 47 +++++++++++++++++++++++++++++++++++++++++++++ tests/watch.spec.ts | 4 +++- 4 files changed, 64 insertions(+), 4 deletions(-) create mode 100644 src/debug.ts diff --git a/src/bundler.ts b/src/bundler.ts index 30abad1..fd63a18 100644 --- a/src/bundler.ts +++ b/src/bundler.ts @@ -8,15 +8,14 @@ */ import slash from 'slash' -import copyfiles from 'cpy' import fs from 'node:fs/promises' import type tsStatic from 'typescript' import { fileURLToPath } from 'node:url' import { join, relative } from 'node:path' import { cliui, type Logger } from '@poppinss/cliui' -import { run, parseConfig } from './helpers.js' import type { BundlerOptions } from './types.js' +import { run, parseConfig, copyFiles } from './helpers.js' /** * Instance of CLIUI @@ -101,7 +100,7 @@ export class Bundler { */ async #copyFiles(files: string[], outDir: string) { try { - await copyfiles(files, outDir, { parents: true, cwd: this.#cwdPath }) + await copyFiles(files, this.#cwdPath, outDir) } catch (error) { if (!error.message.includes("the file doesn't exist")) { throw error diff --git a/src/debug.ts b/src/debug.ts new file mode 100644 index 0000000..1884192 --- /dev/null +++ b/src/debug.ts @@ -0,0 +1,12 @@ +/* + * @adonisjs/assembler + * + * (c) AdonisJS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { debuglog } from 'node:util' + +export default debuglog('adonisjs:assembler') diff --git a/src/helpers.ts b/src/helpers.ts index 2b76759..954fe96 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -7,14 +7,19 @@ * file that was distributed with this source code. */ +import cpy from 'cpy' +import { isNotJunk } from 'junk' +import fastGlob from 'fast-glob' import getRandomPort from 'get-port' import type tsStatic from 'typescript' import { fileURLToPath } from 'node:url' import { execaNode, execa } from 'execa' +import { isAbsolute, relative } from 'node:path' import { EnvLoader, EnvParser } from '@adonisjs/env' import { ConfigParser, Watcher } from '@poppinss/chokidar-ts' import type { RunOptions, WatchOptions } from './types.js' +import debug from './debug.js' /** * Default set of args to pass in order to run TypeScript @@ -160,3 +165,45 @@ export async function getPort(cwd: URL): Promise { */ return getRandomPort({ port: 3333 }) } + +/** + * Helper function to copy files from relative paths or glob + * patterns + */ +export async function copyFiles(files: string[], cwd: string, outDir: string) { + /** + * Looping over files and create a new collection with paths + * and glob patterns + */ + const { paths, patterns } = files.reduce<{ patterns: string[]; paths: string[] }>( + (result, file) => { + if (fastGlob.isDynamicPattern(file)) { + result.patterns.push(file) + } else { + result.paths.push(file) + } + + return result + }, + { patterns: [], paths: [] } + ) + + debug('copyFiles inputs: %O, paths: %O, patterns: %O', files, paths, patterns) + + /** + * Getting list of relative paths from glob patterns + */ + const filePaths = paths.concat(await fastGlob(patterns, { cwd })) + + /** + * Computing relative destination. This is because, cpy is buggy when + * outDir is an absolute path. + */ + const destination = isAbsolute(outDir) ? relative(cwd, outDir) : outDir + debug('copying files %O to destination "%s"', filePaths, destination) + + return cpy(filePaths.filter(isNotJunk), destination, { + cwd: cwd, + flat: false, + }) +} diff --git a/tests/watch.spec.ts b/tests/watch.spec.ts index 559d67c..8814cf5 100644 --- a/tests/watch.spec.ts +++ b/tests/watch.spec.ts @@ -11,7 +11,9 @@ import ts from 'typescript' import { test } from '@japa/runner' import { watch } from '../src/helpers.js' -test.group('Watcher', () => { +test.group('Watcher', (group) => { + group.tap((t) => t.disableTimeout()) + test('watch files included by the tsconfig.json', async ({ fs, assert, cleanup }, done) => { assert.plan(1) From 35feb1f265d33fc230fe69fdc1a4a55cfc04f10d Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Mon, 10 Jul 2023 12:08:32 +0530 Subject: [PATCH 063/131] fix: add missing packages --- package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index bc67c9b..cce0ec3 100644 --- a/package.json +++ b/package.json @@ -66,9 +66,11 @@ "@adonisjs/env": "^4.2.0-3", "@poppinss/chokidar-ts": "^4.1.0-4", "@poppinss/cliui": "^6.1.1-3", - "cpy": "^8.1.2", + "cpy": "^10.1.0", "execa": "^7.0.0", + "fast-glob": "^3.3.0", "get-port": "^7.0.0", + "junk": "^4.0.1", "picomatch": "^2.3.1", "slash": "^5.1.0" }, From 1f462f01d91a8b848e434c4ad41e9f8a6f2fea4e Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Mon, 10 Jul 2023 12:22:55 +0530 Subject: [PATCH 064/131] chore(release): 6.1.3-13 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cce0ec3..39e0845 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@adonisjs/assembler", - "version": "6.1.3-12", + "version": "6.1.3-13", "description": "Provides utilities to run AdonisJS development server and build project for production", "main": "build/index.js", "type": "module", From 5d4eede0d2d858e494fa7f736312cd12053487e5 Mon Sep 17 00:00:00 2001 From: Julien Date: Wed, 12 Jul 2023 17:46:31 +0200 Subject: [PATCH 065/131] fix: pass `--enable-source-maps` to spawned process --- src/helpers.ts | 2 ++ tests/run.spec.ts | 1 + 2 files changed, 3 insertions(+) diff --git a/src/helpers.ts b/src/helpers.ts index 954fe96..082d5b5 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -32,6 +32,8 @@ const DEFAULT_NODE_ARGS = [ '--no-warnings', // Enable expiremental meta resolve for cases where someone uses magic import string '--experimental-import-meta-resolve', + // Enable source maps, since TSNode source maps are broken + '--enable-source-maps', ] /** diff --git a/tests/run.spec.ts b/tests/run.spec.ts index b551244..413422f 100644 --- a/tests/run.spec.ts +++ b/tests/run.spec.ts @@ -72,6 +72,7 @@ test.group('Child process', () => { '--loader=ts-node/esm', '--no-warnings', '--experimental-import-meta-resolve', + '--enable-source-maps', '--throw-deprecation', ], }) From b5be22505efe91faac9f97ea1320101ed22835f8 Mon Sep 17 00:00:00 2001 From: Julien Date: Wed, 12 Jul 2023 17:47:09 +0200 Subject: [PATCH 066/131] chore: lint pkg json --- package.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 39e0845..be22ded 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,10 @@ { "name": "@adonisjs/assembler", - "version": "6.1.3-13", "description": "Provides utilities to run AdonisJS development server and build project for production", + "version": "6.1.3-13", + "engines": { + "node": ">=18.16.0" + }, "main": "build/index.js", "type": "module", "files": [ @@ -13,9 +16,6 @@ ".": "./build/index.js", "./types": "./build/src/types.js" }, - "engines": { - "node": ">=18.16.0" - }, "scripts": { "pretest": "npm run lint", "test": "cross-env NODE_DEBUG=chokidar:ts c8 npm run quick:test", @@ -31,13 +31,6 @@ "prepublishOnly": "npm run build", "quick:test": "node --loader=ts-node/esm bin/test.ts" }, - "keywords": [ - "adonisjs", - "build", - "ts" - ], - "author": "virk,adonisjs", - "license": "MIT", "devDependencies": { "@adonisjs/eslint-config": "^1.1.7", "@adonisjs/prettier-config": "^1.1.7", @@ -77,6 +70,9 @@ "peerDependencies": { "typescript": "^4.0.0 || ^5.0.0" }, + "author": "virk,adonisjs", + "license": "MIT", + "homepage": "https://github.com/adonisjs/assembler#readme", "repository": { "type": "git", "url": "git+ssh://git@github.com/adonisjs/assembler.git" @@ -84,7 +80,15 @@ "bugs": { "url": "https://github.com/adonisjs/assembler/issues" }, - "homepage": "https://github.com/adonisjs/assembler#readme", + "keywords": [ + "adonisjs", + "build", + "ts" + ], + "eslintConfig": { + "extends": "@adonisjs/eslint-config/package" + }, + "prettier": "@adonisjs/prettier-config", "commitlint": { "extends": [ "@commitlint/config-conventional" @@ -113,9 +117,5 @@ "src/test_runner.ts", "src/assets_dev_server.ts" ] - }, - "eslintConfig": { - "extends": "@adonisjs/eslint-config/package" - }, - "prettier": "@adonisjs/prettier-config" + } } From deb5398aa8c772e85d9fb388a3adfd73d962c81e Mon Sep 17 00:00:00 2001 From: Julien Date: Wed, 12 Jul 2023 17:48:49 +0200 Subject: [PATCH 067/131] chore: update dependencies --- package.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index be22ded..2b9a634 100644 --- a/package.json +++ b/package.json @@ -32,15 +32,15 @@ "quick:test": "node --loader=ts-node/esm bin/test.ts" }, "devDependencies": { - "@adonisjs/eslint-config": "^1.1.7", - "@adonisjs/prettier-config": "^1.1.7", - "@adonisjs/tsconfig": "^1.1.7", + "@adonisjs/eslint-config": "^1.1.8", + "@adonisjs/prettier-config": "^1.1.8", + "@adonisjs/tsconfig": "^1.1.8", "@commitlint/cli": "^17.6.6", "@commitlint/config-conventional": "^17.6.6", "@japa/assert": "^2.0.0-1", "@japa/file-system": "^2.0.0-1", - "@japa/runner": "^3.0.0-3", - "@swc/core": "^1.3.67", + "@japa/runner": "^3.0.0-6", + "@swc/core": "^1.3.68", "@types/node": "^20.4.1", "@types/picomatch": "^2.3.0", "c8": "^8.0.0", @@ -51,7 +51,7 @@ "husky": "^8.0.3", "np": "^8.0.4", "p-event": "^6.0.0", - "prettier": "^2.8.8", + "prettier": "^3.0.0", "ts-node": "^10.9.1", "typescript": "^5.1.6" }, @@ -60,7 +60,7 @@ "@poppinss/chokidar-ts": "^4.1.0-4", "@poppinss/cliui": "^6.1.1-3", "cpy": "^10.1.0", - "execa": "^7.0.0", + "execa": "^7.1.1", "fast-glob": "^3.3.0", "get-port": "^7.0.0", "junk": "^4.0.1", From 98fe0229b1f897da200f232ef643cf36248e0983 Mon Sep 17 00:00:00 2001 From: Julien Date: Wed, 12 Jul 2023 17:53:58 +0200 Subject: [PATCH 068/131] chore(release): 6.1.3-14 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2b9a634..73f2670 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@adonisjs/assembler", "description": "Provides utilities to run AdonisJS development server and build project for production", - "version": "6.1.3-13", + "version": "6.1.3-14", "engines": { "node": ">=18.16.0" }, From 49b515b5396ee4be1ef485ef854ad0ebacae61c5 Mon Sep 17 00:00:00 2001 From: Julien Date: Wed, 12 Jul 2023 22:13:00 +0200 Subject: [PATCH 069/131] fix: assets bundler args not passed in Bundler --- src/bundler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bundler.ts b/src/bundler.ts index fd63a18..cda535a 100644 --- a/src/bundler.ts +++ b/src/bundler.ts @@ -71,7 +71,7 @@ export class Bundler { await run(this.#cwd, { stdio: 'inherit', script: assetsBundler.cmd, - scriptArgs: [], + scriptArgs: assetsBundler.args, }) return true } catch { From 9db2c348a3e2c1b2653da92accb6794d535278bc Mon Sep 17 00:00:00 2001 From: Julien Date: Wed, 12 Jul 2023 22:14:26 +0200 Subject: [PATCH 070/131] chore(release): 6.1.3-15 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 73f2670..8286b16 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@adonisjs/assembler", "description": "Provides utilities to run AdonisJS development server and build project for production", - "version": "6.1.3-14", + "version": "6.1.3-15", "engines": { "node": ">=18.16.0" }, From cf9d3e32b9be9347e01f7dca0b2b994cba71afe3 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Sat, 22 Jul 2023 10:42:41 +0530 Subject: [PATCH 071/131] chore: update dependencies --- package.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 8286b16..22561c7 100644 --- a/package.json +++ b/package.json @@ -35,18 +35,18 @@ "@adonisjs/eslint-config": "^1.1.8", "@adonisjs/prettier-config": "^1.1.8", "@adonisjs/tsconfig": "^1.1.8", - "@commitlint/cli": "^17.6.6", - "@commitlint/config-conventional": "^17.6.6", + "@commitlint/cli": "^17.6.7", + "@commitlint/config-conventional": "^17.6.7", "@japa/assert": "^2.0.0-1", "@japa/file-system": "^2.0.0-1", "@japa/runner": "^3.0.0-6", - "@swc/core": "^1.3.68", - "@types/node": "^20.4.1", + "@swc/core": "^1.3.70", + "@types/node": "^20.4.3", "@types/picomatch": "^2.3.0", "c8": "^8.0.0", "cross-env": "^7.0.3", "del-cli": "^5.0.0", - "eslint": "^8.44.0", + "eslint": "^8.45.0", "github-label-sync": "^2.3.1", "husky": "^8.0.3", "np": "^8.0.4", @@ -57,7 +57,7 @@ }, "dependencies": { "@adonisjs/env": "^4.2.0-3", - "@poppinss/chokidar-ts": "^4.1.0-4", + "@poppinss/chokidar-ts": "^4.1.0-5", "@poppinss/cliui": "^6.1.1-3", "cpy": "^10.1.0", "execa": "^7.1.1", From 5e2b231823153080d522a9a8c86e96a380623da3 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Sat, 22 Jul 2023 10:50:47 +0530 Subject: [PATCH 072/131] chore: update dependencies --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 22561c7..fc32191 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ }, "dependencies": { "@adonisjs/env": "^4.2.0-3", - "@poppinss/chokidar-ts": "^4.1.0-5", + "@poppinss/chokidar-ts": "^4.1.0-6", "@poppinss/cliui": "^6.1.1-3", "cpy": "^10.1.0", "execa": "^7.1.1", From 6534f423acf4b7fd595dabe34d2c4bc13d80e0dc Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Sat, 22 Jul 2023 10:53:24 +0530 Subject: [PATCH 073/131] docs: remove snyk badge --- README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/README.md b/README.md index 2bbe91c..c394d81 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@
-[![gh-workflow-image]][gh-workflow-url] [![npm-image]][npm-url] ![][typescript-image] [![license-image]][license-url] [![snyk-image]][snyk-url] +[![gh-workflow-image]][gh-workflow-url] [![npm-image]][npm-url] ![][typescript-image] [![license-image]][license-url] ## Introduction Assembler exports the API for starting the **AdonisJS development server**, **building project for production** and **running tests** in watch mode. Assembler must be used during development only. @@ -31,6 +31,3 @@ AdonisJS Assembler is open-sourced software licensed under the [MIT license](LIC [license-url]: LICENSE.md [license-image]: https://img.shields.io/github/license/adonisjs/ace?style=for-the-badge - -[snyk-image]: https://img.shields.io/snyk/vulnerabilities/github/adonisjs/assembler?label=snyk%20Vulnerabilities&style=for-the-badge -[snyk-url]: https://snyk.io/test/github/adonisjs/assembler?targetFile=package.json "snyk" From 9e87d5777c176e4b11d7bc7774c88d0a722f0224 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Sat, 22 Jul 2023 10:54:12 +0530 Subject: [PATCH 074/131] docs(README): update github badges url --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c394d81..6acd931 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,8 @@ In order to ensure that the AdonisJS community is welcoming to all, please revie ## License AdonisJS Assembler is open-sourced software licensed under the [MIT license](LICENSE.md). -[gh-workflow-image]: https://img.shields.io/github/actions/workflow/status/adonisjs/assembler/test.yml?style=for-the-badge -[gh-workflow-url]: https://github.com/adonisjs/assembler/actions/workflows/test.yml "Github action" +[gh-workflow-image]: https://img.shields.io/github/actions/workflow/status/adonisjs/assembler/checks.yml?style=for-the-badge +[gh-workflow-url]: https://github.com/adonisjs/assembler/actions/workflows/checks.yml "Github action" [npm-image]: https://img.shields.io/npm/v/@adonisjs/assembler/latest.svg?style=for-the-badge&logo=npm [npm-url]: https://npmjs.org/package/@adonisjs/assembler/v/latest "npm" From 4e4dec015385b59970ed2b643db3999c1ea1d365 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Sat, 22 Jul 2023 10:58:19 +0530 Subject: [PATCH 075/131] chore(release): 6.1.3-16 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fc32191..be34e2c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@adonisjs/assembler", "description": "Provides utilities to run AdonisJS development server and build project for production", - "version": "6.1.3-15", + "version": "6.1.3-16", "engines": { "node": ">=18.16.0" }, From 4d69943c339a624f3d4e9319501482a54381e5e6 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Wed, 26 Jul 2023 14:03:17 +0530 Subject: [PATCH 076/131] chore: update dependencies --- package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index be34e2c..1d06fed 100644 --- a/package.json +++ b/package.json @@ -40,10 +40,10 @@ "@japa/assert": "^2.0.0-1", "@japa/file-system": "^2.0.0-1", "@japa/runner": "^3.0.0-6", - "@swc/core": "^1.3.70", - "@types/node": "^20.4.3", + "@swc/core": "^1.3.71", + "@types/node": "^20.4.5", "@types/picomatch": "^2.3.0", - "c8": "^8.0.0", + "c8": "^8.0.1", "cross-env": "^7.0.3", "del-cli": "^5.0.0", "eslint": "^8.45.0", @@ -61,7 +61,7 @@ "@poppinss/cliui": "^6.1.1-3", "cpy": "^10.1.0", "execa": "^7.1.1", - "fast-glob": "^3.3.0", + "fast-glob": "^3.3.1", "get-port": "^7.0.0", "junk": "^4.0.1", "picomatch": "^2.3.1", From 19f3cbd5693028565a6df5cdaefb1c194f4f3b81 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Wed, 26 Jul 2023 14:06:06 +0530 Subject: [PATCH 077/131] chore(release): 6.1.3-17 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1d06fed..002765a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@adonisjs/assembler", "description": "Provides utilities to run AdonisJS development server and build project for production", - "version": "6.1.3-16", + "version": "6.1.3-17", "engines": { "node": ">=18.16.0" }, From a256b82e1a894d08a2420e5a060c5dc9871a449f Mon Sep 17 00:00:00 2001 From: Julien Ripouteau Date: Sun, 20 Aug 2023 12:20:59 +0200 Subject: [PATCH 078/131] feat: add code transformer APIs (#61) --- bin/test.ts | 4 +- package.json | 9 +- src/code_transformer/code_transformer.ts | 193 +++++++ src/code_transformer/rc_file_transformer.ts | 332 ++++++++++++ src/types.ts | 49 ++ .../code_transformer.spec.ts.cjs | 506 ++++++++++++++++++ tests/code_transformer.spec.ts | 450 ++++++++++++++++ tests/fixtures/adonisrc.txt | 28 + tests/fixtures/env.txt | 6 + tests/fixtures/kernel.txt | 16 + 10 files changed, 1590 insertions(+), 3 deletions(-) create mode 100644 src/code_transformer/code_transformer.ts create mode 100644 src/code_transformer/rc_file_transformer.ts create mode 100644 tests/__snapshots__/code_transformer.spec.ts.cjs create mode 100644 tests/code_transformer.spec.ts create mode 100644 tests/fixtures/adonisrc.txt create mode 100644 tests/fixtures/env.txt create mode 100644 tests/fixtures/kernel.txt diff --git a/bin/test.ts b/bin/test.ts index 7c443c2..2b1d61a 100644 --- a/bin/test.ts +++ b/bin/test.ts @@ -1,5 +1,7 @@ import { assert } from '@japa/assert' import { fileURLToPath } from 'node:url' + +import { snapshot } from '@japa/snapshot' import { fileSystem } from '@japa/file-system' import { processCLIArgs, configure, run } from '@japa/runner' @@ -21,7 +23,7 @@ const TEST_TMP_DIR_PATH = fileURLToPath(new URL('../tmp', import.meta.url)) processCLIArgs(process.argv.slice(2)) configure({ files: ['tests/**/*.spec.ts'], - plugins: [assert(), fileSystem({ basePath: TEST_TMP_DIR_PATH })], + plugins: [assert(), fileSystem({ basePath: TEST_TMP_DIR_PATH }), snapshot()], timeout: 5 * 1000, }) diff --git a/package.json b/package.json index 002765a..8a81c73 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ ], "exports": { ".": "./build/index.js", + "./code_transformer": "./build/src/code_transformer/code_transformer.js", "./types": "./build/src/types.js" }, "scripts": { @@ -29,9 +30,10 @@ "sync-labels": "github-label-sync --labels .github/labels.json adonisjs/assembler", "format": "prettier --write .", "prepublishOnly": "npm run build", - "quick:test": "node --loader=ts-node/esm bin/test.ts" + "quick:test": "node --enable-source-maps --loader=ts-node/esm bin/test.ts" }, "devDependencies": { + "@adonisjs/application": "7.1.2-9", "@adonisjs/eslint-config": "^1.1.8", "@adonisjs/prettier-config": "^1.1.8", "@adonisjs/tsconfig": "^1.1.8", @@ -40,11 +42,13 @@ "@japa/assert": "^2.0.0-1", "@japa/file-system": "^2.0.0-1", "@japa/runner": "^3.0.0-6", + "@japa/snapshot": "2.0.0-1", "@swc/core": "^1.3.71", "@types/node": "^20.4.5", "@types/picomatch": "^2.3.0", "c8": "^8.0.1", "cross-env": "^7.0.3", + "dedent": "^1.5.1", "del-cli": "^5.0.0", "eslint": "^8.45.0", "github-label-sync": "^2.3.1", @@ -65,7 +69,8 @@ "get-port": "^7.0.0", "junk": "^4.0.1", "picomatch": "^2.3.1", - "slash": "^5.1.0" + "slash": "^5.1.0", + "ts-morph": "^19.0.0" }, "peerDependencies": { "typescript": "^4.0.0 || ^5.0.0" diff --git a/src/code_transformer/code_transformer.ts b/src/code_transformer/code_transformer.ts new file mode 100644 index 0000000..f1401a7 --- /dev/null +++ b/src/code_transformer/code_transformer.ts @@ -0,0 +1,193 @@ +/* + * @adonisjs/assembler + * + * (c) AdonisJS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { join } from 'node:path' +import { fileURLToPath } from 'node:url' +import { CodeBlockWriter, Node, Project, SourceFile, SyntaxKind } from 'ts-morph' + +import { RcFileTransformer } from './rc_file_transformer.js' +import type { AddMiddlewareEntry, EnvValidationDefinition } from '../types.js' + +/** + * This class is responsible for updating + */ +export class CodeTransformer { + /** + * Directory of the adonisjs project + */ + #cwd: URL + + /** + * The TsMorph project + */ + #project: Project + + constructor(cwd: URL) { + this.#cwd = cwd + this.#project = new Project({ + tsConfigFilePath: join(fileURLToPath(this.#cwd), 'tsconfig.json'), + }) + } + + /** + * Update the `adonisrc.ts` file + */ + async updateRcFile(callback: (transformer: RcFileTransformer) => void) { + const rcFileTransformer = new RcFileTransformer(this.#cwd, this.#project) + callback(rcFileTransformer) + await rcFileTransformer.save() + } + + /** + * Add a new middleware to the middleware array of the + * given file + */ + #addToMiddlewareArray(file: SourceFile, target: string, middlewareEntry: AddMiddlewareEntry) { + const callExpressions = file + .getDescendantsOfKind(SyntaxKind.CallExpression) + .filter((statement) => statement.getExpression().getText() === target) + + if (!callExpressions.length) { + throw new Error(`Cannot find ${target} statement in the file.`) + } + + const arrayLiteralExpression = callExpressions[0].getArguments()[0] + if (!arrayLiteralExpression || !Node.isArrayLiteralExpression(arrayLiteralExpression)) { + throw new Error(`Cannot find middleware array in ${target} statement.`) + } + + const middleware = `() => import('${middlewareEntry.path}')` + + if (middlewareEntry.position === 'before') { + arrayLiteralExpression.insertElement(0, middleware) + } else { + arrayLiteralExpression.addElement(middleware) + } + } + + /** + * Add a new middleware to the named middleware of the given file + */ + #addToNamedMiddleware(file: SourceFile, middlewareEntry: AddMiddlewareEntry) { + if (!middlewareEntry.name) throw new Error('Named middleware requires a name.') + + const callArguments = file + .getVariableDeclarationOrThrow('middleware') + .getInitializerIfKindOrThrow(SyntaxKind.CallExpression) + .getArguments() + + if (callArguments.length === 0) { + throw new Error('Named middleware call has no arguments.') + } + + const namedMiddlewareObject = callArguments[0] + if (!Node.isObjectLiteralExpression(namedMiddlewareObject)) { + throw new Error('The argument of the named middleware call is not an object literal.') + } + + const middleware = `${middlewareEntry.name}: () => import('${middlewareEntry.path}')` + namedMiddlewareObject!.insertProperty(0, middleware) + } + + /** + * Write a leading comment + */ + #addLeadingComment(writer: CodeBlockWriter, comment?: string) { + if (!comment) return writer.blankLine() + + return writer + .blankLine() + .writeLine('/*') + .writeLine(`|----------------------------------------------------------`) + .writeLine(`| ${comment}`) + .writeLine(`|----------------------------------------------------------`) + .writeLine(`*/`) + } + + /** + * Define new middlewares inside the `start/kernel.ts` + * file + * + * This function is highly based on some assumptions + * and will not work if you significantly tweaked + * your `start/kernel.ts` file. + */ + async addMiddlewareToStack( + stack: 'server' | 'router' | 'named', + middleware: AddMiddlewareEntry[] + ) { + /** + * Get the `start/kernel.ts` source file + */ + const kernelUrl = fileURLToPath(new URL('./start/kernel.ts', this.#cwd)) + const file = this.#project.getSourceFileOrThrow(kernelUrl) + + /** + * Process each middleware entry + */ + for (const middlewareEntry of middleware) { + if (stack === 'named') { + this.#addToNamedMiddleware(file, middlewareEntry) + } else { + this.#addToMiddlewareArray(file!, `${stack}.use`, middlewareEntry) + } + } + + file.formatText() + await file.save() + } + + /** + * Add new env variable validation in the + * `env.ts` file + */ + async defineEnvValidations(definition: EnvValidationDefinition) { + /** + * Get the `start/env.ts` source file + */ + const kernelUrl = fileURLToPath(new URL('./start/env.ts', this.#cwd)) + const file = this.#project.getSourceFileOrThrow(kernelUrl) + + /** + * Get the `Env.create` call expression + */ + const callExpressions = file + .getDescendantsOfKind(SyntaxKind.CallExpression) + .filter((statement) => statement.getExpression().getText() === 'Env.create') + + if (!callExpressions.length) { + throw new Error(`Cannot find Env.create statement in the file.`) + } + + const objectLiteralExpression = callExpressions[0].getArguments()[1] + if (!Node.isObjectLiteralExpression(objectLiteralExpression)) { + throw new Error(`The second argument of Env.create is not an object literal.`) + } + + let firstAdded = false + + /** + * Add each variable validation + */ + for (const [variable, validation] of Object.entries(definition.variables)) { + objectLiteralExpression.addPropertyAssignment({ + name: variable, + initializer: validation, + leadingTrivia: (writer) => { + if (firstAdded) return + firstAdded = true + return this.#addLeadingComment(writer, definition.leadingComment) + }, + }) + } + + file.formatText() + await file.save() + } +} diff --git a/src/code_transformer/rc_file_transformer.ts b/src/code_transformer/rc_file_transformer.ts new file mode 100644 index 0000000..112c237 --- /dev/null +++ b/src/code_transformer/rc_file_transformer.ts @@ -0,0 +1,332 @@ +import { fileURLToPath } from 'node:url' +import { + ArrayLiteralExpression, + CallExpression, + Node, + Project, + PropertyAssignment, + SourceFile, + SyntaxKind, +} from 'ts-morph' +import type { AppEnvironments } from '@adonisjs/application/types' + +/** + * RcFileTransformer is used to transform the `adonisrc.ts` file + * for adding new commands, providers, meta files etc + */ +export class RcFileTransformer { + #cwd: URL + #project: Project + + constructor(cwd: URL, project: Project) { + this.#cwd = cwd + this.#project = project + } + + /** + * Get the `adonisrc.ts` source file + */ + #getRcFileOrThrow() { + const kernelUrl = fileURLToPath(new URL('./adonisrc.ts', this.#cwd)) + return this.#project.getSourceFileOrThrow(kernelUrl) + } + + /** + * Check if environments array has a subset of available environments + */ + #isInSpecificEnvironment(environments?: AppEnvironments[]): boolean { + if (!environments) return false + + return !!(['web', 'console', 'test', 'repl'] as const).find( + (env) => !environments.includes(env) + ) + } + + /** + * Locate the `defineConfig` call inside the `adonisrc.ts` file + */ + #locateDefineConfigCallOrThrow(file: SourceFile) { + const call = file + .getDescendantsOfKind(SyntaxKind.CallExpression) + .find((statement) => statement.getExpression().getText() === 'defineConfig') + + if (!call) { + throw new Error('Could not locate the defineConfig call.') + } + + return call + } + + /** + * Return the ObjectLiteralExpression of the defineConfig call + */ + #getDefineConfigObjectOrThrow(defineConfigCall: CallExpression) { + const configObject = defineConfigCall + .getArguments()[0] + .asKindOrThrow(SyntaxKind.ObjectLiteralExpression) + + return configObject + } + + /** + * Check if the defineConfig() call has the property assignment + * inside it or not. If not, it will create one and return it. + */ + #getPropertyAssignmentInDefineConfigCall(propertyName: string, initializer: string) { + const file = this.#getRcFileOrThrow() + const defineConfigCall = this.#locateDefineConfigCallOrThrow(file) + const configObject = this.#getDefineConfigObjectOrThrow(defineConfigCall) + + let property = configObject.getProperty(propertyName) + + if (!property) { + configObject.addPropertyAssignment({ name: propertyName, initializer }) + property = configObject.getProperty(propertyName) + } + + return property as PropertyAssignment + } + + /** + * Extract list of imported modules from an ArrayLiteralExpression + * + * It assumes that the array can have two types of elements: + * + * - Simple lazy imported modules: [() => import('path/to/file')] + * - Or an object entry: [{ file: () => import('path/to/file'), environment: ['web', 'console'] }] + * where the `file` property is a lazy imported module. + */ + #extractModulesFromArray(array: ArrayLiteralExpression) { + const modules = array.getElements().map((element) => { + /** + * Simple lazy imported module + */ + if (Node.isArrowFunction(element)) { + const importExp = element.getFirstDescendantByKindOrThrow(SyntaxKind.CallExpression) + const literal = importExp.getFirstDescendantByKindOrThrow(SyntaxKind.StringLiteral) + return literal.getLiteralValue() + } + + /** + * Object entry + */ + if (Node.isObjectLiteralExpression(element)) { + const fileProp = element.getPropertyOrThrow('file') as PropertyAssignment + const arrowFn = fileProp.getFirstDescendantByKindOrThrow(SyntaxKind.ArrowFunction) + const importExp = arrowFn.getFirstDescendantByKindOrThrow(SyntaxKind.CallExpression) + const literal = importExp.getFirstDescendantByKindOrThrow(SyntaxKind.StringLiteral) + return literal.getLiteralValue() + } + }) + + return modules.filter(Boolean) as string[] + } + + /** + * Extract a specific property from an ArrayLiteralExpression + * that contains object entries. + * + * This function is mainly used for extractring the `pattern` property + * when adding a new meta files entry, or the `name` property when + * adding a new test suite. + */ + #extractPropertyFromArray(array: ArrayLiteralExpression, propertyName: string) { + const property = array.getElements().map((el) => { + if (!Node.isObjectLiteralExpression(el)) return + + const nameProp = el.getPropertyOrThrow(propertyName) + if (!Node.isPropertyAssignment(nameProp)) return + + const name = nameProp.getInitializerIfKindOrThrow(SyntaxKind.StringLiteral) + return name.getLiteralValue() + }) + + return property.filter(Boolean) as string[] + } + + /** + * Build a new module entry for the preloads and providers array + * based upon the environments specified + */ + #buildNewModuleEntry(modulePath: string, environments?: AppEnvironments[]) { + if (!this.#isInSpecificEnvironment(environments)) { + return `() => import('${modulePath}')` + } + + return `{ + file: () => import('${modulePath}'), + environment: [${environments?.map((env) => `'${env}'`).join(', ')}], + }` + } + + /** + * Add a new command to the rcFile + */ + addCommand(commandPath: string) { + const commandsProperty = this.#getPropertyAssignmentInDefineConfigCall('providers', '[]') + const commandsArray = commandsProperty.getInitializerIfKindOrThrow( + SyntaxKind.ArrayLiteralExpression + ) + + const commandString = `() => import('${commandPath}')` + + /** + * If the command already exists, do nothing + */ + if (commandsArray.getElements().some((el) => el.getText() === commandString)) { + return this + } + + /** + * Add the command to the array + */ + commandsArray.addElement(commandString) + return this + } + + /** + * Add a new preloaded file to the rcFile + */ + addPreloadFile(modulePath: string, environments?: AppEnvironments[]) { + const preloadsProperty = this.#getPropertyAssignmentInDefineConfigCall('preloads', '[]') + const preloadsArray = preloadsProperty.getInitializerIfKindOrThrow( + SyntaxKind.ArrayLiteralExpression + ) + + /** + * Check for duplicates + */ + const existingPreloadedFiles = this.#extractModulesFromArray(preloadsArray) + const isDuplicate = existingPreloadedFiles.includes(modulePath) + if (isDuplicate) { + return this + } + + /** + * Add the preloaded file to the array + */ + preloadsArray.addElement(this.#buildNewModuleEntry(modulePath, environments)) + return this + } + + /** + * Add a new provider to the rcFile + */ + addProvider(providerPath: string, environments?: AppEnvironments[]) { + const property = this.#getPropertyAssignmentInDefineConfigCall('providers', '[]') + const providersArray = property.getInitializerIfKindOrThrow(SyntaxKind.ArrayLiteralExpression) + + /** + * Check for duplicates + */ + const existingProviderPaths = this.#extractModulesFromArray(providersArray) + const isDuplicate = existingProviderPaths.includes(providerPath) + if (isDuplicate) { + return this + } + + /** + * Add the provider to the array + */ + providersArray.addElement(this.#buildNewModuleEntry(providerPath, environments)) + + return this + } + + /** + * Add a new meta file to the rcFile + */ + addMetaFile(globPattern: string, reloadServer = false) { + const property = this.#getPropertyAssignmentInDefineConfigCall('metaFiles', '[]') + const metaFilesArray = property.getInitializerIfKindOrThrow(SyntaxKind.ArrayLiteralExpression) + + /** + * Check for duplicates + */ + const alreadyDefinedPatterns = this.#extractPropertyFromArray(metaFilesArray, 'pattern') + if (alreadyDefinedPatterns.includes(globPattern)) { + return this + } + + /** + * Add the meta file to the array + */ + metaFilesArray.addElement( + `{ + pattern: '${globPattern}', + reloadServer: ${reloadServer}, + }` + ) + + return this + } + + /** + * Set directory name and path + */ + setDirectory(key: string, value: string) { + const property = this.#getPropertyAssignmentInDefineConfigCall('directories', '{}') + const directories = property.getInitializerIfKindOrThrow(SyntaxKind.ObjectLiteralExpression) + directories.addPropertyAssignment({ name: key, initializer: `'${value}'` }) + + return this + } + + /** + * Set command alias + */ + setCommandAlias(alias: string, command: string) { + const aliasProperty = this.#getPropertyAssignmentInDefineConfigCall('commandsAliases', '{}') + const aliases = aliasProperty.getInitializerIfKindOrThrow(SyntaxKind.ObjectLiteralExpression) + aliases.addPropertyAssignment({ name: alias, initializer: `'${command}'` }) + + return this + } + + /** + * Add a new test suite to the rcFile + */ + addSuite(suiteName: string, files: string | string[], timeout?: number) { + const testProperty = this.#getPropertyAssignmentInDefineConfigCall( + 'tests', + `{ suites: [], forceExit: true, timeout: 2000 }` + ) + + const property = testProperty + .getInitializerIfKindOrThrow(SyntaxKind.ObjectLiteralExpression) + .getPropertyOrThrow('suites') as PropertyAssignment + + const suitesArray = property.getInitializerIfKindOrThrow(SyntaxKind.ArrayLiteralExpression) + + /** + * Check for duplicates + */ + const existingSuitesNames = this.#extractPropertyFromArray(suitesArray, 'name') + if (existingSuitesNames.includes(suiteName)) { + return this + } + + /** + * Add the suite to the array + */ + const filesArray = Array.isArray(files) ? files : [files] + suitesArray.addElement( + `{ + name: '${suiteName}', + files: [${filesArray.map((file) => `'${file}'`).join(', ')}], + timeout: ${timeout ?? 2000}, + }` + ) + + return this + } + + /** + * Save the adonisrc.ts file + */ + save() { + const file = this.#getRcFileOrThrow() + file.formatText() + return file.save() + } +} diff --git a/src/types.ts b/src/types.ts index ba7e2f5..85a9e19 100644 --- a/src/types.ts +++ b/src/types.ts @@ -111,3 +111,52 @@ export type BundlerOptions = { metaFiles?: MetaFile[] assets?: AssetsBundlerOptions } + +/** + * Entry to add a middleware to a given middleware stack + * via the CodeTransformer + */ +export type AddMiddlewareEntry = { + /** + * If you are adding a named middleware, then you must + * define the name. + */ + name?: string + + /** + * The path to the middleware file + * + * @example + * `@adonisjs/static/static_middleware` + * `#middlewares/silent_auth.js` + */ + path: string + + /** + * The position to add the middleware. If `before` + * middleware will be added at the first position and + * therefore will be run before all others + * + * @default 'after' + */ + position?: 'before' | 'after' +} + +/** + * Defines the structure of an environment variable validation + * definition + */ +export type EnvValidationDefinition = { + /** + * Write a leading comment on top of your variables + */ + leadingComment?: string + + /** + * A key-value pair of env variables and their validation + * + * @example + * MY_VAR: 'Env.schema.string.optional()' + */ + variables: Record +} diff --git a/tests/__snapshots__/code_transformer.spec.ts.cjs b/tests/__snapshots__/code_transformer.spec.ts.cjs new file mode 100644 index 0000000..02c8556 --- /dev/null +++ b/tests/__snapshots__/code_transformer.spec.ts.cjs @@ -0,0 +1,506 @@ +exports[`Code transformer > set correct position when defined 1`] = `"import router from '@adonisjs/core/services/router' +import server from '@adonisjs/core/services/server' + +server.errorHandler(() => import('#exceptions/handler')) + +server.use([ + () => import('#foo/middleware.js'), + () => import('@adonisjs/static/static_middleware'), + () => import('#middleware/container_bindings_middleware'), + () => import('@adonisjs/session/session_middleware'), + () => import('#foo/middleware2.js') +]) + +router.use([ + () => import('@adonisjs/core/bodyparser_middleware'), + () => import('@adonisjs/shield/shield_middleware'), +]) + +export const middleware = router.named({}) +"` + +exports[`Code transformer > add a route middleware 1`] = `"import router from '@adonisjs/core/services/router' +import server from '@adonisjs/core/services/server' + +server.errorHandler(() => import('#exceptions/handler')) + +server.use([ + () => import('#middleware/container_bindings_middleware'), + () => import('@adonisjs/session/session_middleware'), +]) + +router.use([ + () => import('#foo/bar.js'), + () => import('@adonisjs/core/bodyparser_middleware'), + () => import('@adonisjs/shield/shield_middleware'), + () => import('@adonisjs/random_middleware') +]) + +export const middleware = router.named({}) +"` + +exports[`Code transformer > add route and server middleware 1`] = `"import router from '@adonisjs/core/services/router' +import server from '@adonisjs/core/services/server' + +server.errorHandler(() => import('#exceptions/handler')) + +server.use([ + () => import('#middleware/container_bindings_middleware'), + () => import('@adonisjs/session/session_middleware'), + () => import('@adonisjs/random_middleware') +]) + +router.use([ + () => import('#foo/bar.js'), + () => import('@adonisjs/core/bodyparser_middleware'), + () => import('@adonisjs/shield/shield_middleware'), +]) + +export const middleware = router.named({}) +"` + +exports[`Code transformer > add leading comment 1`] = `"import { Env } from '@adonisjs/core/env' + +export default await Env.create(new URL('../', import.meta.url), { + NODE_ENV: Env.schema.enum(['development', 'production', 'test'] as const), + PORT: Env.schema.number(), + + /* + |---------------------------------------------------------- + | Redis configuration + |---------------------------------------------------------- + */ + REDIS_HOST: Env.schema.string.optional(), + REDIS_PORT: Env.schema.number() +}) +"` + +exports[`Code transformer > add provider to rc file with specific environments 1`] = `"import { defineConfig } from '@adonisjs/core/app' + +export default defineConfig({ + typescript: true, + preloads: [ + () => import('./start/routes.ts'), + { + file: () => import('./start/ace.ts'), + environment: ['console'], + }, + ], + providers: [ + () => import('@adonisjs/core/providers/app_provider'), + { + file: () => import('@adonisjs/core/providers/repl_provider'), + environment: ['repl'], + }, + { + file: () => import('@adonisjs/redis-provider'), + environment: ['console', 'repl'], + } + ], + commands: [ + () => import('@adonisjs/core/commands') + ] +}) +"` + +exports[`Code transformer > do no add environments when they are all specified 1`] = `"import { defineConfig } from '@adonisjs/core/app' + +export default defineConfig({ + typescript: true, + preloads: [ + () => import('./start/routes.ts'), + { + file: () => import('./start/ace.ts'), + environment: ['console'], + }, + ], + providers: [ + () => import('@adonisjs/core/providers/app_provider'), + { + file: () => import('@adonisjs/core/providers/repl_provider'), + environment: ['repl'], + }, + () => import('@adonisjs/redis-provider') + ], + commands: [ + () => import('@adonisjs/core/commands') + ] +}) +"` + +exports[`Code transformer > add meta files to rc file with reload server 1`] = `"import { defineConfig } from '@adonisjs/core/app' + +export default defineConfig({ + typescript: true, + preloads: [ + () => import('./start/routes.ts'), + { + file: () => import('./start/ace.ts'), + environment: ['console'], + }, + ], + providers: [ + () => import('@adonisjs/core/providers/app_provider'), + { + file: () => import('@adonisjs/core/providers/repl_provider'), + environment: ['repl'], + } + ], + metaFiles: [ + { + pattern: 'public/**', + reloadServer: true + }, + { + pattern: 'assets/**', + reloadServer: true, + } + ], + commands: [ + () => import('@adonisjs/core/commands') + ] +}) +"` + +exports[`Code transformer > set directory in rc file 1`] = `"import { defineConfig } from '@adonisjs/core/app' + +export default defineConfig({ + typescript: true, + preloads: [ + () => import('./start/routes.ts'), + { + file: () => import('./start/ace.ts'), + environment: ['console'], + }, + ], + providers: [ + () => import('@adonisjs/core/providers/app_provider'), + { + file: () => import('@adonisjs/core/providers/repl_provider'), + environment: ['repl'], + } + ], + metaFiles: [ + { + pattern: 'public/**', + reloadServer: true + }, + ], + commands: [ + () => import('@adonisjs/core/commands') + ], + directories: { + views: 'templates' + } +}) +"` + +exports[`Code transformer > set command alias in rc file 1`] = `"import { defineConfig } from '@adonisjs/core/app' + +export default defineConfig({ + typescript: true, + preloads: [ + () => import('./start/routes.ts'), + { + file: () => import('./start/ace.ts'), + environment: ['console'], + }, + ], + providers: [ + () => import('@adonisjs/core/providers/app_provider'), + { + file: () => import('@adonisjs/core/providers/repl_provider'), + environment: ['repl'], + } + ], + metaFiles: [ + { + pattern: 'public/**', + reloadServer: true + }, + ], + commands: [ + () => import('@adonisjs/core/commands') + ], + commandsAliases: { + migrate: 'migration:run' + } +}) +"` + +exports[`Code transformer | addPreloadFile > add preload file with specific environments 1`] = `"import { defineConfig } from '@adonisjs/core/app' + +export default defineConfig({ + typescript: true, + preloads: [ + () => import('./start/routes.ts'), + { + file: () => import('./start/ace.ts'), + environment: ['console'], + }, + { + file: () => import('#start/foo.js'), + environment: ['console', 'repl'], + } + ], + providers: [ + () => import('@adonisjs/core/providers/app_provider'), + { + file: () => import('@adonisjs/core/providers/repl_provider'), + environment: ['repl'], + } + ], + metaFiles: [ + { + pattern: 'public/**', + reloadServer: true + }, + ], + commands: [ + () => import('@adonisjs/core/commands') + ] +}) +"` + +exports[`Code transformer | addMiddlewareToStack > set correct position when defined 1`] = `"import router from '@adonisjs/core/services/router' +import server from '@adonisjs/core/services/server' + +server.errorHandler(() => import('#exceptions/handler')) + +server.use([ + () => import('#foo/middleware.js'), + () => import('@adonisjs/static/static_middleware'), + () => import('#middleware/container_bindings_middleware'), + () => import('@adonisjs/session/session_middleware'), + () => import('#foo/middleware2.js') +]) + +router.use([ + () => import('@adonisjs/core/bodyparser_middleware'), + () => import('@adonisjs/shield/shield_middleware'), +]) + +export const middleware = router.named({}) +"` + +exports[`Code transformer | addMiddlewareToStack > add a route middleware 1`] = `"import router from '@adonisjs/core/services/router' +import server from '@adonisjs/core/services/server' + +server.errorHandler(() => import('#exceptions/handler')) + +server.use([ + () => import('#middleware/container_bindings_middleware'), + () => import('@adonisjs/session/session_middleware'), +]) + +router.use([ + () => import('#foo/bar.js'), + () => import('@adonisjs/core/bodyparser_middleware'), + () => import('@adonisjs/shield/shield_middleware'), + () => import('@adonisjs/random_middleware') +]) + +export const middleware = router.named({}) +"` + +exports[`Code transformer | addMiddlewareToStack > add route and server middleware 1`] = `"import router from '@adonisjs/core/services/router' +import server from '@adonisjs/core/services/server' + +server.errorHandler(() => import('#exceptions/handler')) + +server.use([ + () => import('#middleware/container_bindings_middleware'), + () => import('@adonisjs/session/session_middleware'), + () => import('@adonisjs/random_middleware') +]) + +router.use([ + () => import('#foo/bar.js'), + () => import('@adonisjs/core/bodyparser_middleware'), + () => import('@adonisjs/shield/shield_middleware'), +]) + +export const middleware = router.named({}) +"` + +exports[`Code transformer | defineEnvValidations > add leading comment 1`] = `"import { Env } from '@adonisjs/core/env' + +export default await Env.create(new URL('../', import.meta.url), { + NODE_ENV: Env.schema.enum(['development', 'production', 'test'] as const), + PORT: Env.schema.number(), + + /* + |---------------------------------------------------------- + | Redis configuration + |---------------------------------------------------------- + */ + REDIS_HOST: Env.schema.string.optional(), + REDIS_PORT: Env.schema.number() +}) +"` + +exports[`Code transformer | addProvider > add provider to rc file with specific environments 1`] = `"import { defineConfig } from '@adonisjs/core/app' + +export default defineConfig({ + typescript: true, + preloads: [ + () => import('./start/routes.ts'), + { + file: () => import('./start/ace.ts'), + environment: ['console'], + }, + ], + providers: [ + () => import('@adonisjs/core/providers/app_provider'), + { + file: () => import('@adonisjs/core/providers/repl_provider'), + environment: ['repl'], + }, + { + file: () => import('@adonisjs/redis-provider'), + environment: ['console', 'repl'], + } + ], + metaFiles: [ + { + pattern: 'public/**', + reloadServer: true + }, + ], + commands: [ + () => import('@adonisjs/core/commands') + ] +}) +"` + +exports[`Code transformer | addProvider > do no add environments when they are all specified 1`] = `"import { defineConfig } from '@adonisjs/core/app' + +export default defineConfig({ + typescript: true, + preloads: [ + () => import('./start/routes.ts'), + { + file: () => import('./start/ace.ts'), + environment: ['console'], + }, + ], + providers: [ + () => import('@adonisjs/core/providers/app_provider'), + { + file: () => import('@adonisjs/core/providers/repl_provider'), + environment: ['repl'], + }, + () => import('@adonisjs/redis-provider') + ], + metaFiles: [ + { + pattern: 'public/**', + reloadServer: true + }, + ], + commands: [ + () => import('@adonisjs/core/commands') + ] +}) +"` + +exports[`Code transformer | addMetaFile > add meta files to rc file with reload server 1`] = `"import { defineConfig } from '@adonisjs/core/app' + +export default defineConfig({ + typescript: true, + preloads: [ + () => import('./start/routes.ts'), + { + file: () => import('./start/ace.ts'), + environment: ['console'], + }, + ], + providers: [ + () => import('@adonisjs/core/providers/app_provider'), + { + file: () => import('@adonisjs/core/providers/repl_provider'), + environment: ['repl'], + } + ], + metaFiles: [ + { + pattern: 'public/**', + reloadServer: true + }, + { + pattern: 'assets/**', + reloadServer: true, + } + ], + commands: [ + () => import('@adonisjs/core/commands') + ] +}) +"` + +exports[`Code transformer | setDirectory > set directory in rc file 1`] = `"import { defineConfig } from '@adonisjs/core/app' + +export default defineConfig({ + typescript: true, + preloads: [ + () => import('./start/routes.ts'), + { + file: () => import('./start/ace.ts'), + environment: ['console'], + }, + ], + providers: [ + () => import('@adonisjs/core/providers/app_provider'), + { + file: () => import('@adonisjs/core/providers/repl_provider'), + environment: ['repl'], + } + ], + metaFiles: [ + { + pattern: 'public/**', + reloadServer: true + }, + ], + commands: [ + () => import('@adonisjs/core/commands') + ], + directories: { + views: 'templates' + } +}) +"` + +exports[`Code transformer | setCommandAlias > set command alias in rc file 1`] = `"import { defineConfig } from '@adonisjs/core/app' + +export default defineConfig({ + typescript: true, + preloads: [ + () => import('./start/routes.ts'), + { + file: () => import('./start/ace.ts'), + environment: ['console'], + }, + ], + providers: [ + () => import('@adonisjs/core/providers/app_provider'), + { + file: () => import('@adonisjs/core/providers/repl_provider'), + environment: ['repl'], + } + ], + metaFiles: [ + { + pattern: 'public/**', + reloadServer: true + }, + ], + commands: [ + () => import('@adonisjs/core/commands') + ], + commandsAliases: { + migrate: 'migration:run' + } +}) +"` + diff --git a/tests/code_transformer.spec.ts b/tests/code_transformer.spec.ts new file mode 100644 index 0000000..60d56f1 --- /dev/null +++ b/tests/code_transformer.spec.ts @@ -0,0 +1,450 @@ +import dedent from 'dedent' +import { test } from '@japa/runner' +import { readFile } from 'node:fs/promises' +import type { FileSystem } from '@japa/file-system' + +import { CodeTransformer } from '../src/code_transformer/code_transformer.js' + +async function setupFakeAdonisproject(fs: FileSystem) { + await Promise.all([ + fs.createJson('tsconfig.json', { compilerOptions: {} }), + fs.create('start/kernel.ts', await readFile('./tests/fixtures/kernel.txt', 'utf-8')), + fs.create('adonisrc.ts', await readFile('./tests/fixtures/adonisrc.txt', 'utf-8')), + fs.create('start/env.ts', await readFile('./tests/fixtures/env.txt', 'utf-8')), + ]) +} + +test.group('Code transformer | addMiddlewareToStack', (group) => { + group.each.setup(async ({ context }) => setupFakeAdonisproject(context.fs)) + + test('add a server middleware', async ({ assert, fs }) => { + const transformer = new CodeTransformer(fs.baseUrl) + + await transformer.addMiddlewareToStack('server', [ + { path: '@adonisjs/static/static_middleware' }, + ]) + + assert.fileContains('start/kernel.ts', `() => import('@adonisjs/static/static_middleware')`) + }) + + test('add multiple server middleware', async ({ assert, fs }) => { + const transformer = new CodeTransformer(fs.baseUrl) + + await transformer.addMiddlewareToStack('server', [ + { path: '@adonisjs/static/static_middleware' }, + { path: '#foo/middleware.js' }, + ]) + + assert.fileContains('start/kernel.ts', `() => import('@adonisjs/static/static_middleware')`) + assert.fileContains('start/kernel.ts', `() => import('#foo/middleware.js')`) + }) + + test('set correct position when defined', async ({ assert, fs }) => { + const transformer = new CodeTransformer(fs.baseUrl) + + await transformer.addMiddlewareToStack('server', [ + { path: '@adonisjs/static/static_middleware', position: 'before' }, + { path: '#foo/middleware.js', position: 'before' }, + { path: '#foo/middleware2.js' }, + ]) + + const file = await fs.contents('start/kernel.ts') + assert.snapshot(file).match() + }) + + test('add a route middleware', async ({ assert, fs }) => { + const transformer = new CodeTransformer(fs.baseUrl) + + await transformer.addMiddlewareToStack('router', [ + { path: '#foo/bar.js', position: 'before' }, + { path: '@adonisjs/random_middleware', position: 'after' }, + ]) + + const file = await fs.contents('start/kernel.ts') + assert.snapshot(file).match() + }) + + test('add route and server middleware', async ({ assert, fs }) => { + const transformer = new CodeTransformer(fs.baseUrl) + + await transformer.addMiddlewareToStack('router', [{ path: '#foo/bar.js', position: 'before' }]) + await transformer.addMiddlewareToStack('server', [ + { path: '@adonisjs/random_middleware', position: 'after' }, + ]) + + const file = await fs.contents('start/kernel.ts') + assert.snapshot(file).match() + }) + + test('add named middleware', async ({ assert, fs }) => { + const transformer = new CodeTransformer(fs.baseUrl) + + await transformer.addMiddlewareToStack('named', [ + { name: 'auth', path: '#foo/bar.js', position: 'before' }, + { name: 'rand', path: '@adonisjs/random_middleware', position: 'after' }, + ]) + + assert.fileContains('start/kernel.ts', `auth: () => import('#foo/bar.js')`) + assert.fileContains('start/kernel.ts', `rand: () => import('@adonisjs/random_middleware')`) + }) +}) + +test.group('Code transformer | defineEnvValidations', (group) => { + group.each.setup(async ({ context }) => setupFakeAdonisproject(context.fs)) + test('define new env validations', async ({ assert, fs }) => { + const transformer = new CodeTransformer(fs.baseUrl) + + await transformer.defineEnvValidations({ + variables: { + MY_VAR: 'Env.schema.string.optional()', + MY_VAR2: 'Env.schema.number()', + }, + }) + + assert.fileContains('start/env.ts', `MY_VAR: Env.schema.string.optional()`) + assert.fileContains('start/env.ts', `MY_VAR2: Env.schema.number()`) + }) + + test('add leading comment', async ({ assert, fs }) => { + const transformer = new CodeTransformer(fs.baseUrl) + + await transformer.defineEnvValidations({ + leadingComment: 'Redis configuration', + variables: { + REDIS_HOST: 'Env.schema.string.optional()', + REDIS_PORT: 'Env.schema.number()', + }, + }) + + const file = await fs.contents('start/env.ts') + assert.snapshot(file).match() + }) +}) + +test.group('Code transformer | addCommand', (group) => { + group.each.setup(async ({ context }) => setupFakeAdonisproject(context.fs)) + + test('add command to rc file', async ({ assert, fs }) => { + const transformer = new CodeTransformer(fs.baseUrl) + + await transformer.updateRcFile((rcFile) => { + rcFile.addCommand('#foo/bar.js').addCommand('#foo/bar2.js') + }) + + assert.fileContains('adonisrc.ts', `() => import('#foo/bar.js')`) + assert.fileContains('adonisrc.ts', `() => import('#foo/bar2.js')`) + }) + + test('add command should not add duplicate', async ({ assert, fs }) => { + const transformer = new CodeTransformer(fs.baseUrl) + + await transformer.updateRcFile((rcFile) => { + rcFile.addCommand('#foo/bar.js').addCommand('#foo/bar.js') + }) + + const file = await fs.contents('adonisrc.ts') + const occurrences = (file.match(/() => import\('#foo\/bar\.js'\)/g) || []).length + + assert.equal(occurrences, 1) + }) + + test('should add command even if commands property is missing', async ({ assert, fs }) => { + await fs.create( + 'adonisrc.ts', + dedent` + import { defineConfig } from '@adonisjs/core/app' + + export default defineConfig({ + typescript: true, + })` + ) + + const transformer = new CodeTransformer(fs.baseUrl) + + await transformer.updateRcFile((rcFile) => { + rcFile.addCommand('#foo/bar.js') + }) + + assert.fileContains('adonisrc.ts', `() => import('#foo/bar.js')`) + }) +}) + +test.group('Code transformer | addProvider', (group) => { + group.each.setup(async ({ context }) => setupFakeAdonisproject(context.fs)) + + test('add provider to rc file', async ({ assert, fs }) => { + const transformer = new CodeTransformer(fs.baseUrl) + + await transformer.updateRcFile((rcFile) => { + rcFile.addProvider('@adonisjs/redis-provider') + }) + + assert.fileContains('adonisrc.ts', `() => import('@adonisjs/redis-provider')`) + }) + + test('add provider to rc file with specific environments', async ({ assert, fs }) => { + const transformer = new CodeTransformer(fs.baseUrl) + + await transformer.updateRcFile((rcFile) => { + rcFile.addProvider('@adonisjs/redis-provider', ['console', 'repl']) + }) + + const file = await fs.contents('adonisrc.ts') + assert.snapshot(file).match() + }) + + test('should add provider even if providers property is missing', async ({ assert, fs }) => { + await fs.create( + 'adonisrc.ts', + dedent` + import { defineConfig } from '@adonisjs/core/app' + + export default defineConfig({ + typescript: true, + })` + ) + + const transformer = new CodeTransformer(fs.baseUrl) + + await transformer.updateRcFile((rcFile) => { + rcFile.addProvider('@adonisjs/redis-provider') + }) + + assert.fileContains('adonisrc.ts', `() => import('@adonisjs/redis-provider')`) + }) + + test('should ignore provider duplicate', async ({ assert, fs }) => { + const transformer = new CodeTransformer(fs.baseUrl) + + await transformer.updateRcFile((rcFile) => { + rcFile.addProvider('@adonisjs/redis-provider').addProvider('@adonisjs/redis-provider') + }) + + const file = await fs.contents('adonisrc.ts') + const occurrences = (file.match(/() => import\('@adonisjs\/redis-provider'\)/g) || []).length + + assert.equal(occurrences, 1) + }) + + test('should ignore provider duplicate when using different environments', async ({ + assert, + fs, + }) => { + const transformer = new CodeTransformer(fs.baseUrl) + + await transformer.updateRcFile((rcFile) => { + rcFile + .addProvider('@adonisjs/redis-provider', ['console']) + .addProvider('@adonisjs/redis-provider') + }) + + const file = await fs.contents('adonisrc.ts') + const occurrences = (file.match(/() => import\('@adonisjs\/redis-provider'\)/g) || []).length + + assert.equal(occurrences, 1) + }) + + test('do no add environments when they are all specified', async ({ assert, fs }) => { + const transformer = new CodeTransformer(fs.baseUrl) + + await transformer.updateRcFile((rcFile) => { + rcFile.addProvider('@adonisjs/redis-provider', ['console', 'repl', 'web', 'test']) + }) + + const file = await fs.contents('adonisrc.ts') + assert.snapshot(file).match() + }) +}) + +test.group('Code transformer | addMetaFile', (group) => { + group.each.setup(async ({ context }) => setupFakeAdonisproject(context.fs)) + + test('add meta files to rc file', async ({ assert, fs }) => { + const transformer = new CodeTransformer(fs.baseUrl) + + await transformer.updateRcFile((rcFile) => { + rcFile.addMetaFile('assets/**', false) + }) + + assert.fileContains('adonisrc.ts', `assets/**`) + }) + + test('add meta files to rc file with reload server', async ({ assert, fs }) => { + const transformer = new CodeTransformer(fs.baseUrl) + + await transformer.updateRcFile((rcFile) => { + rcFile.addMetaFile('assets/**', true) + }) + + const file = await fs.contents('adonisrc.ts') + assert.snapshot(file).match() + }) +}) + +test.group('Code transformer | setDirectory', (group) => { + group.each.setup(async ({ context }) => setupFakeAdonisproject(context.fs)) + + test('set directory in rc file', async ({ assert, fs }) => { + const transformer = new CodeTransformer(fs.baseUrl) + + await transformer.updateRcFile((rcFile) => { + rcFile.setDirectory('views', 'templates') + }) + + const file = await fs.contents('adonisrc.ts') + assert.snapshot(file).match() + }) + + test('set directory should overwrite if already defined', async ({ assert, fs }) => { + await fs.create( + 'adonisrc.ts', + dedent` + import { defineConfig } from '@adonisjs/core/app' + + export default defineConfig({ + directories: { + views: 'resources/views', + }, + })` + ) + + const transformer = new CodeTransformer(fs.baseUrl) + + await transformer.updateRcFile((rcFile) => { + rcFile.setDirectory('views', 'templates') + }) + + assert.fileContains('adonisrc.ts', `templates`) + }) +}) + +test.group('Code transformer | setCommandAlias', (group) => { + group.each.setup(async ({ context }) => setupFakeAdonisproject(context.fs)) + + test('set command alias in rc file', async ({ assert, fs }) => { + const transformer = new CodeTransformer(fs.baseUrl) + + await transformer.updateRcFile((rcFile) => { + rcFile.setCommandAlias('migrate', 'migration:run') + }) + + const file = await fs.contents('adonisrc.ts') + assert.snapshot(file).match() + }) + + test('set commandAlias should overwrite if already defined', async ({ assert, fs }) => { + await fs.create( + 'adonisrc.ts', + dedent` + import { defineConfig } from '@adonisjs/core/app' + + export default defineConfig({ + commandsAliases: { + migrate: 'migration:run', + }, + })` + ) + + const transformer = new CodeTransformer(fs.baseUrl) + + await transformer.updateRcFile((rcFile) => { + rcFile.setCommandAlias('migrate', 'migration:run --force') + }) + + assert.fileContains('adonisrc.ts', `migration:run --force`) + }) +}) + +test.group('Code transformer | addSuite', (group) => { + group.each.setup(async ({ context }) => setupFakeAdonisproject(context.fs)) + + test('add a new test suite to the rcFile', async ({ assert, fs }) => { + await fs.create( + 'adonisrc.ts', + dedent` + import { defineConfig } from '@adonisjs/core/build/standalone' + + export default defineConfig({ + commands: [], + }) + ` + ) + + const transformer = new CodeTransformer(fs.baseUrl) + + await transformer.updateRcFile((rcFile) => { + rcFile.addSuite('unit', 'test/unit') + }) + + assert.fileContains('adonisrc.ts', `name: 'unit'`) + }) + + test('should ignore suite duplicate', async ({ assert, fs }) => { + await fs.create( + 'adonisrc.ts', + dedent` + import { defineConfig } from '@adonisjs/core/build/standalone' + + export default defineConfig({ + commands: [], + tests: { + suites: [ + { + name: 'unit', + files: ['test/unit'], + }, + ], + }, + }) + ` + ) + + const transformer = new CodeTransformer(fs.baseUrl) + + await transformer.updateRcFile((rcFile) => { + rcFile.addSuite('unit', 'nope') + }) + + const file = await fs.contents('adonisrc.ts') + assert.include(file, `name: 'unit'`) + assert.notInclude(file, `nope`) + }) +}) + +test.group('Code transformer | addPreloadFile', (group) => { + group.each.setup(async ({ context }) => setupFakeAdonisproject(context.fs)) + + test('add preload file', async ({ assert, fs }) => { + const transformer = new CodeTransformer(fs.baseUrl) + + await transformer.updateRcFile((rcFile) => { + rcFile.addPreloadFile('#start/foo.js') + }) + + assert.fileContains('adonisrc.ts', `'#start/foo.js'`) + }) + + test('add preload file with specific environments', async ({ assert, fs }) => { + const transformer = new CodeTransformer(fs.baseUrl) + + await transformer.updateRcFile((rcFile) => { + rcFile.addPreloadFile('#start/foo.js', ['console', 'repl']) + }) + + const file = await fs.contents('adonisrc.ts') + assert.snapshot(file).match() + }) + + test('do not add preload file when already defined', async ({ assert, fs }) => { + const transformer = new CodeTransformer(fs.baseUrl) + + await transformer.updateRcFile((rcFile) => { + rcFile.addPreloadFile('#start/foo.js').addPreloadFile('#start/foo.js') + }) + + const file = await fs.contents('adonisrc.ts') + const occurrences = (file.match(/'#start\/foo\.js'/g) || []).length + + assert.equal(occurrences, 1) + }) +}) diff --git a/tests/fixtures/adonisrc.txt b/tests/fixtures/adonisrc.txt new file mode 100644 index 0000000..a84a4fb --- /dev/null +++ b/tests/fixtures/adonisrc.txt @@ -0,0 +1,28 @@ +import { defineConfig } from '@adonisjs/core/app' + +export default defineConfig({ + typescript: true, + preloads: [ + () => import('./start/routes.ts'), + { + file: () => import('./start/ace.ts'), + environment: ['console'], + }, + ], + providers: [ + () => import('@adonisjs/core/providers/app_provider'), + { + file: () => import('@adonisjs/core/providers/repl_provider'), + environment: ['repl'], + } + ], + metaFiles: [ + { + pattern: 'public/**', + reloadServer: true + }, + ], + commands: [ + () => import('@adonisjs/core/commands') + ] +}) diff --git a/tests/fixtures/env.txt b/tests/fixtures/env.txt new file mode 100644 index 0000000..361cc1e --- /dev/null +++ b/tests/fixtures/env.txt @@ -0,0 +1,6 @@ +import { Env } from '@adonisjs/core/env' + +export default await Env.create(new URL('../', import.meta.url), { + NODE_ENV: Env.schema.enum(['development', 'production', 'test'] as const), + PORT: Env.schema.number(), +}) diff --git a/tests/fixtures/kernel.txt b/tests/fixtures/kernel.txt new file mode 100644 index 0000000..9823edd --- /dev/null +++ b/tests/fixtures/kernel.txt @@ -0,0 +1,16 @@ +import router from '@adonisjs/core/services/router' +import server from '@adonisjs/core/services/server' + +server.errorHandler(() => import('#exceptions/handler')) + +server.use([ + () => import('#middleware/container_bindings_middleware'), + () => import('@adonisjs/session/session_middleware'), +]) + +router.use([ + () => import('@adonisjs/core/bodyparser_middleware'), + () => import('@adonisjs/shield/shield_middleware'), +]) + +export const middleware = router.named({}) From de3d91a6cedd085b67fbcbe023be7d0dd22ea969 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Mon, 21 Aug 2023 13:51:32 +0530 Subject: [PATCH 079/131] refactor: rename code_transformer file name --- package.json | 2 +- src/code_transformer/{code_transformer.ts => main.ts} | 0 tests/code_transformer.spec.ts | 2 +- tsconfig.json | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename src/code_transformer/{code_transformer.ts => main.ts} (100%) diff --git a/package.json b/package.json index 8a81c73..d363db2 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ ], "exports": { ".": "./build/index.js", - "./code_transformer": "./build/src/code_transformer/code_transformer.js", + "./code_transformer": "./build/src/code_transformer/main.js", "./types": "./build/src/types.js" }, "scripts": { diff --git a/src/code_transformer/code_transformer.ts b/src/code_transformer/main.ts similarity index 100% rename from src/code_transformer/code_transformer.ts rename to src/code_transformer/main.ts diff --git a/tests/code_transformer.spec.ts b/tests/code_transformer.spec.ts index 60d56f1..d9aefc5 100644 --- a/tests/code_transformer.spec.ts +++ b/tests/code_transformer.spec.ts @@ -3,7 +3,7 @@ import { test } from '@japa/runner' import { readFile } from 'node:fs/promises' import type { FileSystem } from '@japa/file-system' -import { CodeTransformer } from '../src/code_transformer/code_transformer.js' +import { CodeTransformer } from '../src/code_transformer/main.js' async function setupFakeAdonisproject(fs: FileSystem) { await Promise.all([ diff --git a/tsconfig.json b/tsconfig.json index 2039043..ad0cc44 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,4 +4,4 @@ "rootDir": "./", "outDir": "./build" } -} \ No newline at end of file +} From c8d96500976387a2dcc120957c2813d1fba36665 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Mon, 21 Aug 2023 13:53:37 +0530 Subject: [PATCH 080/131] chore(release): 6.1.3-18 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d363db2..029c1fd 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@adonisjs/assembler", "description": "Provides utilities to run AdonisJS development server and build project for production", - "version": "6.1.3-17", + "version": "6.1.3-18", "engines": { "node": ">=18.16.0" }, From 107839dd0b09f9f3f44e4654c3128d3e725eb2b6 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Tue, 22 Aug 2023 10:59:00 +0530 Subject: [PATCH 081/131] chore: update dependencies --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 029c1fd..03951ae 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "quick:test": "node --enable-source-maps --loader=ts-node/esm bin/test.ts" }, "devDependencies": { - "@adonisjs/application": "7.1.2-9", + "@adonisjs/application": "^7.1.2-12", "@adonisjs/eslint-config": "^1.1.8", "@adonisjs/prettier-config": "^1.1.8", "@adonisjs/tsconfig": "^1.1.8", From b48f406c40436f4a2796489148a532a578b0bf6a Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Tue, 22 Aug 2023 12:12:19 +0530 Subject: [PATCH 082/131] refactor: format code as per default formatting settings --- src/code_transformer/main.ts | 13 +- src/code_transformer/rc_file_transformer.ts | 11 +- .../code_transformer.spec.ts.cjs | 622 ++++++------------ 3 files changed, 217 insertions(+), 429 deletions(-) diff --git a/src/code_transformer/main.ts b/src/code_transformer/main.ts index f1401a7..2b565e0 100644 --- a/src/code_transformer/main.ts +++ b/src/code_transformer/main.ts @@ -28,6 +28,15 @@ export class CodeTransformer { */ #project: Project + /** + * Settings to use when persisting files + */ + #editorSettings = { + indentSize: 2, + convertTabsToSpaces: true, + trimTrailingWhitespace: true, + } + constructor(cwd: URL) { this.#cwd = cwd this.#project = new Project({ @@ -139,7 +148,7 @@ export class CodeTransformer { } } - file.formatText() + file.formatText(this.#editorSettings) await file.save() } @@ -187,7 +196,7 @@ export class CodeTransformer { }) } - file.formatText() + file.formatText(this.#editorSettings) await file.save() } } diff --git a/src/code_transformer/rc_file_transformer.ts b/src/code_transformer/rc_file_transformer.ts index 112c237..b81c101 100644 --- a/src/code_transformer/rc_file_transformer.ts +++ b/src/code_transformer/rc_file_transformer.ts @@ -18,6 +18,15 @@ export class RcFileTransformer { #cwd: URL #project: Project + /** + * Settings to use when persisting files + */ + #editorSettings = { + indentSize: 2, + convertTabsToSpaces: true, + trimTrailingWhitespace: true, + } + constructor(cwd: URL, project: Project) { this.#cwd = cwd this.#project = project @@ -326,7 +335,7 @@ export class RcFileTransformer { */ save() { const file = this.#getRcFileOrThrow() - file.formatText() + file.formatText(this.#editorSettings) return file.save() } } diff --git a/tests/__snapshots__/code_transformer.spec.ts.cjs b/tests/__snapshots__/code_transformer.spec.ts.cjs index 02c8556..a332b5c 100644 --- a/tests/__snapshots__/code_transformer.spec.ts.cjs +++ b/tests/__snapshots__/code_transformer.spec.ts.cjs @@ -1,283 +1,19 @@ -exports[`Code transformer > set correct position when defined 1`] = `"import router from '@adonisjs/core/services/router' -import server from '@adonisjs/core/services/server' - -server.errorHandler(() => import('#exceptions/handler')) - -server.use([ - () => import('#foo/middleware.js'), - () => import('@adonisjs/static/static_middleware'), - () => import('#middleware/container_bindings_middleware'), - () => import('@adonisjs/session/session_middleware'), - () => import('#foo/middleware2.js') -]) - -router.use([ - () => import('@adonisjs/core/bodyparser_middleware'), - () => import('@adonisjs/shield/shield_middleware'), -]) - -export const middleware = router.named({}) -"` - -exports[`Code transformer > add a route middleware 1`] = `"import router from '@adonisjs/core/services/router' -import server from '@adonisjs/core/services/server' - -server.errorHandler(() => import('#exceptions/handler')) - -server.use([ - () => import('#middleware/container_bindings_middleware'), - () => import('@adonisjs/session/session_middleware'), -]) - -router.use([ - () => import('#foo/bar.js'), - () => import('@adonisjs/core/bodyparser_middleware'), - () => import('@adonisjs/shield/shield_middleware'), - () => import('@adonisjs/random_middleware') -]) - -export const middleware = router.named({}) -"` - -exports[`Code transformer > add route and server middleware 1`] = `"import router from '@adonisjs/core/services/router' -import server from '@adonisjs/core/services/server' - -server.errorHandler(() => import('#exceptions/handler')) - -server.use([ - () => import('#middleware/container_bindings_middleware'), - () => import('@adonisjs/session/session_middleware'), - () => import('@adonisjs/random_middleware') -]) - -router.use([ - () => import('#foo/bar.js'), - () => import('@adonisjs/core/bodyparser_middleware'), - () => import('@adonisjs/shield/shield_middleware'), -]) - -export const middleware = router.named({}) -"` - -exports[`Code transformer > add leading comment 1`] = `"import { Env } from '@adonisjs/core/env' - -export default await Env.create(new URL('../', import.meta.url), { - NODE_ENV: Env.schema.enum(['development', 'production', 'test'] as const), - PORT: Env.schema.number(), - - /* - |---------------------------------------------------------- - | Redis configuration - |---------------------------------------------------------- - */ - REDIS_HOST: Env.schema.string.optional(), - REDIS_PORT: Env.schema.number() -}) -"` - -exports[`Code transformer > add provider to rc file with specific environments 1`] = `"import { defineConfig } from '@adonisjs/core/app' - -export default defineConfig({ - typescript: true, - preloads: [ - () => import('./start/routes.ts'), - { - file: () => import('./start/ace.ts'), - environment: ['console'], - }, - ], - providers: [ - () => import('@adonisjs/core/providers/app_provider'), - { - file: () => import('@adonisjs/core/providers/repl_provider'), - environment: ['repl'], - }, - { - file: () => import('@adonisjs/redis-provider'), - environment: ['console', 'repl'], - } - ], - commands: [ - () => import('@adonisjs/core/commands') - ] -}) -"` - -exports[`Code transformer > do no add environments when they are all specified 1`] = `"import { defineConfig } from '@adonisjs/core/app' - -export default defineConfig({ - typescript: true, - preloads: [ - () => import('./start/routes.ts'), - { - file: () => import('./start/ace.ts'), - environment: ['console'], - }, - ], - providers: [ - () => import('@adonisjs/core/providers/app_provider'), - { - file: () => import('@adonisjs/core/providers/repl_provider'), - environment: ['repl'], - }, - () => import('@adonisjs/redis-provider') - ], - commands: [ - () => import('@adonisjs/core/commands') - ] -}) -"` - -exports[`Code transformer > add meta files to rc file with reload server 1`] = `"import { defineConfig } from '@adonisjs/core/app' - -export default defineConfig({ - typescript: true, - preloads: [ - () => import('./start/routes.ts'), - { - file: () => import('./start/ace.ts'), - environment: ['console'], - }, - ], - providers: [ - () => import('@adonisjs/core/providers/app_provider'), - { - file: () => import('@adonisjs/core/providers/repl_provider'), - environment: ['repl'], - } - ], - metaFiles: [ - { - pattern: 'public/**', - reloadServer: true - }, - { - pattern: 'assets/**', - reloadServer: true, - } - ], - commands: [ - () => import('@adonisjs/core/commands') - ] -}) -"` - -exports[`Code transformer > set directory in rc file 1`] = `"import { defineConfig } from '@adonisjs/core/app' - -export default defineConfig({ - typescript: true, - preloads: [ - () => import('./start/routes.ts'), - { - file: () => import('./start/ace.ts'), - environment: ['console'], - }, - ], - providers: [ - () => import('@adonisjs/core/providers/app_provider'), - { - file: () => import('@adonisjs/core/providers/repl_provider'), - environment: ['repl'], - } - ], - metaFiles: [ - { - pattern: 'public/**', - reloadServer: true - }, - ], - commands: [ - () => import('@adonisjs/core/commands') - ], - directories: { - views: 'templates' - } -}) -"` - -exports[`Code transformer > set command alias in rc file 1`] = `"import { defineConfig } from '@adonisjs/core/app' - -export default defineConfig({ - typescript: true, - preloads: [ - () => import('./start/routes.ts'), - { - file: () => import('./start/ace.ts'), - environment: ['console'], - }, - ], - providers: [ - () => import('@adonisjs/core/providers/app_provider'), - { - file: () => import('@adonisjs/core/providers/repl_provider'), - environment: ['repl'], - } - ], - metaFiles: [ - { - pattern: 'public/**', - reloadServer: true - }, - ], - commands: [ - () => import('@adonisjs/core/commands') - ], - commandsAliases: { - migrate: 'migration:run' - } -}) -"` - -exports[`Code transformer | addPreloadFile > add preload file with specific environments 1`] = `"import { defineConfig } from '@adonisjs/core/app' - -export default defineConfig({ - typescript: true, - preloads: [ - () => import('./start/routes.ts'), - { - file: () => import('./start/ace.ts'), - environment: ['console'], - }, - { - file: () => import('#start/foo.js'), - environment: ['console', 'repl'], - } - ], - providers: [ - () => import('@adonisjs/core/providers/app_provider'), - { - file: () => import('@adonisjs/core/providers/repl_provider'), - environment: ['repl'], - } - ], - metaFiles: [ - { - pattern: 'public/**', - reloadServer: true - }, - ], - commands: [ - () => import('@adonisjs/core/commands') - ] -}) -"` - exports[`Code transformer | addMiddlewareToStack > set correct position when defined 1`] = `"import router from '@adonisjs/core/services/router' import server from '@adonisjs/core/services/server' server.errorHandler(() => import('#exceptions/handler')) server.use([ - () => import('#foo/middleware.js'), - () => import('@adonisjs/static/static_middleware'), - () => import('#middleware/container_bindings_middleware'), - () => import('@adonisjs/session/session_middleware'), - () => import('#foo/middleware2.js') + () => import('#foo/middleware.js'), + () => import('@adonisjs/static/static_middleware'), + () => import('#middleware/container_bindings_middleware'), + () => import('@adonisjs/session/session_middleware'), + () => import('#foo/middleware2.js') ]) router.use([ - () => import('@adonisjs/core/bodyparser_middleware'), - () => import('@adonisjs/shield/shield_middleware'), + () => import('@adonisjs/core/bodyparser_middleware'), + () => import('@adonisjs/shield/shield_middleware'), ]) export const middleware = router.named({}) @@ -289,15 +25,15 @@ import server from '@adonisjs/core/services/server' server.errorHandler(() => import('#exceptions/handler')) server.use([ - () => import('#middleware/container_bindings_middleware'), - () => import('@adonisjs/session/session_middleware'), + () => import('#middleware/container_bindings_middleware'), + () => import('@adonisjs/session/session_middleware'), ]) router.use([ - () => import('#foo/bar.js'), - () => import('@adonisjs/core/bodyparser_middleware'), - () => import('@adonisjs/shield/shield_middleware'), - () => import('@adonisjs/random_middleware') + () => import('#foo/bar.js'), + () => import('@adonisjs/core/bodyparser_middleware'), + () => import('@adonisjs/shield/shield_middleware'), + () => import('@adonisjs/random_middleware') ]) export const middleware = router.named({}) @@ -309,15 +45,15 @@ import server from '@adonisjs/core/services/server' server.errorHandler(() => import('#exceptions/handler')) server.use([ - () => import('#middleware/container_bindings_middleware'), - () => import('@adonisjs/session/session_middleware'), - () => import('@adonisjs/random_middleware') + () => import('#middleware/container_bindings_middleware'), + () => import('@adonisjs/session/session_middleware'), + () => import('@adonisjs/random_middleware') ]) router.use([ - () => import('#foo/bar.js'), - () => import('@adonisjs/core/bodyparser_middleware'), - () => import('@adonisjs/shield/shield_middleware'), + () => import('#foo/bar.js'), + () => import('@adonisjs/core/bodyparser_middleware'), + () => import('@adonisjs/shield/shield_middleware'), ]) export const middleware = router.named({}) @@ -326,181 +62,215 @@ export const middleware = router.named({}) exports[`Code transformer | defineEnvValidations > add leading comment 1`] = `"import { Env } from '@adonisjs/core/env' export default await Env.create(new URL('../', import.meta.url), { - NODE_ENV: Env.schema.enum(['development', 'production', 'test'] as const), - PORT: Env.schema.number(), - - /* - |---------------------------------------------------------- - | Redis configuration - |---------------------------------------------------------- - */ - REDIS_HOST: Env.schema.string.optional(), - REDIS_PORT: Env.schema.number() + NODE_ENV: Env.schema.enum(['development', 'production', 'test'] as const), + PORT: Env.schema.number(), + + /* + |---------------------------------------------------------- + | Redis configuration + |---------------------------------------------------------- + */ + REDIS_HOST: Env.schema.string.optional(), + REDIS_PORT: Env.schema.number() }) "` exports[`Code transformer | addProvider > add provider to rc file with specific environments 1`] = `"import { defineConfig } from '@adonisjs/core/app' export default defineConfig({ - typescript: true, - preloads: [ - () => import('./start/routes.ts'), - { - file: () => import('./start/ace.ts'), - environment: ['console'], - }, - ], - providers: [ - () => import('@adonisjs/core/providers/app_provider'), - { - file: () => import('@adonisjs/core/providers/repl_provider'), - environment: ['repl'], - }, - { - file: () => import('@adonisjs/redis-provider'), - environment: ['console', 'repl'], - } - ], - metaFiles: [ - { - pattern: 'public/**', - reloadServer: true - }, - ], - commands: [ - () => import('@adonisjs/core/commands') - ] + typescript: true, + preloads: [ + () => import('./start/routes.ts'), + { + file: () => import('./start/ace.ts'), + environment: ['console'], + }, + ], + providers: [ + () => import('@adonisjs/core/providers/app_provider'), + { + file: () => import('@adonisjs/core/providers/repl_provider'), + environment: ['repl'], + }, + { + file: () => import('@adonisjs/redis-provider'), + environment: ['console', 'repl'], + } + ], + metaFiles: [ + { + pattern: 'public/**', + reloadServer: true + }, + ], + commands: [ + () => import('@adonisjs/core/commands') + ] }) "` exports[`Code transformer | addProvider > do no add environments when they are all specified 1`] = `"import { defineConfig } from '@adonisjs/core/app' export default defineConfig({ - typescript: true, - preloads: [ - () => import('./start/routes.ts'), - { - file: () => import('./start/ace.ts'), - environment: ['console'], - }, - ], - providers: [ - () => import('@adonisjs/core/providers/app_provider'), - { - file: () => import('@adonisjs/core/providers/repl_provider'), - environment: ['repl'], - }, - () => import('@adonisjs/redis-provider') - ], - metaFiles: [ - { - pattern: 'public/**', - reloadServer: true - }, - ], - commands: [ - () => import('@adonisjs/core/commands') - ] + typescript: true, + preloads: [ + () => import('./start/routes.ts'), + { + file: () => import('./start/ace.ts'), + environment: ['console'], + }, + ], + providers: [ + () => import('@adonisjs/core/providers/app_provider'), + { + file: () => import('@adonisjs/core/providers/repl_provider'), + environment: ['repl'], + }, + () => import('@adonisjs/redis-provider') + ], + metaFiles: [ + { + pattern: 'public/**', + reloadServer: true + }, + ], + commands: [ + () => import('@adonisjs/core/commands') + ] }) "` exports[`Code transformer | addMetaFile > add meta files to rc file with reload server 1`] = `"import { defineConfig } from '@adonisjs/core/app' export default defineConfig({ - typescript: true, - preloads: [ - () => import('./start/routes.ts'), - { - file: () => import('./start/ace.ts'), - environment: ['console'], - }, - ], - providers: [ - () => import('@adonisjs/core/providers/app_provider'), - { - file: () => import('@adonisjs/core/providers/repl_provider'), - environment: ['repl'], - } - ], - metaFiles: [ - { - pattern: 'public/**', - reloadServer: true - }, - { - pattern: 'assets/**', - reloadServer: true, - } - ], - commands: [ - () => import('@adonisjs/core/commands') - ] + typescript: true, + preloads: [ + () => import('./start/routes.ts'), + { + file: () => import('./start/ace.ts'), + environment: ['console'], + }, + ], + providers: [ + () => import('@adonisjs/core/providers/app_provider'), + { + file: () => import('@adonisjs/core/providers/repl_provider'), + environment: ['repl'], + } + ], + metaFiles: [ + { + pattern: 'public/**', + reloadServer: true + }, + { + pattern: 'assets/**', + reloadServer: true, + } + ], + commands: [ + () => import('@adonisjs/core/commands') + ] }) "` exports[`Code transformer | setDirectory > set directory in rc file 1`] = `"import { defineConfig } from '@adonisjs/core/app' export default defineConfig({ - typescript: true, - preloads: [ - () => import('./start/routes.ts'), - { - file: () => import('./start/ace.ts'), - environment: ['console'], - }, - ], - providers: [ - () => import('@adonisjs/core/providers/app_provider'), - { - file: () => import('@adonisjs/core/providers/repl_provider'), - environment: ['repl'], - } - ], - metaFiles: [ - { - pattern: 'public/**', - reloadServer: true - }, - ], - commands: [ - () => import('@adonisjs/core/commands') - ], - directories: { - views: 'templates' + typescript: true, + preloads: [ + () => import('./start/routes.ts'), + { + file: () => import('./start/ace.ts'), + environment: ['console'], + }, + ], + providers: [ + () => import('@adonisjs/core/providers/app_provider'), + { + file: () => import('@adonisjs/core/providers/repl_provider'), + environment: ['repl'], } + ], + metaFiles: [ + { + pattern: 'public/**', + reloadServer: true + }, + ], + commands: [ + () => import('@adonisjs/core/commands') + ], + directories: { + views: 'templates' + } }) "` exports[`Code transformer | setCommandAlias > set command alias in rc file 1`] = `"import { defineConfig } from '@adonisjs/core/app' export default defineConfig({ - typescript: true, - preloads: [ - () => import('./start/routes.ts'), - { - file: () => import('./start/ace.ts'), - environment: ['console'], - }, - ], - providers: [ - () => import('@adonisjs/core/providers/app_provider'), - { - file: () => import('@adonisjs/core/providers/repl_provider'), - environment: ['repl'], - } - ], - metaFiles: [ - { - pattern: 'public/**', - reloadServer: true - }, - ], - commands: [ - () => import('@adonisjs/core/commands') - ], - commandsAliases: { - migrate: 'migration:run' + typescript: true, + preloads: [ + () => import('./start/routes.ts'), + { + file: () => import('./start/ace.ts'), + environment: ['console'], + }, + ], + providers: [ + () => import('@adonisjs/core/providers/app_provider'), + { + file: () => import('@adonisjs/core/providers/repl_provider'), + environment: ['repl'], + } + ], + metaFiles: [ + { + pattern: 'public/**', + reloadServer: true + }, + ], + commands: [ + () => import('@adonisjs/core/commands') + ], + commandsAliases: { + migrate: 'migration:run' + } +}) +"` + +exports[`Code transformer | addPreloadFile > add preload file with specific environments 1`] = `"import { defineConfig } from '@adonisjs/core/app' + +export default defineConfig({ + typescript: true, + preloads: [ + () => import('./start/routes.ts'), + { + file: () => import('./start/ace.ts'), + environment: ['console'], + }, + { + file: () => import('#start/foo.js'), + environment: ['console', 'repl'], + } + ], + providers: [ + () => import('@adonisjs/core/providers/app_provider'), + { + file: () => import('@adonisjs/core/providers/repl_provider'), + environment: ['repl'], } + ], + metaFiles: [ + { + pattern: 'public/**', + reloadServer: true + }, + ], + commands: [ + () => import('@adonisjs/core/commands') + ] }) "` From 2d4110aba4aa813bf5d71dabe8cbf0bef193038d Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Tue, 22 Aug 2023 12:14:35 +0530 Subject: [PATCH 083/131] chore(release): 6.1.3-19 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 03951ae..f560f89 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@adonisjs/assembler", "description": "Provides utilities to run AdonisJS development server and build project for production", - "version": "6.1.3-18", + "version": "6.1.3-19", "engines": { "node": ">=18.16.0" }, From 9537d502e9b18c7eeea64fb03ece54e2db59e709 Mon Sep 17 00:00:00 2001 From: Julien Date: Tue, 22 Aug 2023 09:31:23 +0200 Subject: [PATCH 084/131] fix: handle duplicates in `defineEnvValidations` --- src/code_transformer/main.ts | 3 +++ tests/code_transformer.spec.ts | 16 ++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/code_transformer/main.ts b/src/code_transformer/main.ts index 2b565e0..65bf787 100644 --- a/src/code_transformer/main.ts +++ b/src/code_transformer/main.ts @@ -185,6 +185,9 @@ export class CodeTransformer { * Add each variable validation */ for (const [variable, validation] of Object.entries(definition.variables)) { + const existingProperty = objectLiteralExpression.getProperty(variable) + if (existingProperty) existingProperty.remove() + objectLiteralExpression.addPropertyAssignment({ name: variable, initializer: validation, diff --git a/tests/code_transformer.spec.ts b/tests/code_transformer.spec.ts index d9aefc5..6dbbdf2 100644 --- a/tests/code_transformer.spec.ts +++ b/tests/code_transformer.spec.ts @@ -119,6 +119,22 @@ test.group('Code transformer | defineEnvValidations', (group) => { const file = await fs.contents('start/env.ts') assert.snapshot(file).match() }) + + test('should replace duplicates', async ({ assert, fs }) => { + const transformer = new CodeTransformer(fs.baseUrl) + + await transformer.defineEnvValidations({ + variables: { + NODE_ENV: 'Env.schema.string.optional()', + }, + }) + + const file = await fs.contents('start/env.ts') + const occurrences = (file.match(/NODE_ENV/g) || []).length + + assert.equal(occurrences, 1) + assert.fileContains('start/env.ts', `NODE_ENV: Env.schema.string.optional()`) + }) }) test.group('Code transformer | addCommand', (group) => { From 242c61250765e7a4ae7f3d9db94349a71b5623fa Mon Sep 17 00:00:00 2001 From: Julien Date: Tue, 22 Aug 2023 09:43:55 +0200 Subject: [PATCH 085/131] feat: handle duplicates in addMiddlewareToStack --- src/code_transformer/main.ts | 26 +++++++++++++ .../code_transformer.spec.ts.cjs | 20 ++++++++++ tests/code_transformer.spec.ts | 38 +++++++++++++++++++ 3 files changed, 84 insertions(+) diff --git a/src/code_transformer/main.ts b/src/code_transformer/main.ts index 65bf787..f588741 100644 --- a/src/code_transformer/main.ts +++ b/src/code_transformer/main.ts @@ -73,6 +73,20 @@ export class CodeTransformer { const middleware = `() => import('${middlewareEntry.path}')` + /** + * Delete the existing middleware if it exists + */ + const existingMiddleware = arrayLiteralExpression + .getElements() + .findIndex((element) => element.getText() === middleware) + + if (existingMiddleware !== -1) { + arrayLiteralExpression.removeElement(existingMiddleware) + } + + /** + * Add the middleware to the top or bottom of the array + */ if (middlewareEntry.position === 'before') { arrayLiteralExpression.insertElement(0, middleware) } else { @@ -100,6 +114,15 @@ export class CodeTransformer { throw new Error('The argument of the named middleware call is not an object literal.') } + /** + * Check if property is already defined. If so, remove it + */ + const existingProperty = namedMiddlewareObject.getProperty(middlewareEntry.name) + if (existingProperty) existingProperty.remove() + + /** + * Add the named middleware + */ const middleware = `${middlewareEntry.name}: () => import('${middlewareEntry.path}')` namedMiddlewareObject!.insertProperty(0, middleware) } @@ -185,6 +208,9 @@ export class CodeTransformer { * Add each variable validation */ for (const [variable, validation] of Object.entries(definition.variables)) { + /** + * Check if the variable is already defined. If so, remove it + */ const existingProperty = objectLiteralExpression.getProperty(variable) if (existingProperty) existingProperty.remove() diff --git a/tests/__snapshots__/code_transformer.spec.ts.cjs b/tests/__snapshots__/code_transformer.spec.ts.cjs index a332b5c..f977b0a 100644 --- a/tests/__snapshots__/code_transformer.spec.ts.cjs +++ b/tests/__snapshots__/code_transformer.spec.ts.cjs @@ -274,3 +274,23 @@ export default defineConfig({ }) "` +exports[`Code transformer | addMiddlewareToStack > override duplicates when adding named middelware 1`] = `"import router from '@adonisjs/core/services/router' +import server from '@adonisjs/core/services/server' + +server.errorHandler(() => import('#exceptions/handler')) + +server.use([ + () => import('#middleware/container_bindings_middleware'), + () => import('@adonisjs/session/session_middleware'), +]) + +router.use([ + () => import('@adonisjs/core/bodyparser_middleware'), + () => import('@adonisjs/shield/shield_middleware'), +]) + +export const middleware = router.named({ + auth: () => import('#foo/bar3.js') +}) +"` + diff --git a/tests/code_transformer.spec.ts b/tests/code_transformer.spec.ts index 6dbbdf2..4bd2204 100644 --- a/tests/code_transformer.spec.ts +++ b/tests/code_transformer.spec.ts @@ -87,6 +87,44 @@ test.group('Code transformer | addMiddlewareToStack', (group) => { assert.fileContains('start/kernel.ts', `auth: () => import('#foo/bar.js')`) assert.fileContains('start/kernel.ts', `rand: () => import('@adonisjs/random_middleware')`) }) + + test('override duplicates when adding router/server middleware', async ({ assert, fs }) => { + const transformer = new CodeTransformer(fs.baseUrl) + + await transformer.addMiddlewareToStack('router', [ + { path: '@adonisjs/core/bodyparser_middleware' }, + ]) + + await transformer.addMiddlewareToStack('server', [ + { path: '#middleware/container_bindings_middleware' }, + ]) + + const file = await fs.contents('start/kernel.ts') + const occurrences = ( + file.match(/() => import\('@adonisjs\/core\/bodyparser_middleware'\)/g) || [] + ).length + + const occurrences2 = ( + file.match(/() => import\('#middleware\/container_bindings_middleware'\)/g) || [] + ).length + + assert.equal(occurrences, 1) + assert.equal(occurrences2, 1) + }) + + test('override duplicates when adding named middelware', async ({ assert, fs }) => { + const transformer = new CodeTransformer(fs.baseUrl) + + await transformer.addMiddlewareToStack('named', [{ name: 'auth', path: '#foo/bar.js' }]) + + await transformer.addMiddlewareToStack('named', [ + { name: 'auth', path: '#foo/bar2.js' }, + { name: 'auth', path: '#foo/bar3.js' }, + ]) + + const file = await fs.contents('start/kernel.ts') + assert.snapshot(file).match() + }) }) test.group('Code transformer | defineEnvValidations', (group) => { From 9d3ff1cc764aeb40b1cb3c37272e50acdea1f847 Mon Sep 17 00:00:00 2001 From: Julien Date: Tue, 22 Aug 2023 09:48:47 +0200 Subject: [PATCH 086/131] chore(release): 6.1.3-20 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f560f89..23c6229 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@adonisjs/assembler", "description": "Provides utilities to run AdonisJS development server and build project for production", - "version": "6.1.3-19", + "version": "6.1.3-20", "engines": { "node": ">=18.16.0" }, From 52ea054e306534f0ea357806a8e96167f067d21c Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Tue, 22 Aug 2023 22:28:51 +0530 Subject: [PATCH 087/131] refactor: do not copy .adonisrc.json file --- src/bundler.ts | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/src/bundler.ts b/src/bundler.ts index cda535a..d426bdb 100644 --- a/src/bundler.ts +++ b/src/bundler.ts @@ -9,9 +9,9 @@ import slash from 'slash' import fs from 'node:fs/promises' +import { relative } from 'node:path' import type tsStatic from 'typescript' import { fileURLToPath } from 'node:url' -import { join, relative } from 'node:path' import { cliui, type Logger } from '@poppinss/cliui' import type { BundlerOptions } from './types.js' @@ -119,25 +119,6 @@ export class Bundler { await this.#copyFiles(metaFiles, outDir) } - /** - * Copies .adonisrc.json file to the destination - */ - async #copyAdonisRcFile(outDir: string) { - const existingContents = JSON.parse( - await fs.readFile(join(this.#cwdPath, '.adonisrc.json'), 'utf-8') - ) - const compiledContents = Object.assign({}, existingContents, { - typescript: false, - lastCompiledAt: new Date().toISOString(), - }) - - await fs.mkdir(outDir, { recursive: true }) - await fs.writeFile( - join(outDir, '.adonisrc.json'), - JSON.stringify(compiledContents, null, 2) + '\n' - ) - } - /** * Returns the lock file name for a given packages client */ @@ -241,12 +222,6 @@ export class Bundler { this.#logger.info('copying meta files to the output directory') await this.#copyMetaFiles(outDir, pkgFiles) - /** - * Step 6: Copy .adonisrc.json file to the build directory - */ - this.#logger.info('copying .adonisrc.json file to the output directory') - await this.#copyAdonisRcFile(outDir) - this.#logger.success('build completed') this.#logger.log('') From 352c5a066a8ec2236380fa9eb517a4237bd9dcdd Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Tue, 22 Aug 2023 22:49:09 +0530 Subject: [PATCH 088/131] test: fix breaking tests --- tests/bundler.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/bundler.spec.ts b/tests/bundler.spec.ts index 928b836..04c58b8 100644 --- a/tests/bundler.spec.ts +++ b/tests/bundler.spec.ts @@ -9,7 +9,7 @@ test.group('Bundler', () => { 'tsconfig.json', JSON.stringify({ compilerOptions: { outDir: 'build', skipLibCheck: true } }) ), - fs.create('.adonisrc.json', '{}'), + fs.create('adonisrc.ts', 'export default {}'), fs.create('package.json', '{}'), fs.create('package-lock.json', '{}'), @@ -36,8 +36,8 @@ test.group('Bundler', () => { assert.fileExists('./build/resources/views/foo.edge'), assert.fileExists('./build/resources/views/nested/bar.edge'), assert.fileExists('./build/resources/views/nested/baz.edge'), - assert.fileExists('./build/.adonisrc.json'), assert.fileExists('./build/package.json'), + assert.fileExists('./build/adonisrc.js'), assert.fileExists('./build/package-lock.json'), ]) }) From 1dec38215ad20b4cbee7c6275ae6483501e96855 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Tue, 22 Aug 2023 22:51:21 +0530 Subject: [PATCH 089/131] chore(release): 6.1.3-21 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 23c6229..c10c53c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@adonisjs/assembler", "description": "Provides utilities to run AdonisJS development server and build project for production", - "version": "6.1.3-20", + "version": "6.1.3-21", "engines": { "node": ">=18.16.0" }, From ed3efb5c7f3ae03b57c248c3b7d035ab282b3990 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Tue, 22 Aug 2023 23:23:51 +0530 Subject: [PATCH 090/131] refactor: do not modify source file on duplicate entries --- src/code_transformer/main.ts | 83 +++++++++++-------- .../code_transformer.spec.ts.cjs | 20 +++++ tests/code_transformer.spec.ts | 11 ++- 3 files changed, 77 insertions(+), 37 deletions(-) diff --git a/src/code_transformer/main.ts b/src/code_transformer/main.ts index f588741..e670eaa 100644 --- a/src/code_transformer/main.ts +++ b/src/code_transformer/main.ts @@ -76,21 +76,19 @@ export class CodeTransformer { /** * Delete the existing middleware if it exists */ - const existingMiddleware = arrayLiteralExpression + const existingMiddlewareIndex = arrayLiteralExpression .getElements() .findIndex((element) => element.getText() === middleware) - if (existingMiddleware !== -1) { - arrayLiteralExpression.removeElement(existingMiddleware) - } - - /** - * Add the middleware to the top or bottom of the array - */ - if (middlewareEntry.position === 'before') { - arrayLiteralExpression.insertElement(0, middleware) - } else { - arrayLiteralExpression.addElement(middleware) + if (existingMiddlewareIndex === -1) { + /** + * Add the middleware to the top or bottom of the array + */ + if (middlewareEntry.position === 'before') { + arrayLiteralExpression.insertElement(0, middleware) + } else { + arrayLiteralExpression.addElement(middleware) + } } } @@ -98,7 +96,9 @@ export class CodeTransformer { * Add a new middleware to the named middleware of the given file */ #addToNamedMiddleware(file: SourceFile, middlewareEntry: AddMiddlewareEntry) { - if (!middlewareEntry.name) throw new Error('Named middleware requires a name.') + if (!middlewareEntry.name) { + throw new Error('Named middleware requires a name.') + } const callArguments = file .getVariableDeclarationOrThrow('middleware') @@ -118,20 +118,22 @@ export class CodeTransformer { * Check if property is already defined. If so, remove it */ const existingProperty = namedMiddlewareObject.getProperty(middlewareEntry.name) - if (existingProperty) existingProperty.remove() - - /** - * Add the named middleware - */ - const middleware = `${middlewareEntry.name}: () => import('${middlewareEntry.path}')` - namedMiddlewareObject!.insertProperty(0, middleware) + if (!existingProperty) { + /** + * Add the named middleware + */ + const middleware = `${middlewareEntry.name}: () => import('${middlewareEntry.path}')` + namedMiddlewareObject!.insertProperty(0, middleware) + } } /** * Write a leading comment */ #addLeadingComment(writer: CodeBlockWriter, comment?: string) { - if (!comment) return writer.blankLine() + if (!comment) { + return writer.blankLine() + } return writer .blankLine() @@ -202,7 +204,7 @@ export class CodeTransformer { throw new Error(`The second argument of Env.create is not an object literal.`) } - let firstAdded = false + let shouldAddComment = true /** * Add each variable validation @@ -212,17 +214,32 @@ export class CodeTransformer { * Check if the variable is already defined. If so, remove it */ const existingProperty = objectLiteralExpression.getProperty(variable) - if (existingProperty) existingProperty.remove() - - objectLiteralExpression.addPropertyAssignment({ - name: variable, - initializer: validation, - leadingTrivia: (writer) => { - if (firstAdded) return - firstAdded = true - return this.#addLeadingComment(writer, definition.leadingComment) - }, - }) + + /** + * Do not add leading comment if one or more properties + * already exists + */ + if (existingProperty) { + shouldAddComment = false + } + + /** + * Add property only when the property does not exist + */ + if (!existingProperty) { + objectLiteralExpression.addPropertyAssignment({ + name: variable, + initializer: validation, + leadingTrivia: (writer) => { + if (!shouldAddComment) { + return + } + + shouldAddComment = false + return this.#addLeadingComment(writer, definition.leadingComment) + }, + }) + } } file.formatText(this.#editorSettings) diff --git a/tests/__snapshots__/code_transformer.spec.ts.cjs b/tests/__snapshots__/code_transformer.spec.ts.cjs index f977b0a..ab07ee9 100644 --- a/tests/__snapshots__/code_transformer.spec.ts.cjs +++ b/tests/__snapshots__/code_transformer.spec.ts.cjs @@ -294,3 +294,23 @@ export const middleware = router.named({ }) "` +exports[`Code transformer | addMiddlewareToStack > do not add duplicate named middleware 1`] = `"import router from '@adonisjs/core/services/router' +import server from '@adonisjs/core/services/server' + +server.errorHandler(() => import('#exceptions/handler')) + +server.use([ + () => import('#middleware/container_bindings_middleware'), + () => import('@adonisjs/session/session_middleware'), +]) + +router.use([ + () => import('@adonisjs/core/bodyparser_middleware'), + () => import('@adonisjs/shield/shield_middleware'), +]) + +export const middleware = router.named({ + auth: () => import('#foo/bar.js') +}) +"` + diff --git a/tests/code_transformer.spec.ts b/tests/code_transformer.spec.ts index 4bd2204..91431c6 100644 --- a/tests/code_transformer.spec.ts +++ b/tests/code_transformer.spec.ts @@ -88,7 +88,7 @@ test.group('Code transformer | addMiddlewareToStack', (group) => { assert.fileContains('start/kernel.ts', `rand: () => import('@adonisjs/random_middleware')`) }) - test('override duplicates when adding router/server middleware', async ({ assert, fs }) => { + test('do not add duplicate router/server middleware', async ({ assert, fs }) => { const transformer = new CodeTransformer(fs.baseUrl) await transformer.addMiddlewareToStack('router', [ @@ -112,7 +112,7 @@ test.group('Code transformer | addMiddlewareToStack', (group) => { assert.equal(occurrences2, 1) }) - test('override duplicates when adding named middelware', async ({ assert, fs }) => { + test('do not add duplicate named middleware', async ({ assert, fs }) => { const transformer = new CodeTransformer(fs.baseUrl) await transformer.addMiddlewareToStack('named', [{ name: 'auth', path: '#foo/bar.js' }]) @@ -158,7 +158,7 @@ test.group('Code transformer | defineEnvValidations', (group) => { assert.snapshot(file).match() }) - test('should replace duplicates', async ({ assert, fs }) => { + test('do not add duplicates', async ({ assert, fs }) => { const transformer = new CodeTransformer(fs.baseUrl) await transformer.defineEnvValidations({ @@ -171,7 +171,10 @@ test.group('Code transformer | defineEnvValidations', (group) => { const occurrences = (file.match(/NODE_ENV/g) || []).length assert.equal(occurrences, 1) - assert.fileContains('start/env.ts', `NODE_ENV: Env.schema.string.optional()`) + assert.fileContains( + 'start/env.ts', + `Env.schema.enum(['development', 'production', 'test'] as const)` + ) }) }) From 74a9298d7d5b071ea048cd44c20cacbbe5484084 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Tue, 22 Aug 2023 23:32:13 +0530 Subject: [PATCH 091/131] chore(release): 6.1.3-22 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c10c53c..cd618c6 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@adonisjs/assembler", "description": "Provides utilities to run AdonisJS development server and build project for production", - "version": "6.1.3-21", + "version": "6.1.3-22", "engines": { "node": ">=18.16.0" }, From 9252b71641c262559dae39b3605833549f8170e3 Mon Sep 17 00:00:00 2001 From: Romain Lanz Date: Tue, 3 Oct 2023 23:08:41 +0200 Subject: [PATCH 092/131] fix(transformer): addCommand method was not using correct property name --- src/code_transformer/rc_file_transformer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/code_transformer/rc_file_transformer.ts b/src/code_transformer/rc_file_transformer.ts index b81c101..18d868c 100644 --- a/src/code_transformer/rc_file_transformer.ts +++ b/src/code_transformer/rc_file_transformer.ts @@ -172,7 +172,7 @@ export class RcFileTransformer { * Add a new command to the rcFile */ addCommand(commandPath: string) { - const commandsProperty = this.#getPropertyAssignmentInDefineConfigCall('providers', '[]') + const commandsProperty = this.#getPropertyAssignmentInDefineConfigCall('commands', '[]') const commandsArray = commandsProperty.getInitializerIfKindOrThrow( SyntaxKind.ArrayLiteralExpression ) From 63dc53c33416a88cedd0b577ad930fce6950dcd5 Mon Sep 17 00:00:00 2001 From: Julien Ripouteau Date: Tue, 3 Oct 2023 23:51:13 +0200 Subject: [PATCH 093/131] chore: pin swc version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cd618c6..286940e 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "@japa/file-system": "^2.0.0-1", "@japa/runner": "^3.0.0-6", "@japa/snapshot": "2.0.0-1", - "@swc/core": "^1.3.71", + "@swc/core": "1.3.71", "@types/node": "^20.4.5", "@types/picomatch": "^2.3.0", "c8": "^8.0.1", From ce331e22700e6e330ff06bf70602b423038854c4 Mon Sep 17 00:00:00 2001 From: Julien Ripouteau Date: Tue, 3 Oct 2023 23:51:34 +0200 Subject: [PATCH 094/131] test: update unreliable tests --- .../code_transformer.spec.ts.cjs | 40 +++++++++++++++++++ tests/code_transformer.spec.ts | 7 ++-- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/tests/__snapshots__/code_transformer.spec.ts.cjs b/tests/__snapshots__/code_transformer.spec.ts.cjs index ab07ee9..0566dcc 100644 --- a/tests/__snapshots__/code_transformer.spec.ts.cjs +++ b/tests/__snapshots__/code_transformer.spec.ts.cjs @@ -314,3 +314,43 @@ export const middleware = router.named({ }) "` +exports[`Code transformer | addCommand > add command to rc file 1`] = `"import { defineConfig } from '@adonisjs/core/app' + +export default defineConfig({ + typescript: true, + preloads: [ + () => import('./start/routes.ts'), + { + file: () => import('./start/ace.ts'), + environment: ['console'], + }, + ], + providers: [ + () => import('@adonisjs/core/providers/app_provider'), + { + file: () => import('@adonisjs/core/providers/repl_provider'), + environment: ['repl'], + } + ], + metaFiles: [ + { + pattern: 'public/**', + reloadServer: true + }, + ], + commands: [ + () => import('@adonisjs/core/commands'), + () => import('#foo/bar.js'), + () => import('#foo/bar2.js') + ] +}) +"` + +exports[`Code transformer | addCommand > should add command even if commands property is missing 1`] = `"import { defineConfig } from '@adonisjs/core/app' + +export default defineConfig({ + typescript: true, + commands: [() => import('#foo/bar.js')] +}) +"` + diff --git a/tests/code_transformer.spec.ts b/tests/code_transformer.spec.ts index 91431c6..ba2c9fb 100644 --- a/tests/code_transformer.spec.ts +++ b/tests/code_transformer.spec.ts @@ -188,8 +188,8 @@ test.group('Code transformer | addCommand', (group) => { rcFile.addCommand('#foo/bar.js').addCommand('#foo/bar2.js') }) - assert.fileContains('adonisrc.ts', `() => import('#foo/bar.js')`) - assert.fileContains('adonisrc.ts', `() => import('#foo/bar2.js')`) + const file = await fs.contents('adonisrc.ts') + assert.snapshot(file).match() }) test('add command should not add duplicate', async ({ assert, fs }) => { @@ -222,7 +222,8 @@ test.group('Code transformer | addCommand', (group) => { rcFile.addCommand('#foo/bar.js') }) - assert.fileContains('adonisrc.ts', `() => import('#foo/bar.js')`) + const file = await fs.contents('adonisrc.ts') + assert.snapshot(file).match() }) }) From 2215c38d4aa9f351371eb7968fbebb076605379f Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Tue, 17 Oct 2023 15:09:56 +0530 Subject: [PATCH 095/131] chore: update dependencies --- package.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 286940e..5ccc0c8 100644 --- a/package.json +++ b/package.json @@ -33,17 +33,17 @@ "quick:test": "node --enable-source-maps --loader=ts-node/esm bin/test.ts" }, "devDependencies": { - "@adonisjs/application": "^7.1.2-12", + "@adonisjs/application": "^8.0.0-0", "@adonisjs/eslint-config": "^1.1.8", "@adonisjs/prettier-config": "^1.1.8", "@adonisjs/tsconfig": "^1.1.8", "@commitlint/cli": "^17.6.7", "@commitlint/config-conventional": "^17.6.7", - "@japa/assert": "^2.0.0-1", - "@japa/file-system": "^2.0.0-1", - "@japa/runner": "^3.0.0-6", - "@japa/snapshot": "2.0.0-1", - "@swc/core": "1.3.71", + "@japa/assert": "^2.0.0", + "@japa/file-system": "^2.0.0", + "@japa/runner": "^3.0.2", + "@japa/snapshot": "^2.0.0", + "@swc/core": "1.3.82", "@types/node": "^20.4.5", "@types/picomatch": "^2.3.0", "c8": "^8.0.1", @@ -60,17 +60,17 @@ "typescript": "^5.1.6" }, "dependencies": { - "@adonisjs/env": "^4.2.0-3", - "@poppinss/chokidar-ts": "^4.1.0-6", - "@poppinss/cliui": "^6.1.1-3", + "@adonisjs/env": "^4.2.0-6", + "@poppinss/chokidar-ts": "^4.1.0", + "@poppinss/cliui": "^6.2.0", "cpy": "^10.1.0", - "execa": "^7.1.1", + "execa": "^8.0.1", "fast-glob": "^3.3.1", "get-port": "^7.0.0", "junk": "^4.0.1", "picomatch": "^2.3.1", "slash": "^5.1.0", - "ts-morph": "^19.0.0" + "ts-morph": "^20.0.0" }, "peerDependencies": { "typescript": "^4.0.0 || ^5.0.0" From 3eaea8040509cbac140fc5c425a0fe6526f8f06f Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Tue, 17 Oct 2023 15:15:36 +0530 Subject: [PATCH 096/131] chore: use tsup for bundling --- package.json | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 5ccc0c8..225b07f 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "lint": "eslint . --ext=.ts", "clean": "del-cli build", "typecheck": "tsc --noEmit", - "compile": "npm run lint && npm run clean && tsc", + "compile": "npm run lint && npm run clean && tsup-node", "build": "npm run compile", "release": "np", "version": "npm run build", @@ -57,6 +57,7 @@ "p-event": "^6.0.0", "prettier": "^3.0.0", "ts-node": "^10.9.1", + "tsup": "^7.2.0", "typescript": "^5.1.6" }, "dependencies": { @@ -122,5 +123,17 @@ "src/test_runner.ts", "src/assets_dev_server.ts" ] + }, + "tsup": { + "entry": [ + "./index.ts", + "./src/code_transformer/main.ts", + "./src/types.ts" + ], + "outDir": "./build", + "clean": true, + "format": "esm", + "dts": true, + "target": "esnext" } } From 8872ef537e2b97cac21aa515cfc58740f9735756 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Tue, 17 Oct 2023 15:19:19 +0530 Subject: [PATCH 097/131] chore: update the list of files to publish --- package.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/package.json b/package.json index 225b07f..7a978d3 100644 --- a/package.json +++ b/package.json @@ -8,9 +8,7 @@ "main": "build/index.js", "type": "module", "files": [ - "build/src", - "build/index.d.ts", - "build/index.js" + "build" ], "exports": { ".": "./build/index.js", From ff1a009093abcad5a972e15ac79d9cef44a8e6b7 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Tue, 17 Oct 2023 15:27:45 +0530 Subject: [PATCH 098/131] chore(release): 6.1.3-24 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7a978d3..ecbd3e4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@adonisjs/assembler", "description": "Provides utilities to run AdonisJS development server and build project for production", - "version": "6.1.3-22", + "version": "6.1.3-24", "engines": { "node": ">=18.16.0" }, From 2d6477d8d644fbac1b665805a0f3d5a08d7c5aff Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Tue, 17 Oct 2023 17:24:25 +0530 Subject: [PATCH 099/131] feat: display time taken to start the HTTP server process --- package.json | 2 ++ src/dev_server.ts | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/package.json b/package.json index ecbd3e4..0e96dbd 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "@swc/core": "1.3.82", "@types/node": "^20.4.5", "@types/picomatch": "^2.3.0", + "@types/pretty-hrtime": "^1.0.1", "c8": "^8.0.1", "cross-env": "^7.0.3", "dedent": "^1.5.1", @@ -68,6 +69,7 @@ "get-port": "^7.0.0", "junk": "^4.0.1", "picomatch": "^2.3.1", + "pretty-hrtime": "^1.0.3", "slash": "^5.1.0", "ts-morph": "^20.0.0" }, diff --git a/src/dev_server.ts b/src/dev_server.ts index 6007799..d80a1ac 100644 --- a/src/dev_server.ts +++ b/src/dev_server.ts @@ -16,6 +16,7 @@ import type { Watcher } from '@poppinss/chokidar-ts' import type { DevServerOptions } from './types.js' import { AssetsDevServer } from './assets_dev_server.js' import { getPort, isDotEnvFile, isRcFile, runNode, watch } from './helpers.js' +import prettyHrtime from 'pretty-hrtime' /** * Instance of CLIUI @@ -101,6 +102,7 @@ export class DevServer { * Starts the HTTP server */ #startHTTPServer(port: string, mode: 'blocking' | 'nonblocking') { + let initialTime = process.hrtime() this.#httpServer = runNode(this.#cwd, { script: this.#scriptFile, env: { PORT: port, ...this.#options.env }, @@ -110,6 +112,8 @@ export class DevServer { this.#httpServer.on('message', (message) => { if (this.#isAdonisJSReadyMessage(message)) { + const readyAt = process.hrtime(initialTime) + ui.sticker() .useColors(this.#colors) .useRenderer(this.#logger.getRenderer()) @@ -119,6 +123,7 @@ export class DevServer { `${this.#isWatching ? 'enabled' : 'disabled'}` )}` ) + .add(`Ready in: ${this.#colors.cyan(prettyHrtime(readyAt))}`) .render() } }) @@ -129,6 +134,8 @@ export class DevServer { this.#onClose?.(result.exitCode) this.#watcher?.close() this.#assetsServer?.stop() + } else { + this.#logger.info('Underlying HTTP server closed. Still watching for changes') } }) .catch((error) => { @@ -136,6 +143,8 @@ export class DevServer { this.#onError?.(error) this.#watcher?.close() this.#assetsServer?.stop() + } else { + this.#logger.info('Underlying HTTP server died. Still watching for changes') } }) } From cca8e1f0b37da8a68bafcbddffb41806189b9dde Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Tue, 17 Oct 2023 17:28:45 +0530 Subject: [PATCH 100/131] chore(release): 6.1.3-25 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0e96dbd..8719951 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@adonisjs/assembler", "description": "Provides utilities to run AdonisJS development server and build project for production", - "version": "6.1.3-24", + "version": "6.1.3-25", "engines": { "node": ">=18.16.0" }, From e18c64ea62113042f43877cdb26412ea945bf5f4 Mon Sep 17 00:00:00 2001 From: Julien Ripouteau Date: Thu, 23 Nov 2023 05:48:34 +0100 Subject: [PATCH 101/131] feat: add `addJapaPlugin` to Codemods (#63) --- src/code_transformer/main.ts | 51 +++++++++++++++++- .../code_transformer.spec.ts.cjs | 22 ++++++++ tests/code_transformer.spec.ts | 52 +++++++++++++++++++ 3 files changed, 123 insertions(+), 2 deletions(-) diff --git a/src/code_transformer/main.ts b/src/code_transformer/main.ts index e670eaa..f8b7083 100644 --- a/src/code_transformer/main.ts +++ b/src/code_transformer/main.ts @@ -9,7 +9,15 @@ import { join } from 'node:path' import { fileURLToPath } from 'node:url' -import { CodeBlockWriter, Node, Project, SourceFile, SyntaxKind } from 'ts-morph' +import { + CodeBlockWriter, + FormatCodeSettings, + Node, + Project, + QuoteKind, + SourceFile, + SyntaxKind, +} from 'ts-morph' import { RcFileTransformer } from './rc_file_transformer.js' import type { AddMiddlewareEntry, EnvValidationDefinition } from '../types.js' @@ -31,16 +39,19 @@ export class CodeTransformer { /** * Settings to use when persisting files */ - #editorSettings = { + #editorSettings: FormatCodeSettings = { indentSize: 2, convertTabsToSpaces: true, trimTrailingWhitespace: true, + // @ts-expect-error SemicolonPreference doesn't seem to be re-exported from ts-morph + semicolons: 'remove', } constructor(cwd: URL) { this.#cwd = cwd this.#project = new Project({ tsConfigFilePath: join(fileURLToPath(this.#cwd), 'tsconfig.json'), + manipulationSettings: { quoteKind: QuoteKind.Single }, }) } @@ -177,6 +188,42 @@ export class CodeTransformer { await file.save() } + /** + * Add a new Japa plugin in the `tests/bootstrap.ts` file + */ + async addJapaPlugin( + pluginCall: string, + importDeclaration: { isNamed: boolean; module: string; identifier: string } + ) { + /** + * Get the `tests/bootstrap.ts` source file + */ + const testBootstrapUrl = fileURLToPath(new URL('./tests/bootstrap.ts', this.#cwd)) + const file = this.#project.getSourceFileOrThrow(testBootstrapUrl) + + /** + * Add the import declaration + */ + file.addImportDeclaration({ + ...(importDeclaration.isNamed + ? { namedImports: [importDeclaration.identifier] } + : { defaultImport: importDeclaration.identifier }), + moduleSpecifier: importDeclaration.module, + }) + + /** + * Insert the plugin call in the `plugins` array + */ + const pluginsArray = file + .getVariableDeclaration('plugins') + ?.getInitializerIfKind(SyntaxKind.ArrayLiteralExpression) + + if (pluginsArray) pluginsArray.addElement(pluginCall) + + file.formatText(this.#editorSettings) + await file.save() + } + /** * Add new env variable validation in the * `env.ts` file diff --git a/tests/__snapshots__/code_transformer.spec.ts.cjs b/tests/__snapshots__/code_transformer.spec.ts.cjs index 0566dcc..244c90f 100644 --- a/tests/__snapshots__/code_transformer.spec.ts.cjs +++ b/tests/__snapshots__/code_transformer.spec.ts.cjs @@ -354,3 +354,25 @@ export default defineConfig({ }) "` +exports[`Code transformer | addJapaPlugin > addJapaPlugin with named import 1`] = `" +import app from '@adonisjs/core/services/app' +import { assert } from '@japa/assert' +import { fooPlugin } from '@adonisjs/foo/plugin/japa' + +export const plugins: Config['plugins'] = [ + assert(), + fooPlugin(app) +] +"` + +exports[`Code transformer | addJapaPlugin > addJapaPlugin with default import 1`] = `" +import app from '@adonisjs/core/services/app' +import { assert } from '@japa/assert' +import fooPlugin from '@adonisjs/foo/plugin/japa' + +export const plugins: Config['plugins'] = [ + assert(), + fooPlugin() +] +"` + diff --git a/tests/code_transformer.spec.ts b/tests/code_transformer.spec.ts index ba2c9fb..317baa1 100644 --- a/tests/code_transformer.spec.ts +++ b/tests/code_transformer.spec.ts @@ -506,3 +506,55 @@ test.group('Code transformer | addPreloadFile', (group) => { assert.equal(occurrences, 1) }) }) + +test.group('Code transformer | addJapaPlugin', (group) => { + group.each.setup(async ({ context }) => setupFakeAdonisproject(context.fs)) + + test('add named import', async ({ assert, fs }) => { + await fs.create( + 'tests/bootstrap.ts', + ` + import app from '@adonisjs/core/services/app' + import { assert } from '@japa/assert' + + export const plugins: Config['plugins'] = [ + assert(), + ]` + ) + + const transformer = new CodeTransformer(fs.baseUrl) + + await transformer.addJapaPlugin('fooPlugin(app)', { + module: '@adonisjs/foo/plugin/japa', + identifier: 'fooPlugin', + isNamed: true, + }) + + const file = await fs.contents('tests/bootstrap.ts') + assert.snapshot(file).match() + }) + + test('add default import', async ({ assert, fs }) => { + await fs.create( + 'tests/bootstrap.ts', + ` + import app from '@adonisjs/core/services/app' + import { assert } from '@japa/assert' + + export const plugins: Config['plugins'] = [ + assert(), + ]` + ) + + const transformer = new CodeTransformer(fs.baseUrl) + + await transformer.addJapaPlugin('fooPlugin()', { + module: '@adonisjs/foo/plugin/japa', + identifier: 'fooPlugin', + isNamed: false, + }) + + const file = await fs.contents('tests/bootstrap.ts') + assert.snapshot(file).match() + }) +}) From ba0b6d027012d239b29935f54c2896580606ef04 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Thu, 23 Nov 2023 10:21:26 +0530 Subject: [PATCH 102/131] chore: update dependencies --- package.json | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index 8719951..857fbe2 100644 --- a/package.json +++ b/package.json @@ -31,44 +31,44 @@ "quick:test": "node --enable-source-maps --loader=ts-node/esm bin/test.ts" }, "devDependencies": { - "@adonisjs/application": "^8.0.0-0", - "@adonisjs/eslint-config": "^1.1.8", - "@adonisjs/prettier-config": "^1.1.8", - "@adonisjs/tsconfig": "^1.1.8", - "@commitlint/cli": "^17.6.7", - "@commitlint/config-conventional": "^17.6.7", - "@japa/assert": "^2.0.0", - "@japa/file-system": "^2.0.0", - "@japa/runner": "^3.0.2", - "@japa/snapshot": "^2.0.0", - "@swc/core": "1.3.82", - "@types/node": "^20.4.5", - "@types/picomatch": "^2.3.0", - "@types/pretty-hrtime": "^1.0.1", + "@adonisjs/application": "^8.0.0-2", + "@adonisjs/eslint-config": "^1.1.9", + "@adonisjs/prettier-config": "^1.1.9", + "@adonisjs/tsconfig": "^1.1.9", + "@commitlint/cli": "^18.4.3", + "@commitlint/config-conventional": "^18.4.3", + "@japa/assert": "^2.0.1", + "@japa/file-system": "^2.0.1", + "@japa/runner": "^3.1.0", + "@japa/snapshot": "^2.0.3", + "@swc/core": "^1.3.99", + "@types/node": "^20.9.4", + "@types/picomatch": "^2.3.3", + "@types/pretty-hrtime": "^1.0.3", "c8": "^8.0.1", "cross-env": "^7.0.3", "dedent": "^1.5.1", "del-cli": "^5.0.0", - "eslint": "^8.45.0", + "eslint": "^8.54.0", "github-label-sync": "^2.3.1", "husky": "^8.0.3", "np": "^8.0.4", "p-event": "^6.0.0", - "prettier": "^3.0.0", + "prettier": "^3.1.0", "ts-node": "^10.9.1", - "tsup": "^7.2.0", - "typescript": "^5.1.6" + "tsup": "^8.0.1", + "typescript": "5.2.2" }, "dependencies": { - "@adonisjs/env": "^4.2.0-6", - "@poppinss/chokidar-ts": "^4.1.0", - "@poppinss/cliui": "^6.2.0", - "cpy": "^10.1.0", + "@adonisjs/env": "^4.2.0-7", + "@poppinss/chokidar-ts": "^4.1.1", + "@poppinss/cliui": "^6.2.1", + "cpy": "^11.0.0", "execa": "^8.0.1", - "fast-glob": "^3.3.1", + "fast-glob": "^3.3.2", "get-port": "^7.0.0", "junk": "^4.0.1", - "picomatch": "^2.3.1", + "picomatch": "^3.0.1", "pretty-hrtime": "^1.0.3", "slash": "^5.1.0", "ts-morph": "^20.0.0" From a726c8dfadb4dad0ef62a69d8f753da8c999110d Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Thu, 23 Nov 2023 10:22:29 +0530 Subject: [PATCH 103/131] chore: update snapshots --- .../code_transformer.spec.ts.cjs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/__snapshots__/code_transformer.spec.ts.cjs b/tests/__snapshots__/code_transformer.spec.ts.cjs index 244c90f..9267556 100644 --- a/tests/__snapshots__/code_transformer.spec.ts.cjs +++ b/tests/__snapshots__/code_transformer.spec.ts.cjs @@ -376,3 +376,25 @@ export const plugins: Config['plugins'] = [ ] "` +exports[`Code transformer | addJapaPlugin > add named import 1`] = `" +import app from '@adonisjs/core/services/app' +import { assert } from '@japa/assert' +import { fooPlugin } from '@adonisjs/foo/plugin/japa' + +export const plugins: Config['plugins'] = [ + assert(), + fooPlugin(app) +] +"` + +exports[`Code transformer | addJapaPlugin > add default import 1`] = `" +import app from '@adonisjs/core/services/app' +import { assert } from '@japa/assert' +import fooPlugin from '@adonisjs/foo/plugin/japa' + +export const plugins: Config['plugins'] = [ + assert(), + fooPlugin() +] +"` + From 62d0251c9bef8d83e4cfde9cfe5ec776ffa3a39c Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Thu, 23 Nov 2023 10:24:18 +0530 Subject: [PATCH 104/131] chore: publish source maps and use tsc for generating types --- package.json | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 857fbe2..8a31347 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,9 @@ "main": "build/index.js", "type": "module", "files": [ - "build" + "build", + "!build/bin", + "!build/tests" ], "exports": { ".": "./build/index.js", @@ -21,7 +23,8 @@ "lint": "eslint . --ext=.ts", "clean": "del-cli build", "typecheck": "tsc --noEmit", - "compile": "npm run lint && npm run clean && tsup-node", + "precompile": "npm run lint && npm run clean", + "compile": "tsup-node && tsc --emitDeclarationOnly --declaration", "build": "npm run compile", "release": "np", "version": "npm run build", @@ -127,13 +130,13 @@ "tsup": { "entry": [ "./index.ts", - "./src/code_transformer/main.ts", - "./src/types.ts" + "./src/code_transformer/main.ts" ], "outDir": "./build", "clean": true, "format": "esm", - "dts": true, + "dts": false, + "sourcemap": true, "target": "esnext" } } From f16a1d6a99f35cfcb0af69085942007d9e0abbf6 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Thu, 23 Nov 2023 10:32:02 +0530 Subject: [PATCH 105/131] chore(release): 6.1.3-26 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8a31347..016f41a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@adonisjs/assembler", "description": "Provides utilities to run AdonisJS development server and build project for production", - "version": "6.1.3-25", + "version": "6.1.3-26", "engines": { "node": ">=18.16.0" }, From bf3d2fb98f72bb6f20fda26eff6866c1fb3b1dc5 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Thu, 23 Nov 2023 11:42:58 +0530 Subject: [PATCH 106/131] refactor: construct click friendly URL in welcome message --- src/dev_server.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/dev_server.ts b/src/dev_server.ts index d80a1ac..2012c5a 100644 --- a/src/dev_server.ts +++ b/src/dev_server.ts @@ -9,6 +9,7 @@ import picomatch from 'picomatch' import type tsStatic from 'typescript' +import prettyHrtime from 'pretty-hrtime' import { type ExecaChildProcess } from 'execa' import { cliui, type Logger } from '@poppinss/cliui' import type { Watcher } from '@poppinss/chokidar-ts' @@ -16,7 +17,6 @@ import type { Watcher } from '@poppinss/chokidar-ts' import type { DevServerOptions } from './types.js' import { AssetsDevServer } from './assets_dev_server.js' import { getPort, isDotEnvFile, isRcFile, runNode, watch } from './helpers.js' -import prettyHrtime from 'pretty-hrtime' /** * Instance of CLIUI @@ -113,11 +113,12 @@ export class DevServer { this.#httpServer.on('message', (message) => { if (this.#isAdonisJSReadyMessage(message)) { const readyAt = process.hrtime(initialTime) + const host = message.host === '0.0.0.0' ? '127.0.0.1' : message.host ui.sticker() .useColors(this.#colors) .useRenderer(this.#logger.getRenderer()) - .add(`Server address: ${this.#colors.cyan(`http://${message.host}:${message.port}`)}`) + .add(`Server address: ${this.#colors.cyan(`http://${host}:${message.port}`)}`) .add( `File system watcher: ${this.#colors.cyan( `${this.#isWatching ? 'enabled' : 'disabled'}` From 382a103cf77a86f153d5c59bc1b4185612fbc9cc Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Thu, 23 Nov 2023 11:51:04 +0530 Subject: [PATCH 107/131] chore(release): 6.1.3-27 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 016f41a..af05352 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@adonisjs/assembler", "description": "Provides utilities to run AdonisJS development server and build project for production", - "version": "6.1.3-26", + "version": "6.1.3-27", "engines": { "node": ">=18.16.0" }, From 16ad6a11a6ce66258392cb2068ec278e4da0acab Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Thu, 23 Nov 2023 12:18:56 +0530 Subject: [PATCH 108/131] refactor: display inter process message duration in the welcome message --- src/dev_server.ts | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/dev_server.ts b/src/dev_server.ts index 2012c5a..481ef99 100644 --- a/src/dev_server.ts +++ b/src/dev_server.ts @@ -77,9 +77,13 @@ export class DevServer { /** * Inspect if child process message is from AdonisJS HTTP server */ - #isAdonisJSReadyMessage( - message: unknown - ): message is { isAdonisJS: true; environment: 'web'; port: number; host: string } { + #isAdonisJSReadyMessage(message: unknown): message is { + isAdonisJS: true + environment: 'web' + port: number + host: string + duration?: [number, number] + } { return ( message !== null && typeof message === 'object' && @@ -102,7 +106,6 @@ export class DevServer { * Starts the HTTP server */ #startHTTPServer(port: string, mode: 'blocking' | 'nonblocking') { - let initialTime = process.hrtime() this.#httpServer = runNode(this.#cwd, { script: this.#scriptFile, env: { PORT: port, ...this.#options.env }, @@ -112,10 +115,10 @@ export class DevServer { this.#httpServer.on('message', (message) => { if (this.#isAdonisJSReadyMessage(message)) { - const readyAt = process.hrtime(initialTime) const host = message.host === '0.0.0.0' ? '127.0.0.1' : message.host - ui.sticker() + const displayMessage = ui + .sticker() .useColors(this.#colors) .useRenderer(this.#logger.getRenderer()) .add(`Server address: ${this.#colors.cyan(`http://${host}:${message.port}`)}`) @@ -124,8 +127,12 @@ export class DevServer { `${this.#isWatching ? 'enabled' : 'disabled'}` )}` ) - .add(`Ready in: ${this.#colors.cyan(prettyHrtime(readyAt))}`) - .render() + + if (message.duration) { + displayMessage.add(`Ready in: ${this.#colors.cyan(prettyHrtime(message.duration))}`) + } + + displayMessage.render() } }) From d7cc300a58079f30f94e9e6bdbb87d5c4d7d4581 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Thu, 23 Nov 2023 12:23:22 +0530 Subject: [PATCH 109/131] chore(release): 6.1.3-28 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index af05352..babc6f7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@adonisjs/assembler", "description": "Provides utilities to run AdonisJS development server and build project for production", - "version": "6.1.3-27", + "version": "6.1.3-28", "engines": { "node": ">=18.16.0" }, From f3e307047e4a561f1e5c5949f3e7e96468b4bd2d Mon Sep 17 00:00:00 2001 From: Julien Ripouteau Date: Tue, 28 Nov 2023 06:44:13 +0100 Subject: [PATCH 110/131] fix: metafile copying when lock file is not found (#64) Adding our own utility to copyfiles --- src/bundler.ts | 17 ++----------- src/helpers.ts | 36 +++++++++++++++++++-------- tests/bundler.spec.ts | 30 +++++++++++++++++++++++ tests/copy.spec.ts | 57 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 115 insertions(+), 25 deletions(-) create mode 100644 tests/copy.spec.ts diff --git a/src/bundler.ts b/src/bundler.ts index d426bdb..0d9e322 100644 --- a/src/bundler.ts +++ b/src/bundler.ts @@ -95,19 +95,6 @@ export class Bundler { } } - /** - * Copy files to destination directory - */ - async #copyFiles(files: string[], outDir: string) { - try { - await copyFiles(files, this.#cwdPath, outDir) - } catch (error) { - if (!error.message.includes("the file doesn't exist")) { - throw error - } - } - } - /** * Copy meta files to the output directory */ @@ -116,7 +103,7 @@ export class Bundler { .map((file) => file.pattern) .concat(additionalFilesToCopy) - await this.#copyFiles(metaFiles, outDir) + await copyFiles(metaFiles, this.#cwdPath, outDir) } /** @@ -189,7 +176,7 @@ export class Bundler { */ this.#logger.info('compiling typescript source', { suffix: 'tsc' }) const buildCompleted = await this.#runTsc(outDir) - await this.#copyFiles(['ace.js'], outDir) + await copyFiles(['ace.js'], this.#cwdPath, outDir) /** * Remove incomplete build directory when tsc build diff --git a/src/helpers.ts b/src/helpers.ts index 082d5b5..58066cd 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -7,14 +7,15 @@ * file that was distributed with this source code. */ -import cpy from 'cpy' import { isNotJunk } from 'junk' import fastGlob from 'fast-glob' import getRandomPort from 'get-port' +import { existsSync } from 'node:fs' import type tsStatic from 'typescript' import { fileURLToPath } from 'node:url' import { execaNode, execa } from 'execa' -import { isAbsolute, relative } from 'node:path' +import { copyFile, mkdir } from 'node:fs/promises' +import { dirname, isAbsolute, join, relative } from 'node:path' import { EnvLoader, EnvParser } from '@adonisjs/env' import { ConfigParser, Watcher } from '@poppinss/chokidar-ts' @@ -179,9 +180,18 @@ export async function copyFiles(files: string[], cwd: string, outDir: string) { */ const { paths, patterns } = files.reduce<{ patterns: string[]; paths: string[] }>( (result, file) => { + /** + * If file is a glob pattern, then push it to patterns + */ if (fastGlob.isDynamicPattern(file)) { result.patterns.push(file) - } else { + return result + } + + /** + * Otherwise, check if file exists and push it to paths to copy + */ + if (existsSync(join(cwd, file))) { result.paths.push(file) } @@ -198,14 +208,20 @@ export async function copyFiles(files: string[], cwd: string, outDir: string) { const filePaths = paths.concat(await fastGlob(patterns, { cwd })) /** - * Computing relative destination. This is because, cpy is buggy when - * outDir is an absolute path. + * Finally copy files to the destination by keeping the same + * directory structure and ignoring junk files */ - const destination = isAbsolute(outDir) ? relative(cwd, outDir) : outDir - debug('copying files %O to destination "%s"', filePaths, destination) + debug('copying files %O to destination "%s"', filePaths, outDir) + const copyPromises = filePaths.map(async (file) => { + const isJunkFile = !isNotJunk(file) + if (isJunkFile) return - return cpy(filePaths.filter(isNotJunk), destination, { - cwd: cwd, - flat: false, + const src = isAbsolute(file) ? file : join(cwd, file) + const dest = join(outDir, relative(cwd, src)) + + await mkdir(dirname(dest), { recursive: true }) + return copyFile(src, dest) }) + + return await Promise.all(copyPromises) } diff --git a/tests/bundler.spec.ts b/tests/bundler.spec.ts index 04c58b8..c07670e 100644 --- a/tests/bundler.spec.ts +++ b/tests/bundler.spec.ts @@ -41,4 +41,34 @@ test.group('Bundler', () => { assert.fileExists('./build/package-lock.json'), ]) }) + + test('should copy metafiles even if lock file is missing', async ({ assert, fs }) => { + await Promise.all([ + fs.create( + 'tsconfig.json', + JSON.stringify({ compilerOptions: { outDir: 'build', skipLibCheck: true } }) + ), + fs.create('adonisrc.ts', 'export default {}'), + fs.create('package.json', '{}'), + + fs.create('resources/views/app.edge', ''), + ]) + + const bundler = new Bundler(fs.baseUrl, ts, { + metaFiles: [ + { + pattern: 'resources/views/**/*.edge', + reloadServer: false, + }, + ], + }) + + await bundler.bundle(true, 'npm') + + await Promise.all([ + assert.fileExists('./build/resources/views/app.edge'), + assert.fileExists('./build/package.json'), + assert.fileExists('./build/adonisrc.js'), + ]) + }) }) diff --git a/tests/copy.spec.ts b/tests/copy.spec.ts new file mode 100644 index 0000000..7aed4af --- /dev/null +++ b/tests/copy.spec.ts @@ -0,0 +1,57 @@ +import { test } from '@japa/runner' +import { copyFiles } from '../src/helpers.js' +import { join } from 'node:path' + +test.group('Copy files', () => { + test('match file patterns', async ({ fs }) => { + await fs.create('resources/views/welcome.edge', '') + await fs.create('resources/views/about.edge', '') + await fs.create('resources/views/contact/main.edge', '') + + await fs.create('public/foo/test/a.json', '') + await fs.create('public/foo/test/b/a.json', '') + + await copyFiles( + ['resources/views/*.edge', 'public/**'], + fs.basePath, + join(fs.basePath, 'build') + ) + + await fs.exists('build/resources/views/welcome.edge') + await fs.exists('build/resources/views/about.edge') + await fs.exists('build/resources/views/contact/main.edge') + + await fs.exists('build/public/foo/test/a.json') + await fs.exists('build/public/foo/test/b/a.json') + }) + + test('copy files that are not glob patterns', async ({ fs }) => { + await fs.create('resources/views/welcome.edge', '') + await fs.create('resources/views/about.edge', '') + await fs.create('package.json', '') + + await copyFiles( + ['resources/views/welcome.edge', 'resources/views/about.edge', 'package.json'], + fs.basePath, + join(fs.basePath, 'build') + ) + + await fs.exists('build/resources/views/welcome.edge') + await fs.exists('build/resources/views/about.edge') + await fs.exists('build/package.json') + }) + + test("copy files even if one path doesn't exist", async ({ fs }) => { + await fs.create('resources/views/welcome.edge', '') + await fs.create('resources/views/about.edge', '') + + await copyFiles( + ['resources/views/welcome.edge', 'resources/views/about.edge', 'package.json'], + fs.basePath, + join(fs.basePath, 'build') + ) + + await fs.exists('build/resources/views/welcome.edge') + await fs.exists('build/resources/views/about.edge') + }) +}) From 9f8c1b6b35f50b13cb0fe130613d8d9f9f50ee91 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Tue, 28 Nov 2023 11:49:43 +0530 Subject: [PATCH 111/131] refactor: do not watch .adonisrc.json file anymore We are using a TypeScript file now and hence this file is not part of the sourcecode --- src/dev_server.ts | 4 ++-- src/helpers.ts | 7 ------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/dev_server.ts b/src/dev_server.ts index 481ef99..10856af 100644 --- a/src/dev_server.ts +++ b/src/dev_server.ts @@ -16,7 +16,7 @@ import type { Watcher } from '@poppinss/chokidar-ts' import type { DevServerOptions } from './types.js' import { AssetsDevServer } from './assets_dev_server.js' -import { getPort, isDotEnvFile, isRcFile, runNode, watch } from './helpers.js' +import { getPort, isDotEnvFile, runNode, watch } from './helpers.js' /** * Instance of CLIUI @@ -182,7 +182,7 @@ export class DevServer { * Handles a non TypeScript file change */ #handleFileChange(action: string, port: string, relativePath: string) { - if (isDotEnvFile(relativePath) || isRcFile(relativePath)) { + if (isDotEnvFile(relativePath)) { this.#clearScreen() this.#logger.log(`${this.#colors.green(action)} ${relativePath}`) this.#restartHTTPServer(port) diff --git a/src/helpers.ts b/src/helpers.ts index 58066cd..e805947 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -124,13 +124,6 @@ export function isDotEnvFile(filePath: string) { return filePath.includes('.env.') } -/** - * Check if file is .adonisrc.json file - */ -export function isRcFile(filePath: string) { - return filePath === '.adonisrc.json' -} - /** * Returns the port to use after inspect the dot-env files inside * a given directory. From 55410a34fa8909e560201fadd99fc2b7f10a8203 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Tue, 28 Nov 2023 12:00:43 +0530 Subject: [PATCH 112/131] feat: copy dot-files and dot-folders from metaFiles patterns --- package.json | 4 ++-- src/helpers.ts | 23 +++++++++++------------ tests/copy.spec.ts | 46 +++++++++++++++++++++++++++++++++------------- 3 files changed, 46 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index babc6f7..0bb75d1 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ }, "scripts": { "pretest": "npm run lint", - "test": "cross-env NODE_DEBUG=chokidar:ts c8 npm run quick:test", + "test": "c8 npm run quick:test", "lint": "eslint . --ext=.ts", "clean": "del-cli build", "typecheck": "tsc --noEmit", @@ -31,7 +31,7 @@ "sync-labels": "github-label-sync --labels .github/labels.json adonisjs/assembler", "format": "prettier --write .", "prepublishOnly": "npm run build", - "quick:test": "node --enable-source-maps --loader=ts-node/esm bin/test.ts" + "quick:test": "cross-env NODE_DEBUG=adonisjs:assembler node --enable-source-maps --loader=ts-node/esm bin/test.ts" }, "devDependencies": { "@adonisjs/application": "^8.0.0-2", diff --git a/src/helpers.ts b/src/helpers.ts index e805947..77e420f 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -7,7 +7,7 @@ * file that was distributed with this source code. */ -import { isNotJunk } from 'junk' +import { isJunk } from 'junk' import fastGlob from 'fast-glob' import getRandomPort from 'get-port' import { existsSync } from 'node:fs' @@ -198,23 +198,22 @@ export async function copyFiles(files: string[], cwd: string, outDir: string) { /** * Getting list of relative paths from glob patterns */ - const filePaths = paths.concat(await fastGlob(patterns, { cwd })) + const filePaths = paths.concat(await fastGlob(patterns, { cwd, dot: true })) /** * Finally copy files to the destination by keeping the same * directory structure and ignoring junk files */ debug('copying files %O to destination "%s"', filePaths, outDir) - const copyPromises = filePaths.map(async (file) => { - const isJunkFile = !isNotJunk(file) - if (isJunkFile) return - - const src = isAbsolute(file) ? file : join(cwd, file) - const dest = join(outDir, relative(cwd, src)) - - await mkdir(dirname(dest), { recursive: true }) - return copyFile(src, dest) - }) + const copyPromises = filePaths + .filter((file) => !isJunk(file)) + .map(async (file) => { + const src = isAbsolute(file) ? file : join(cwd, file) + const dest = join(outDir, relative(cwd, src)) + + await mkdir(dirname(dest), { recursive: true }) + return copyFile(src, dest) + }) return await Promise.all(copyPromises) } diff --git a/tests/copy.spec.ts b/tests/copy.spec.ts index 7aed4af..824514a 100644 --- a/tests/copy.spec.ts +++ b/tests/copy.spec.ts @@ -3,7 +3,7 @@ import { copyFiles } from '../src/helpers.js' import { join } from 'node:path' test.group('Copy files', () => { - test('match file patterns', async ({ fs }) => { + test('expand glob patterns and copy files to the destination', async ({ assert, fs }) => { await fs.create('resources/views/welcome.edge', '') await fs.create('resources/views/about.edge', '') await fs.create('resources/views/contact/main.edge', '') @@ -17,15 +17,15 @@ test.group('Copy files', () => { join(fs.basePath, 'build') ) - await fs.exists('build/resources/views/welcome.edge') - await fs.exists('build/resources/views/about.edge') - await fs.exists('build/resources/views/contact/main.edge') + await assert.fileExists('build/resources/views/welcome.edge') + await assert.fileExists('build/resources/views/about.edge') + await assert.fileExists('build/resources/views/contact/main.edge') - await fs.exists('build/public/foo/test/a.json') - await fs.exists('build/public/foo/test/b/a.json') + await assert.fileExists('build/public/foo/test/a.json') + await assert.fileExists('build/public/foo/test/b/a.json') }) - test('copy files that are not glob patterns', async ({ fs }) => { + test('copy relative file paths to the destination', async ({ fs, assert }) => { await fs.create('resources/views/welcome.edge', '') await fs.create('resources/views/about.edge', '') await fs.create('package.json', '') @@ -36,12 +36,12 @@ test.group('Copy files', () => { join(fs.basePath, 'build') ) - await fs.exists('build/resources/views/welcome.edge') - await fs.exists('build/resources/views/about.edge') - await fs.exists('build/package.json') + await assert.fileExists('build/resources/views/welcome.edge') + await assert.fileExists('build/resources/views/about.edge') + await assert.fileExists('build/package.json') }) - test("copy files even if one path doesn't exist", async ({ fs }) => { + test('ignore missing files at source', async ({ fs, assert }) => { await fs.create('resources/views/welcome.edge', '') await fs.create('resources/views/about.edge', '') @@ -51,7 +51,27 @@ test.group('Copy files', () => { join(fs.basePath, 'build') ) - await fs.exists('build/resources/views/welcome.edge') - await fs.exists('build/resources/views/about.edge') + await assert.fileExists('build/resources/views/welcome.edge') + await assert.fileExists('build/resources/views/about.edge') + }) + + test('ignore junk files', async ({ fs, assert }) => { + await fs.create('resources/views/welcome.edge', '') + await fs.create('resources/views/about.edge', '') + await fs.create('resources/views/.DS_Store', '') + + await copyFiles(['resources/views/*'], fs.basePath, join(fs.basePath, 'build')) + await assert.fileExists('build/resources/views/welcome.edge') + await assert.fileExists('build/resources/views/about.edge') + await assert.fileNotExists('build/resources/views/.DS_STORE') + }) + + test('glob pattern should pick dot-files and dot-folders', async ({ fs, assert }) => { + await fs.create('public/.vite/manifest.json', '') + await fs.create('public/.redirects', '') + + await copyFiles(['public/**'], fs.basePath, join(fs.basePath, 'build')) + await assert.fileExists('build/public/.vite/manifest.json') + await assert.fileExists('build/public/.redirects') }) }) From c2ebc6ae22568a588e00639e1aa94b17f7468ee1 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Tue, 28 Nov 2023 12:10:27 +0530 Subject: [PATCH 113/131] fix: junk files filtering logic --- src/helpers.ts | 22 +++++++++++----------- tests/copy.spec.ts | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/helpers.ts b/src/helpers.ts index 77e420f..7432f38 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -15,9 +15,9 @@ import type tsStatic from 'typescript' import { fileURLToPath } from 'node:url' import { execaNode, execa } from 'execa' import { copyFile, mkdir } from 'node:fs/promises' -import { dirname, isAbsolute, join, relative } from 'node:path' import { EnvLoader, EnvParser } from '@adonisjs/env' import { ConfigParser, Watcher } from '@poppinss/chokidar-ts' +import { basename, dirname, isAbsolute, join, relative } from 'node:path' import type { RunOptions, WatchOptions } from './types.js' import debug from './debug.js' @@ -198,22 +198,22 @@ export async function copyFiles(files: string[], cwd: string, outDir: string) { /** * Getting list of relative paths from glob patterns */ - const filePaths = paths.concat(await fastGlob(patterns, { cwd, dot: true })) + const filePaths = paths.concat(await fastGlob(patterns, { cwd, dot: true })).filter((file) => { + return !isJunk(basename(file)) + }) /** * Finally copy files to the destination by keeping the same * directory structure and ignoring junk files */ debug('copying files %O to destination "%s"', filePaths, outDir) - const copyPromises = filePaths - .filter((file) => !isJunk(file)) - .map(async (file) => { - const src = isAbsolute(file) ? file : join(cwd, file) - const dest = join(outDir, relative(cwd, src)) - - await mkdir(dirname(dest), { recursive: true }) - return copyFile(src, dest) - }) + const copyPromises = filePaths.map(async (file) => { + const src = isAbsolute(file) ? file : join(cwd, file) + const dest = join(outDir, relative(cwd, src)) + + await mkdir(dirname(dest), { recursive: true }) + return copyFile(src, dest) + }) return await Promise.all(copyPromises) } diff --git a/tests/copy.spec.ts b/tests/copy.spec.ts index 824514a..3339bcf 100644 --- a/tests/copy.spec.ts +++ b/tests/copy.spec.ts @@ -19,7 +19,7 @@ test.group('Copy files', () => { await assert.fileExists('build/resources/views/welcome.edge') await assert.fileExists('build/resources/views/about.edge') - await assert.fileExists('build/resources/views/contact/main.edge') + await assert.fileNotExists('build/resources/views/contact/main.edge') await assert.fileExists('build/public/foo/test/a.json') await assert.fileExists('build/public/foo/test/b/a.json') From 04ab722e12218eccede35ac38e65f9ae5b304244 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Tue, 28 Nov 2023 12:24:00 +0530 Subject: [PATCH 114/131] chore(release): 6.1.3-29 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0bb75d1..5198ec0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@adonisjs/assembler", "description": "Provides utilities to run AdonisJS development server and build project for production", - "version": "6.1.3-28", + "version": "6.1.3-29", "engines": { "node": ">=18.16.0" }, From 683198d1487e31fc77595df0b0c262433ff6476d Mon Sep 17 00:00:00 2001 From: Julien Ripouteau Date: Thu, 14 Dec 2023 08:13:52 +0100 Subject: [PATCH 115/131] feat: detect package manager --- package.json | 1 + src/bundler.ts | 56 ++++++++++++++++++++++--------------------- tests/bundler.spec.ts | 56 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index 5198ec0..01e7261 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ }, "dependencies": { "@adonisjs/env": "^4.2.0-7", + "@antfu/install-pkg": "^0.2.0", "@poppinss/chokidar-ts": "^4.1.1", "@poppinss/cliui": "^6.2.1", "cpy": "^11.0.0", diff --git a/src/bundler.ts b/src/bundler.ts index 0d9e322..3209367 100644 --- a/src/bundler.ts +++ b/src/bundler.ts @@ -13,10 +13,13 @@ import { relative } from 'node:path' import type tsStatic from 'typescript' import { fileURLToPath } from 'node:url' import { cliui, type Logger } from '@poppinss/cliui' +import { detectPackageManager } from '@antfu/install-pkg' import type { BundlerOptions } from './types.js' import { run, parseConfig, copyFiles } from './helpers.js' +type SupportedPackageManager = 'npm' | 'yarn' | 'pnpm' + /** * Instance of CLIUI */ @@ -107,31 +110,32 @@ export class Bundler { } /** - * Returns the lock file name for a given packages client + * Detect the package manager used by the project + * and return the lockfile name and install command + * related to it. */ - #getClientLockFile(client: 'npm' | 'yarn' | 'pnpm') { - switch (client) { - case 'npm': - return 'package-lock.json' - case 'yarn': - return 'yarn.lock' - case 'pnpm': - return 'pnpm-lock.yaml' + async #getPackageManager(client?: SupportedPackageManager) { + const pkgManagerInfo = { + npm: { + lockFile: 'package-lock.json', + installCommand: 'npm ci --omit="dev"', + }, + yarn: { + lockFile: 'yarn.lock', + installCommand: 'yarn install --production', + }, + pnpm: { + lockFile: 'pnpm-lock.yaml', + installCommand: 'pnpm i --prod', + }, } - } - /** - * Returns the installation command for a given packages client - */ - #getClientInstallCommand(client: 'npm' | 'yarn' | 'pnpm') { - switch (client) { - case 'npm': - return 'npm ci --omit="dev"' - case 'yarn': - return 'yarn install --production' - case 'pnpm': - return 'pnpm i --prod' + const pkgManager = client || (await detectPackageManager(this.#cwdPath)) || 'npm' + if (!['npm', 'yarn', 'pnpm'].includes(pkgManager)) { + throw new Error(`Unsupported package manager "${pkgManager}"`) } + + return pkgManagerInfo[pkgManager as SupportedPackageManager] } /** @@ -145,10 +149,7 @@ export class Bundler { /** * Bundles the application to be run in production */ - async bundle( - stopOnError: boolean = true, - client: 'npm' | 'yarn' | 'pnpm' = 'npm' - ): Promise { + async bundle(stopOnError: boolean = true, client?: SupportedPackageManager): Promise { /** * Step 1: Parse config file to get the build output directory */ @@ -205,7 +206,8 @@ export class Bundler { /** * Step 5: Copy meta files to the build directory */ - const pkgFiles = ['package.json', this.#getClientLockFile(client)] + const pkgManager = await this.#getPackageManager(client) + const pkgFiles = ['package.json', pkgManager.lockFile] this.#logger.info('copying meta files to the output directory') await this.#copyMetaFiles(outDir, pkgFiles) @@ -219,7 +221,7 @@ export class Bundler { .useRenderer(this.#logger.getRenderer()) .heading('Run the following commands to start the server in production') .add(this.#colors.cyan(`cd ${this.#getRelativeName(outDir)}`)) - .add(this.#colors.cyan(this.#getClientInstallCommand(client))) + .add(this.#colors.cyan(pkgManager.installCommand)) .add(this.#colors.cyan('node bin/server.js')) .render() diff --git a/tests/bundler.spec.ts b/tests/bundler.spec.ts index c07670e..d9c21a5 100644 --- a/tests/bundler.spec.ts +++ b/tests/bundler.spec.ts @@ -71,4 +71,60 @@ test.group('Bundler', () => { assert.fileExists('./build/adonisrc.js'), ]) }) + + test('use npm by default if not specified', async ({ assert, fs }) => { + await Promise.all([ + fs.create( + 'tsconfig.json', + JSON.stringify({ compilerOptions: { outDir: 'build', skipLibCheck: true } }) + ), + fs.create('adonisrc.ts', 'export default {}'), + fs.create('package.json', '{}'), + fs.create('package-lock.json', '{}'), + ]) + + const bundler = new Bundler(fs.baseUrl, ts, { + metaFiles: [ + { + pattern: 'resources/views/**/*.edge', + reloadServer: false, + }, + ], + }) + + await bundler.bundle(true) + + await Promise.all([ + assert.fileExists('./build/package.json'), + assert.fileExists('./build/package-lock.json'), + ]) + }) + + test('detect package manager if not specified', async ({ assert, fs }) => { + await Promise.all([ + fs.create( + 'tsconfig.json', + JSON.stringify({ compilerOptions: { outDir: 'build', skipLibCheck: true } }) + ), + fs.create('adonisrc.ts', 'export default {}'), + fs.create('package.json', '{}'), + fs.create('pnpm-lock.yaml', '{}'), + ]) + + const bundler = new Bundler(fs.baseUrl, ts, { + metaFiles: [ + { + pattern: 'resources/views/**/*.edge', + reloadServer: false, + }, + ], + }) + + await bundler.bundle(true) + + await Promise.all([ + assert.fileExists('./build/package.json'), + assert.fileExists('./build/pnpm-lock.yaml'), + ]) + }) }) From 8afda7700524bc96189773a811d9a9bd17dde26c Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Tue, 19 Dec 2023 12:16:06 +0530 Subject: [PATCH 116/131] chore: update dependencies --- package.json | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index 01e7261..a683492 100644 --- a/package.json +++ b/package.json @@ -34,39 +34,39 @@ "quick:test": "cross-env NODE_DEBUG=adonisjs:assembler node --enable-source-maps --loader=ts-node/esm bin/test.ts" }, "devDependencies": { - "@adonisjs/application": "^8.0.0-2", - "@adonisjs/eslint-config": "^1.1.9", - "@adonisjs/prettier-config": "^1.1.9", - "@adonisjs/tsconfig": "^1.1.9", + "@adonisjs/application": "^8.0.0-3", + "@adonisjs/eslint-config": "^1.2.0", + "@adonisjs/prettier-config": "^1.2.0", + "@adonisjs/tsconfig": "^1.2.0", "@commitlint/cli": "^18.4.3", "@commitlint/config-conventional": "^18.4.3", - "@japa/assert": "^2.0.1", - "@japa/file-system": "^2.0.1", - "@japa/runner": "^3.1.0", - "@japa/snapshot": "^2.0.3", - "@swc/core": "^1.3.99", - "@types/node": "^20.9.4", + "@japa/assert": "^2.1.0", + "@japa/file-system": "^2.1.0", + "@japa/runner": "^3.1.1", + "@japa/snapshot": "^2.0.4", + "@swc/core": "^1.3.101", + "@types/node": "^20.10.5", "@types/picomatch": "^2.3.3", "@types/pretty-hrtime": "^1.0.3", "c8": "^8.0.1", "cross-env": "^7.0.3", "dedent": "^1.5.1", "del-cli": "^5.0.0", - "eslint": "^8.54.0", + "eslint": "^8.56.0", "github-label-sync": "^2.3.1", "husky": "^8.0.3", - "np": "^8.0.4", + "np": "^9.2.0", "p-event": "^6.0.0", - "prettier": "^3.1.0", - "ts-node": "^10.9.1", + "prettier": "^3.1.1", + "ts-node": "^10.9.2", "tsup": "^8.0.1", - "typescript": "5.2.2" + "typescript": "^5.3.3" }, "dependencies": { "@adonisjs/env": "^4.2.0-7", - "@antfu/install-pkg": "^0.2.0", - "@poppinss/chokidar-ts": "^4.1.1", - "@poppinss/cliui": "^6.2.1", + "@antfu/install-pkg": "^0.3.1", + "@poppinss/chokidar-ts": "^4.1.3", + "@poppinss/cliui": "^6.2.3", "cpy": "^11.0.0", "execa": "^8.0.1", "fast-glob": "^3.3.2", @@ -75,7 +75,7 @@ "picomatch": "^3.0.1", "pretty-hrtime": "^1.0.3", "slash": "^5.1.0", - "ts-morph": "^20.0.0" + "ts-morph": "^21.0.1" }, "peerDependencies": { "typescript": "^4.0.0 || ^5.0.0" From ac656a2c19314bc4de73c8975b4e93c2b692d56f Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Tue, 19 Dec 2023 16:33:25 +0530 Subject: [PATCH 117/131] feat: export installPackages and detectPackageManager utilities --- src/code_transformer/main.ts | 168 ++++++++++---------- src/code_transformer/rc_file_transformer.ts | 8 +- 2 files changed, 92 insertions(+), 84 deletions(-) diff --git a/src/code_transformer/main.ts b/src/code_transformer/main.ts index f8b7083..d466b26 100644 --- a/src/code_transformer/main.ts +++ b/src/code_transformer/main.ts @@ -9,14 +9,15 @@ import { join } from 'node:path' import { fileURLToPath } from 'node:url' +import { installPackage, detectPackageManager } from '@antfu/install-pkg' import { - CodeBlockWriter, - FormatCodeSettings, Node, Project, QuoteKind, SourceFile, SyntaxKind, + CodeBlockWriter, + FormatCodeSettings, } from 'ts-morph' import { RcFileTransformer } from './rc_file_transformer.js' @@ -26,6 +27,13 @@ import type { AddMiddlewareEntry, EnvValidationDefinition } from '../types.js' * This class is responsible for updating */ export class CodeTransformer { + /** + * Exporting utilities to install package and detect + * the package manager + */ + installPackage = installPackage + detectPackageManager = detectPackageManager + /** * Directory of the adonisjs project */ @@ -55,15 +63,6 @@ export class CodeTransformer { }) } - /** - * Update the `adonisrc.ts` file - */ - async updateRcFile(callback: (transformer: RcFileTransformer) => void) { - const rcFileTransformer = new RcFileTransformer(this.#cwd, this.#project) - callback(rcFileTransformer) - await rcFileTransformer.save() - } - /** * Add a new middleware to the middleware array of the * given file @@ -155,75 +154,6 @@ export class CodeTransformer { .writeLine(`*/`) } - /** - * Define new middlewares inside the `start/kernel.ts` - * file - * - * This function is highly based on some assumptions - * and will not work if you significantly tweaked - * your `start/kernel.ts` file. - */ - async addMiddlewareToStack( - stack: 'server' | 'router' | 'named', - middleware: AddMiddlewareEntry[] - ) { - /** - * Get the `start/kernel.ts` source file - */ - const kernelUrl = fileURLToPath(new URL('./start/kernel.ts', this.#cwd)) - const file = this.#project.getSourceFileOrThrow(kernelUrl) - - /** - * Process each middleware entry - */ - for (const middlewareEntry of middleware) { - if (stack === 'named') { - this.#addToNamedMiddleware(file, middlewareEntry) - } else { - this.#addToMiddlewareArray(file!, `${stack}.use`, middlewareEntry) - } - } - - file.formatText(this.#editorSettings) - await file.save() - } - - /** - * Add a new Japa plugin in the `tests/bootstrap.ts` file - */ - async addJapaPlugin( - pluginCall: string, - importDeclaration: { isNamed: boolean; module: string; identifier: string } - ) { - /** - * Get the `tests/bootstrap.ts` source file - */ - const testBootstrapUrl = fileURLToPath(new URL('./tests/bootstrap.ts', this.#cwd)) - const file = this.#project.getSourceFileOrThrow(testBootstrapUrl) - - /** - * Add the import declaration - */ - file.addImportDeclaration({ - ...(importDeclaration.isNamed - ? { namedImports: [importDeclaration.identifier] } - : { defaultImport: importDeclaration.identifier }), - moduleSpecifier: importDeclaration.module, - }) - - /** - * Insert the plugin call in the `plugins` array - */ - const pluginsArray = file - .getVariableDeclaration('plugins') - ?.getInitializerIfKind(SyntaxKind.ArrayLiteralExpression) - - if (pluginsArray) pluginsArray.addElement(pluginCall) - - file.formatText(this.#editorSettings) - await file.save() - } - /** * Add new env variable validation in the * `env.ts` file @@ -292,4 +222,82 @@ export class CodeTransformer { file.formatText(this.#editorSettings) await file.save() } + + /** + * Define new middlewares inside the `start/kernel.ts` + * file + * + * This function is highly based on some assumptions + * and will not work if you significantly tweaked + * your `start/kernel.ts` file. + */ + async addMiddlewareToStack( + stack: 'server' | 'router' | 'named', + middleware: AddMiddlewareEntry[] + ) { + /** + * Get the `start/kernel.ts` source file + */ + const kernelUrl = fileURLToPath(new URL('./start/kernel.ts', this.#cwd)) + const file = this.#project.getSourceFileOrThrow(kernelUrl) + + /** + * Process each middleware entry + */ + for (const middlewareEntry of middleware) { + if (stack === 'named') { + this.#addToNamedMiddleware(file, middlewareEntry) + } else { + this.#addToMiddlewareArray(file!, `${stack}.use`, middlewareEntry) + } + } + + file.formatText(this.#editorSettings) + await file.save() + } + + /** + * Update the `adonisrc.ts` file + */ + async updateRcFile(callback: (transformer: RcFileTransformer) => void) { + const rcFileTransformer = new RcFileTransformer(this.#cwd, this.#project) + callback(rcFileTransformer) + await rcFileTransformer.save() + } + + /** + * Add a new Japa plugin in the `tests/bootstrap.ts` file + */ + async addJapaPlugin( + pluginCall: string, + importDeclaration: { isNamed: boolean; module: string; identifier: string } + ) { + /** + * Get the `tests/bootstrap.ts` source file + */ + const testBootstrapUrl = fileURLToPath(new URL('./tests/bootstrap.ts', this.#cwd)) + const file = this.#project.getSourceFileOrThrow(testBootstrapUrl) + + /** + * Add the import declaration + */ + file.addImportDeclaration({ + ...(importDeclaration.isNamed + ? { namedImports: [importDeclaration.identifier] } + : { defaultImport: importDeclaration.identifier }), + moduleSpecifier: importDeclaration.module, + }) + + /** + * Insert the plugin call in the `plugins` array + */ + const pluginsArray = file + .getVariableDeclaration('plugins') + ?.getInitializerIfKind(SyntaxKind.ArrayLiteralExpression) + + if (pluginsArray) pluginsArray.addElement(pluginCall) + + file.formatText(this.#editorSettings) + await file.save() + } } diff --git a/src/code_transformer/rc_file_transformer.ts b/src/code_transformer/rc_file_transformer.ts index 18d868c..2afeba0 100644 --- a/src/code_transformer/rc_file_transformer.ts +++ b/src/code_transformer/rc_file_transformer.ts @@ -1,14 +1,14 @@ import { fileURLToPath } from 'node:url' +import type { AppEnvironments } from '@adonisjs/application/types' import { - ArrayLiteralExpression, - CallExpression, Node, Project, - PropertyAssignment, SourceFile, SyntaxKind, + CallExpression, + PropertyAssignment, + ArrayLiteralExpression, } from 'ts-morph' -import type { AppEnvironments } from '@adonisjs/application/types' /** * RcFileTransformer is used to transform the `adonisrc.ts` file From 1d4ede60ea8e9f62880f7d2c6dde84b0f4c37dc2 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Tue, 19 Dec 2023 16:39:33 +0530 Subject: [PATCH 118/131] chore(release): 6.1.3-30 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a683492..5eaa9c6 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@adonisjs/assembler", "description": "Provides utilities to run AdonisJS development server and build project for production", - "version": "6.1.3-29", + "version": "6.1.3-30", "engines": { "node": ">=18.16.0" }, From 05ba15bcc4026c547741170c2ff88dff7374aaad Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Wed, 20 Dec 2023 13:00:27 +0530 Subject: [PATCH 119/131] refactor: small internal refactors and add documentation --- .github/stale.yml | 4 +- .gitignore | 1 + README.md | 390 +++++++++++++++++- package.json | 2 + src/assets_dev_server.ts | 22 +- src/bundler.ts | 73 +++- src/code_transformer/main.ts | 69 +++- src/code_transformer/rc_file_transformer.ts | 20 +- src/dev_server.ts | 41 +- src/helpers.ts | 6 +- src/test_runner.ts | 73 +++- src/types.ts | 150 +++++-- .../code_transformer.spec.ts.cjs | 45 +- tests/bundler.spec.ts | 11 +- tests/code_transformer.spec.ts | 186 ++++++++- tests/copy.spec.ts | 11 +- tests/helpers.spec.ts | 73 ++++ 17 files changed, 1039 insertions(+), 138 deletions(-) create mode 100644 tests/helpers.spec.ts diff --git a/.github/stale.yml b/.github/stale.yml index 7a6a571..f767674 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -6,10 +6,10 @@ daysUntilClose: 7 # Issues with these labels will never be considered stale exemptLabels: - - "Type: Security" + - 'Type: Security' # Label to use when marking an issue as stale -staleLabel: "Status: Abandoned" +staleLabel: 'Status: Abandoned' # Comment to post when marking an issue as stale. Set to `false` to disable markComment: > diff --git a/.gitignore b/.gitignore index 1223d04..104dae5 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ yarn.lock shrinkwrap.yaml test/__app/ test/__app +tmp diff --git a/README.md b/README.md index 6acd931..881f9d2 100644 --- a/README.md +++ b/README.md @@ -5,18 +5,398 @@ [![gh-workflow-image]][gh-workflow-url] [![npm-image]][npm-url] ![][typescript-image] [![license-image]][license-url] ## Introduction -Assembler exports the API for starting the **AdonisJS development server**, **building project for production** and **running tests** in watch mode. Assembler must be used during development only. +AdonisJS Assembler is a development toolkit used by AdonisJS to perform tasks like **starting the dev server in watch mode**, **running tests in watch mode**, and **applying codemods** to modify source files. -## Official Documentation -The documentation is available on the official website +Assembler should always be installed as a development dependency. If your project needs Assembler APIs in production, you must reconsider your approach. + +## Goals +Assembler is built around the following goals. + +- Expose a coding interface and not a user interface. In other words, Assembler will never expose any CLI commands. +- Encapsulate tasks under a single API. Instead of providing ten different utilities to run a dev server, Assembler will expose one API to run the dev server. +- House all development APIs needed by AdonisJS. Therefore, the scope of the Assembler might increase over time. + +## Dev server +You can start the HTTP server of an AdonisJS application using the `node --loader=ts-node/esm bin/server.ts` file. However, this approach has some limitations and may not provide the best DX. + +### Using a file watcher +You might be tempted to use the Node.js built-in file watcher with the `--watch` flag. However, the Node.js file watcher does not integrate with TypeScript. As a result, you will be tweaking its configuration options to get an ideal experience. + +On the other hand, the Assembler file watcher takes the following approach. + +- Parses the `tsconfig.json` file to collect the list of files that are part of your TypeScript project. As a result, if you ever want to ignore any file, you do it directly within the `tsconfig.json` file, and the watcher will pick it up. +- It uses the `metaFiles` array defined inside the `adonisrc.ts` file to watch additional files that are not `.js` or `.ts`. It may be the Edge templates, markdown files, YAML files, etc. + +### Starting the asset bundler server +If you create a full-stack application, the chances of using Webpack or Vite are high. Instead of starting your assets bundler inside a separate process, you can also rely on Assembler to start a parallel process for the assets bundler. + +The [`node ace serve` command](https://github.com/adonisjs/core/blob/next/commands/serve.ts#L88) detects the assets bundler used by your AdonisJS project and passes it to Assembler. + +Therefore, if you run the `serve` command with a `vite.config.js` file, you will notice that the Assembler will start both Vite and the AdonisJS HTTP server. + +### Picking a random port +The PORT on which an AdonisJS application should run is configured inside the `.env` file of your AdonisJS application. However, you will often start multiple projects together and have to edit the `.env` file to ensure both projects run on different ports. + +With Assembler, you do not have to edit the `.env` files since Assembler will pick a random port of your application if the configured one is already in use. + +### Usage +You may import and use the `DevServer` as follows. + +```ts +import ts from 'typescript' +import { DevServer } from '@adonisjs/assembler' + +const appRoot = new URL('./', import.meta.url) + +const devServer = new DevServer(appRoot, { + /** + * Arguments to pass to the "bin/server.ts" file + */ + scriptArgs: [], + + /** + * Arguments to pass to the Node.js CLI + */ + nodeArgs: [], + + /** + * An array of metaFiles to watch and re-start the + * HTTP server only if the "reloadServer" flag is + * true. + */ + metaFiles: [ + { + pattern: 'resources/views/**/*.edge', + reloadServer: false, + } + ], + + /** + * The assets bundler process to start + */ + assets: { + enabled: true, + name: 'vite', + cmd: 'vite', + args: [] + } +}) + +devServer.onError((error) => { + process.exitCode = 1 +}) +devServer.onClose((exitCode) => { + process.exitCode = exitCode +}) + +await devServer.runAndWatch(ts) +``` + +You may start the dev server and assets bundler dev server using the `start` method. + +```ts +await devServer.start() +``` + +## Test runner +The `TestRunner` is used to execute the `bin/test.ts` file of your AdonisJS application. Like the `DevServer`, the `TestRunner` allows you to watch for file changes and re-run the tests. The following steps are taken to re-run tests in watch mode. + +> [!NOTE] +> Read [Using a file watcher](#using-a-file-watcher) section to understand which files are watched by the file watcher. + +- If the changed file is a test file, only tests for that file will be re-run. +- Otherwise, all tests will re-run with respect to the initial filters applied when running the `node ace test` command. + +### Usage + +You may import and use the `TestRunner` as follows. + +```ts +import ts from 'typescript' +import { TestRunner } from '@adonisjs/assembler' + +const appRoot = new URL('./', import.meta.url) + +const runner = new TestRunner(appRoot, { + /** + * Arguments to pass to the "bin/test.ts" file + */ + scriptArgs: [], + + /** + * Arguments to pass to the Node.js CLI + */ + nodeArgs: [], + + /** + * An array of suites and their glob patterns + */ + suites: [ + { + name: 'unit', + files: ['tests/unit/**/*.spec.ts'] + }, + { + name: 'functional', + files: ['tests/functional/**/*.spec.ts'] + } + ], + + /** + * Initial set of filters to apply. These filters + * will be re-applied when re-running tests in + * watch mode + */ + filters: { + suites: ['unit'], + tags: ['@slow'] + } +}) + +await runner.runAndWatch(ts) +``` + +You can run tests without the watcher using the `run` method. + +```ts +await runner.run() +``` + +## Bundler +The `Bundler` is used to create the production build of an AdonisJS application. The following steps are performed to generate the build. + +- Clean up the existing build directory. +- Compile frontend assets (if an assets bundler is configured). +- Create JavaScript build using `tsc` (The TypeScript's official compiler). +- Copy the `ace.js` file to the build folder. Since the ace file ends with the `.js` extension, it is not compiled by the TypeScript compiler. +- Copy `package.json` and the **lock-file of the package manager** you are using to the `build` folder. This operation only supports `bun | npm | yarn | pnpm`. For other bundlers, you will have to copy the lock file manually. +- The end. + +### Usage +You may import and use the `Bundler` as follows. + +```ts +import ts from 'typescript' +import { Bundler } from '@adonisjs/assembler' + +const appRoot = new URL('./', import.meta.url) + +const bundler = new Bundler(appRoot, ts, { + /** + * Metafiles to copy to the build folder + */ + metaFiles: [ + { + pattern: 'resources/views/**/*.edge', + reloadServer: false, + } + ], + + /** + * The assets bundler to use to bundle the frontend + * assets + */ + assets: { + enabled: true, + name: 'vite', + cmd: 'vite', + args: ['build'] + } +}) +``` + +## Codemods +Assembler also exports certain codemods to modify the source files of an AdonisJS project to configure packages. + +The codemods relies on the defaults of AdonisJS and will not work if a project does not follow the defaults. This is an intentional limit since we only have limited time to craft codemods that work with every possible setup. + +### Usage +You may import and use the `Codemods` as follows. + +```ts +import { CodeTransformer } from '@adonisjs/assembler/code_transformer' + +const appRoot = new URL('./', import.meta.url) + +const transformer = new CodeTransformer(appRoot) +``` + +### defineEnvValidations +Define validation rules for environment variables. The method accepts a key-value pair of variables. The `key` is the env variable name, and the `value` is the validation expression as a string. + +> [!IMPORTANT] +> This codemod expects the `start/env.ts` file to exist and must have the `export default await Env.create` method call. +> +> Also, the codemod does not overwrite the existing validation rule for a given environment variable. This is done to respect in-app modifications. + +```ts +const transformer = new CodeTransformer(appRoot) + +try { + await transformer.defineEnvValidations({ + leadingComment: 'App environment variables', + variables: { + PORT: 'Env.schema.number()', + HOST: 'Env.schema.string()', + } + }) +} catch (error) { + console.error('Unable to define env validations') + console.error(error) +} +``` + +Output + +```ts +import { Env } from '@adonisjs/core/env' + +export default await Env.create(new URL('../', import.meta.url), { + PORT: Env.schema.number(), + HOST: Env.schema.string(), +}) +``` + +### addMiddlewareToStack +Register AdonisJS middleware to one of the known middleware stacks. The method accepts the middleware stack and an array of middleware to register. + +The middleware stack could be one of `server | router | named`. + +> [!IMPORTANT] +> This codemod expects the `start/kernel.ts` file to exist and must have a function call for the middleware stack for which you are trying to register a middleware. + +```ts +const transformer = new CodeTransformer(appRoot) + +try { + await transformer.addMiddlewareToStack('router', [ + { + path: '@adonisjs/core/bodyparser_middleware' + } + ]) +} catch (error) { + console.error('Unable to register middleware') + console.error(error) +} +``` + +Output + +```ts +import router from '@adonisjs/core/services/router' + +router.use([ + () => import('@adonisjs/core/bodyparser_middleware') +]) +``` + +You may define named middleware as follows. + +```ts +const transformer = new CodeTransformer(appRoot) + +try { + await transformer.addMiddlewareToStack('named', [ + { + name: 'auth', + path: '@adonisjs/auth/auth_middleware' + } + ]) +} catch (error) { + console.error('Unable to register middleware') + console.error(error) +} +``` + +### updateRcFile +Register `providers`, `commands`, define `metaFiles` and `commandAliases` to the `adonisrc.ts` file. + +> [!IMPORTANT] +> This codemod expects the `adonisrc.ts` file to exist and must have an `export default defineConfig` function call. + +```ts +const transformer = new CodeTransformer(appRoot) + +try { + await transformer.updateRcFile((rcFile) => { + rcFile + .addProvider('@adonisjs/lucid/db_provider') + .addCommand('@adonisjs/lucid/commands'), + .setCommandAlias('migrate', 'migration:run') + }) +} catch (error) { + console.error('Unable to update adonisrc.ts file') + console.error(error) +} +``` + +Output + +```ts +import { defineConfig } from '@adonisjs/core/app' + +export default defineConfig({ + commands: [ + () => import('@adonisjs/lucid/commands') + ], + providers: [ + () => import('@adonisjs/lucid/db_provider') + ], + commandAliases: { + migrate: 'migration:run' + } +}) +``` + +### addJapaPlugin +Register a Japa plugin to the `tests/bootstrap.ts` file. + +> [!IMPORTANT] +> This codemod expects the `tests/bootstrap.ts` file to exist and must have the `export const plugins: Config['plugins']` export. + +```ts +const transformer = new CodeTransformer(appRoot) + +const imports = [ + { + isNamed: false, + module: '@adonisjs/core/services/app', + identifier: 'app' + }, + { + isNamed: true, + module: '@adonisjs/session/plugins/api_client', + identifier: 'sessionApiClient' + } +] +const pluginUsage = 'sessionApiClient(app)' + +try { + await transformer.addJapaPlugin(pluginUsage, imports) +} catch (error) { + console.error('Unable to register japa plugin') + console.error(error) +} +``` + +Output + +```ts +import app from '@adonisjs/core/services/app' +import { sessionApiClient } from '@adonisjs/session/plugins/api_client' + +export const plugins: Config['plugins'] = [ + sessionApiClient(app) +] +``` ## Contributing -One of the primary goals of AdonisJS is to have a vibrant community of users and contributors who believes in the principles of the framework. +One of the primary goals of AdonisJS is to have a vibrant community of users and contributors who believe in the framework's principles. We encourage you to read the [contribution guide](https://github.com/adonisjs/.github/blob/main/docs/CONTRIBUTING.md) before contributing to the framework. ## Code of Conduct -In order to ensure that the AdonisJS community is welcoming to all, please review and abide by the [Code of Conduct](https://github.com/adonisjs/.github/blob/main/docs/CODE_OF_CONDUCT.md). +To ensure that the AdonisJS community is welcoming to all, please review and abide by the [Code of Conduct](https://github.com/adonisjs/.github/blob/main/docs/CODE_OF_CONDUCT.md). ## License AdonisJS Assembler is open-sourced software licensed under the [MIT license](LICENSE.md). diff --git a/package.json b/package.json index 5eaa9c6..bc149ab 100644 --- a/package.json +++ b/package.json @@ -122,6 +122,8 @@ "exclude": [ "tests/**", "build/**", + "bin/**", + "tmp/**", "examples/**", "src/dev_server.ts", "src/test_runner.ts", diff --git a/src/assets_dev_server.ts b/src/assets_dev_server.ts index 3131882..5721c8d 100644 --- a/src/assets_dev_server.ts +++ b/src/assets_dev_server.ts @@ -54,16 +54,6 @@ export class AssetsDevServer { const dataString = data.toString() const lines = dataString.split('\n') - /** - * Logging VITE ready in message with proper - * spaces and newlines - */ - if (dataString.includes('ready in')) { - console.log('') - console.log(dataString.trim()) - return - } - /** * Put a wrapper around vite network address log */ @@ -80,6 +70,16 @@ export class AssetsDevServer { return } + /** + * Logging VITE ready in message with proper + * spaces and newlines + */ + if (dataString.includes('ready in')) { + console.log('') + console.log(dataString.trim()) + return + } + /** * Log rest of the lines */ @@ -117,7 +117,7 @@ export class AssetsDevServer { * any cleanup if it dies. */ start() { - if (!this.#options?.serve) { + if (!this.#options?.enabled) { return } diff --git a/src/bundler.ts b/src/bundler.ts index 3209367..a5d5367 100644 --- a/src/bundler.ts +++ b/src/bundler.ts @@ -18,7 +18,35 @@ import { detectPackageManager } from '@antfu/install-pkg' import type { BundlerOptions } from './types.js' import { run, parseConfig, copyFiles } from './helpers.js' -type SupportedPackageManager = 'npm' | 'yarn' | 'pnpm' +type SupportedPackageManager = 'npm' | 'yarn' | 'pnpm' | 'bun' + +/** + * List of package managers we support in order to + * copy lockfiles + */ +const SUPPORT_PACKAGE_MANAGERS: { + [K in SupportedPackageManager]: { + lockFile: string + installCommand: string + } +} = { + npm: { + lockFile: 'package-lock.json', + installCommand: 'npm ci --omit="dev"', + }, + yarn: { + lockFile: 'yarn.lock', + installCommand: 'yarn install --production', + }, + pnpm: { + lockFile: 'pnpm-lock.yaml', + installCommand: 'pnpm i --prod', + }, + bun: { + lockFile: 'bun.lockb', + installCommand: 'bun install --production', + }, +} /** * Instance of CLIUI @@ -49,6 +77,10 @@ export class Bundler { this.#options = options } + /** + * Returns the relative unix path for an absolute + * file path + */ #getRelativeName(filePath: string) { return slash(relative(this.#cwdPath, filePath)) } @@ -61,11 +93,11 @@ export class Bundler { } /** - * Runs assets bundler command to build assets. + * Runs assets bundler command to build assets */ async #buildAssets(): Promise { const assetsBundler = this.#options.assets - if (!assetsBundler?.serve) { + if (!assetsBundler?.enabled) { return true } @@ -115,27 +147,20 @@ export class Bundler { * related to it. */ async #getPackageManager(client?: SupportedPackageManager) { - const pkgManagerInfo = { - npm: { - lockFile: 'package-lock.json', - installCommand: 'npm ci --omit="dev"', - }, - yarn: { - lockFile: 'yarn.lock', - installCommand: 'yarn install --production', - }, - pnpm: { - lockFile: 'pnpm-lock.yaml', - installCommand: 'pnpm i --prod', - }, + let pkgManager: string | null | undefined = client + + if (!pkgManager) { + pkgManager = await detectPackageManager(this.#cwdPath) + } + if (!pkgManager) { + pkgManager = 'npm' } - const pkgManager = client || (await detectPackageManager(this.#cwdPath)) || 'npm' - if (!['npm', 'yarn', 'pnpm'].includes(pkgManager)) { - throw new Error(`Unsupported package manager "${pkgManager}"`) + if (!Object.keys(SUPPORT_PACKAGE_MANAGERS).includes(pkgManager)) { + return null } - return pkgManagerInfo[pkgManager as SupportedPackageManager] + return SUPPORT_PACKAGE_MANAGERS[pkgManager as SupportedPackageManager] } /** @@ -207,7 +232,7 @@ export class Bundler { * Step 5: Copy meta files to the build directory */ const pkgManager = await this.#getPackageManager(client) - const pkgFiles = ['package.json', pkgManager.lockFile] + const pkgFiles = pkgManager ? ['package.json', pkgManager.lockFile] : ['package.json'] this.#logger.info('copying meta files to the output directory') await this.#copyMetaFiles(outDir, pkgFiles) @@ -221,7 +246,11 @@ export class Bundler { .useRenderer(this.#logger.getRenderer()) .heading('Run the following commands to start the server in production') .add(this.#colors.cyan(`cd ${this.#getRelativeName(outDir)}`)) - .add(this.#colors.cyan(pkgManager.installCommand)) + .add( + this.#colors.cyan( + pkgManager ? pkgManager.installCommand : 'Install production dependencies' + ) + ) .add(this.#colors.cyan('node bin/server.js')) .render() diff --git a/src/code_transformer/main.ts b/src/code_transformer/main.ts index d466b26..a0cc29a 100644 --- a/src/code_transformer/main.ts +++ b/src/code_transformer/main.ts @@ -21,7 +21,8 @@ import { } from 'ts-morph' import { RcFileTransformer } from './rc_file_transformer.js' -import type { AddMiddlewareEntry, EnvValidationDefinition } from '../types.js' +import type { MiddlewareNode, EnvValidationNode } from '../types.js' +import { exists } from 'node:fs' /** * This class is responsible for updating @@ -51,6 +52,8 @@ export class CodeTransformer { indentSize: 2, convertTabsToSpaces: true, trimTrailingWhitespace: true, + ensureNewLineAtEndOfFile: true, + indentStyle: 2, // @ts-expect-error SemicolonPreference doesn't seem to be re-exported from ts-morph semicolons: 'remove', } @@ -67,7 +70,7 @@ export class CodeTransformer { * Add a new middleware to the middleware array of the * given file */ - #addToMiddlewareArray(file: SourceFile, target: string, middlewareEntry: AddMiddlewareEntry) { + #addToMiddlewareArray(file: SourceFile, target: string, middlewareEntry: MiddlewareNode) { const callExpressions = file .getDescendantsOfKind(SyntaxKind.CallExpression) .filter((statement) => statement.getExpression().getText() === target) @@ -105,7 +108,7 @@ export class CodeTransformer { /** * Add a new middleware to the named middleware of the given file */ - #addToNamedMiddleware(file: SourceFile, middlewareEntry: AddMiddlewareEntry) { + #addToNamedMiddleware(file: SourceFile, middlewareEntry: MiddlewareNode) { if (!middlewareEntry.name) { throw new Error('Named middleware requires a name.') } @@ -158,7 +161,7 @@ export class CodeTransformer { * Add new env variable validation in the * `env.ts` file */ - async defineEnvValidations(definition: EnvValidationDefinition) { + async defineEnvValidations(definition: EnvValidationNode) { /** * Get the `start/env.ts` source file */ @@ -231,10 +234,7 @@ export class CodeTransformer { * and will not work if you significantly tweaked * your `start/kernel.ts` file. */ - async addMiddlewareToStack( - stack: 'server' | 'router' | 'named', - middleware: AddMiddlewareEntry[] - ) { + async addMiddlewareToStack(stack: 'server' | 'router' | 'named', middleware: MiddlewareNode[]) { /** * Get the `start/kernel.ts` source file */ @@ -270,7 +270,7 @@ export class CodeTransformer { */ async addJapaPlugin( pluginCall: string, - importDeclaration: { isNamed: boolean; module: string; identifier: string } + importDeclarations: { isNamed: boolean; module: string; identifier: string }[] ) { /** * Get the `tests/bootstrap.ts` source file @@ -281,11 +281,43 @@ export class CodeTransformer { /** * Add the import declaration */ - file.addImportDeclaration({ - ...(importDeclaration.isNamed - ? { namedImports: [importDeclaration.identifier] } - : { defaultImport: importDeclaration.identifier }), - moduleSpecifier: importDeclaration.module, + const existingImports = file.getImportDeclarations() + + importDeclarations.forEach((importDeclaration) => { + const existingImport = existingImports.find( + (existingImport) => existingImport.getModuleSpecifierValue() === importDeclaration.module + ) + + /** + * Add a new named import to existing import for the + * same module + */ + if (existingImport && importDeclaration.isNamed) { + if ( + !existingImport + .getNamedImports() + .find((namedImport) => namedImport.getName() === importDeclaration.identifier) + ) { + existingImport.addNamedImport(importDeclaration.identifier) + } + return + } + + /** + * Ignore default import when the same module is already imported. + * The chances are the existing default import and the importDeclaration + * identifiers are not the same. But we should not modify existing source + */ + if (existingImport) { + return + } + + file.addImportDeclaration({ + ...(importDeclaration.isNamed + ? { namedImports: [importDeclaration.identifier] } + : { defaultImport: importDeclaration.identifier }), + moduleSpecifier: importDeclaration.module, + }) }) /** @@ -295,7 +327,14 @@ export class CodeTransformer { .getVariableDeclaration('plugins') ?.getInitializerIfKind(SyntaxKind.ArrayLiteralExpression) - if (pluginsArray) pluginsArray.addElement(pluginCall) + /** + * Add plugin call to the plugins array + */ + if (pluginsArray) { + if (!pluginsArray.getElements().find((element) => element.getText() === pluginCall)) { + pluginsArray.addElement(pluginCall) + } + } file.formatText(this.#editorSettings) await file.save() diff --git a/src/code_transformer/rc_file_transformer.ts b/src/code_transformer/rc_file_transformer.ts index 2afeba0..a2a70fe 100644 --- a/src/code_transformer/rc_file_transformer.ts +++ b/src/code_transformer/rc_file_transformer.ts @@ -1,3 +1,12 @@ +/* + * @adonisjs/assembler + * + * (c) AdonisJS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + import { fileURLToPath } from 'node:url' import type { AppEnvironments } from '@adonisjs/application/types' import { @@ -7,6 +16,7 @@ import { SyntaxKind, CallExpression, PropertyAssignment, + FormatCodeSettings, ArrayLiteralExpression, } from 'ts-morph' @@ -21,10 +31,14 @@ export class RcFileTransformer { /** * Settings to use when persisting files */ - #editorSettings = { + #editorSettings: FormatCodeSettings = { indentSize: 2, convertTabsToSpaces: true, trimTrailingWhitespace: true, + ensureNewLineAtEndOfFile: true, + indentStyle: 2, + // @ts-expect-error SemicolonPreference doesn't seem to be re-exported from ts-morph + semicolons: 'remove', } constructor(cwd: URL, project: Project) { @@ -44,7 +58,9 @@ export class RcFileTransformer { * Check if environments array has a subset of available environments */ #isInSpecificEnvironment(environments?: AppEnvironments[]): boolean { - if (!environments) return false + if (!environments) { + return false + } return !!(['web', 'console', 'test', 'repl'] as const).find( (env) => !environments.includes(env) diff --git a/src/dev_server.ts b/src/dev_server.ts index 10856af..51ee93b 100644 --- a/src/dev_server.ts +++ b/src/dev_server.ts @@ -29,25 +29,59 @@ const ui = cliui() * * The Dev server performs the following actions * - * - Assigns a random PORT, when PORT inside .env file is in use + * - Assigns a random PORT, when PORT inside .env file is in use. * - Uses tsconfig.json file to collect a list of files to watch. - * - Uses metaFiles from .adonisrc.json file to collect a list of files to watch. + * - Uses metaFiles from adonisrc.ts file to collect a list of files to watch. * - Restart HTTP server on every file change. */ export class DevServer { #cwd: URL #logger = ui.logger #options: DevServerOptions + + /** + * Flag to know if the dev server is running in watch + * mode + */ #isWatching: boolean = false + + /** + * Script file to start the development server + */ #scriptFile: string = 'bin/server.js' + + /** + * Picomatch matcher function to know if a file path is a + * meta file with reloadServer option enabled + */ #isMetaFileWithReloadsEnabled: picomatch.Matcher + + /** + * Picomatch matcher function to know if a file path is a + * meta file with reloadServer option disabled + */ #isMetaFileWithReloadsDisabled: picomatch.Matcher + /** + * External listeners that are invoked when child process + * gets an error or closes + */ #onError?: (error: any) => any #onClose?: (exitCode: number) => any + /** + * Reference to the child process + */ #httpServer?: ExecaChildProcess + + /** + * Reference to the watcher + */ #watcher?: ReturnType + + /** + * Reference to the assets server + */ #assetsServer?: AssetsDevServer /** @@ -167,7 +201,8 @@ export class DevServer { } /** - * Restarts the HTTP server + * Restarts the HTTP server in the watch mode. Do not call this + * method when not in watch mode */ #restartHTTPServer(port: string) { if (this.#httpServer) { diff --git a/src/helpers.ts b/src/helpers.ts index 7432f38..943c578 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -29,7 +29,7 @@ import debug from './debug.js' const DEFAULT_NODE_ARGS = [ // Use ts-node/esm loader. The project must install it '--loader=ts-node/esm', - // Disable annonying warnings + // Disable annoying warnings '--no-warnings', // Enable expiremental meta resolve for cases where someone uses magic import string '--experimental-import-meta-resolve', @@ -133,8 +133,8 @@ export function isDotEnvFile(filePath: string) { * * - The "process.env.PORT" value is used if exists. * - The dot-env files are loaded using the "EnvLoader" and the PORT - * value is by iterating over all the loaded files. The iteration - * stops after first find. + * value is used by iterating over all the loaded files. The + * iteration stops after first find. */ export async function getPort(cwd: URL): Promise { /** diff --git a/src/test_runner.ts b/src/test_runner.ts index a799880..2a68536 100644 --- a/src/test_runner.ts +++ b/src/test_runner.ts @@ -23,24 +23,50 @@ import { getPort, isDotEnvFile, runNode, watch } from './helpers.js' const ui = cliui() /** - * Exposes the API to start the development. Optionally, the watch API can be - * used to watch for file changes and restart the development server. + * Exposes the API to run Japa tests and optionally watch for file + * changes to re-run the tests. * - * The Dev server performs the following actions + * The watch mode functions as follows. + * + * - If the changed file is a test file, then only tests for that file + * will be re-run. + * - Otherwise, all tests will re-run with respect to the initial + * filters applied when running the `node ace test` command. * - * - Assigns a random PORT, when PORT inside .env file is in use - * - Uses tsconfig.json file to collect a list of files to watch. - * - Uses metaFiles from .adonisrc.json file to collect a list of files to watch. - * - Restart HTTP server on every file change. */ export class TestRunner { #cwd: URL #logger = ui.logger #options: TestRunnerOptions + + /** + * The script file to run as a child process + */ #scriptFile: string = 'bin/test.js' + + /** + * Pico matcher function to check if the filepath is + * part of the `metaFiles` glob patterns + */ #isMetaFile: picomatch.Matcher + + /** + * Pico matcher function to check if the filepath is + * part of a test file. + */ #isTestFile: picomatch.Matcher + + /** + * Arguments to pass to the "bin/test.js" file. + */ #scriptArgs: string[] + + /** + * Set of initial filters applied when running the test + * command. In watch mode, we will append an additional + * filter to run tests only for the file that has been + * changed. + */ #initialFiltersArgs: string[] /** @@ -51,11 +77,26 @@ export class TestRunner { */ #isBusy: boolean = false + /** + * External listeners that are invoked when child process + * gets an error or closes + */ #onError?: (error: any) => any #onClose?: (exitCode: number) => any + /** + * Reference to the test script child process + */ #testScript?: ExecaChildProcess + + /** + * Reference to the watcher + */ #watcher?: ReturnType + + /** + * Reference to the assets server + */ #assetsServer?: AssetsDevServer /** @@ -68,14 +109,21 @@ export class TestRunner { constructor(cwd: URL, options: TestRunnerOptions) { this.#cwd = cwd this.#options = options + this.#isMetaFile = picomatch((this.#options.metaFiles || []).map(({ pattern }) => pattern)) + + /** + * Create a test file watch by collection all the globs + * used by all the suites. However, if a suite filter + * was used, then we only collect glob for the mentioned + * suites. + */ this.#isTestFile = picomatch( this.#options.suites .filter((suite) => { if (this.#options.filters.suites) { return this.#options.filters.suites.includes(suite.name) } - return true }) .map((suite) => suite.files) @@ -87,7 +135,7 @@ export class TestRunner { } /** - * Converts options to CLI args + * Convert test runner options to the CLI args */ #convertOptionsToArgs() { const args: string[] = [] @@ -170,6 +218,10 @@ export class TestRunner { ) { this.#isBusy = true + /** + * If inline filters are defined, then we ignore the + * initial filters + */ const scriptArgs = filters ? this.#convertFiltersToArgs(filters).concat(this.#scriptArgs) : this.#initialFiltersArgs.concat(this.#scriptArgs) @@ -200,7 +252,8 @@ export class TestRunner { } /** - * Restarts the HTTP server + * Re-run tests with additional inline filters. Should be + * executed in watch mode only. */ #rerunTests(port: string, filters?: TestRunnerOptions['filters']) { if (this.#testScript) { diff --git a/src/types.ts b/src/types.ts index 85a9e19..45129b6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -11,10 +11,29 @@ * Options needed to run a script file */ export type RunOptions = { + /** + * Script to run + */ script: string + + /** + * Arguments to pass to the script + */ scriptArgs: string[] + + /** + * Arguments to pass to NodeJS CLI + */ nodeArgs: string[] + + /** + * Standard input ouput stream options + */ stdio?: 'pipe' | 'inherit' + + /** + * Environment variables to pass to the child process + */ env?: NodeJS.ProcessEnv } @@ -26,7 +45,7 @@ export type WatchOptions = { } /** - * Meta file config defined in ".adonisrc.json" file + * Meta file config defined in "adonisrc.ts" file */ export type MetaFile = { pattern: string @@ -34,11 +53,11 @@ export type MetaFile = { } /** - * Test suite defined in ".adonisrc.json" file + * Test suite defined in "adonisrc.ts" file */ export type Suite = { - files: string | string[] name: string + files: string | string[] } /** @@ -46,27 +65,54 @@ export type Suite = { */ export type AssetsBundlerOptions = | { - serve: false - args?: string[] + enabled: false driver?: string cmd?: string + args?: string[] } | { - serve: true - args: string[] + enabled: true driver: string cmd: string + args: string[] } /** - * Options accepted by the dev server + * Options accepted when starting the dev + * server */ export type DevServerOptions = { + /** + * Arguments to pass to the "bin/server.js" file + * executed a child process + */ scriptArgs: string[] + + /** + * Arguments to pass to Node.js CLI when executing + * the "bin/server.js" file + */ nodeArgs: string[] + + /** + * Clear screen after every file change + */ clearScreen?: boolean + + /** + * Environment variables to share with the "bin/server.js" + * file. + */ env?: NodeJS.ProcessEnv + + /** + * An array of metaFiles glob patterns to watch + */ metaFiles?: MetaFile[] + + /** + * Assets bundler options to start its dev server + */ assets?: AssetsBundlerOptions } @@ -75,40 +121,90 @@ export type DevServerOptions = { */ export type TestRunnerOptions = { /** - * Filter arguments are provided as a key-value - * pair, so that we can mutate them (if needed) + * Arguments to pass to the "bin/server.js" file + * executed a child process */ - filters: Partial<{ - tests: string[] - suites: string[] - groups: string[] - files: string[] - tags: string[] - }> - - reporters?: string[] - timeout?: number - retries?: number - failed?: boolean + scriptArgs: string[] /** - * All other tags are provided as a collection of - * arguments + * Arguments to pass to Node.js CLI when executing + * the "bin/server.js" file */ - scriptArgs: string[] nodeArgs: string[] + + /** + * Clear screen after every file change + */ clearScreen?: boolean + + /** + * Environment variables to share with the "bin/server.js" + * file. + */ env?: NodeJS.ProcessEnv + + /** + * An array of metaFiles glob patterns to watch + */ metaFiles?: MetaFile[] + + /** + * Assets bundler options to start its dev server + */ assets?: AssetsBundlerOptions + + /** + * An array of suites for which to run tests + */ suites: Suite[] + + /** + * Set the tests runner reporter via the CLI flag + */ + reporters?: string[] + + /** + * Set the tests global timeout via the CLI flag + */ + timeout?: number + + /** + * Define retries via the CLI flag + */ + retries?: number + + /** + * Run only failed tests + */ + failed?: boolean + + /** + * Filter arguments are provided as a key-value + * pair, so that we can mutate them (if needed) + */ + filters: Partial<{ + tests: string[] + suites: string[] + groups: string[] + files: string[] + tags: string[] + }> } /** * Options accepted by the project bundler */ export type BundlerOptions = { + /** + * An array of metaFiles glob patterns to copy the + * files to the build folder + */ metaFiles?: MetaFile[] + + /** + * Assets bundler options to create the production build + * for assets + */ assets?: AssetsBundlerOptions } @@ -116,7 +212,7 @@ export type BundlerOptions = { * Entry to add a middleware to a given middleware stack * via the CodeTransformer */ -export type AddMiddlewareEntry = { +export type MiddlewareNode = { /** * If you are adding a named middleware, then you must * define the name. @@ -146,7 +242,7 @@ export type AddMiddlewareEntry = { * Defines the structure of an environment variable validation * definition */ -export type EnvValidationDefinition = { +export type EnvValidationNode = { /** * Write a leading comment on top of your variables */ diff --git a/tests/__snapshots__/code_transformer.spec.ts.cjs b/tests/__snapshots__/code_transformer.spec.ts.cjs index 9267556..4bc3b2c 100644 --- a/tests/__snapshots__/code_transformer.spec.ts.cjs +++ b/tests/__snapshots__/code_transformer.spec.ts.cjs @@ -1,4 +1,5 @@ -exports[`Code transformer | addMiddlewareToStack > set correct position when defined 1`] = `"import router from '@adonisjs/core/services/router' +exports[`Code transformer | addMiddlewareToStack > set correct position when defined 1`] = + `"import router from '@adonisjs/core/services/router' import server from '@adonisjs/core/services/server' server.errorHandler(() => import('#exceptions/handler')) @@ -19,7 +20,8 @@ router.use([ export const middleware = router.named({}) "` -exports[`Code transformer | addMiddlewareToStack > add a route middleware 1`] = `"import router from '@adonisjs/core/services/router' +exports[`Code transformer | addMiddlewareToStack > add a route middleware 1`] = + `"import router from '@adonisjs/core/services/router' import server from '@adonisjs/core/services/server' server.errorHandler(() => import('#exceptions/handler')) @@ -39,7 +41,8 @@ router.use([ export const middleware = router.named({}) "` -exports[`Code transformer | addMiddlewareToStack > add route and server middleware 1`] = `"import router from '@adonisjs/core/services/router' +exports[`Code transformer | addMiddlewareToStack > add route and server middleware 1`] = + `"import router from '@adonisjs/core/services/router' import server from '@adonisjs/core/services/server' server.errorHandler(() => import('#exceptions/handler')) @@ -59,7 +62,8 @@ router.use([ export const middleware = router.named({}) "` -exports[`Code transformer | defineEnvValidations > add leading comment 1`] = `"import { Env } from '@adonisjs/core/env' +exports[`Code transformer | defineEnvValidations > add leading comment 1`] = + `"import { Env } from '@adonisjs/core/env' export default await Env.create(new URL('../', import.meta.url), { NODE_ENV: Env.schema.enum(['development', 'production', 'test'] as const), @@ -75,7 +79,8 @@ export default await Env.create(new URL('../', import.meta.url), { }) "` -exports[`Code transformer | addProvider > add provider to rc file with specific environments 1`] = `"import { defineConfig } from '@adonisjs/core/app' +exports[`Code transformer | addProvider > add provider to rc file with specific environments 1`] = + `"import { defineConfig } from '@adonisjs/core/app' export default defineConfig({ typescript: true, @@ -109,7 +114,8 @@ export default defineConfig({ }) "` -exports[`Code transformer | addProvider > do no add environments when they are all specified 1`] = `"import { defineConfig } from '@adonisjs/core/app' +exports[`Code transformer | addProvider > do no add environments when they are all specified 1`] = + `"import { defineConfig } from '@adonisjs/core/app' export default defineConfig({ typescript: true, @@ -140,7 +146,8 @@ export default defineConfig({ }) "` -exports[`Code transformer | addMetaFile > add meta files to rc file with reload server 1`] = `"import { defineConfig } from '@adonisjs/core/app' +exports[`Code transformer | addMetaFile > add meta files to rc file with reload server 1`] = + `"import { defineConfig } from '@adonisjs/core/app' export default defineConfig({ typescript: true, @@ -174,7 +181,8 @@ export default defineConfig({ }) "` -exports[`Code transformer | setDirectory > set directory in rc file 1`] = `"import { defineConfig } from '@adonisjs/core/app' +exports[`Code transformer | setDirectory > set directory in rc file 1`] = + `"import { defineConfig } from '@adonisjs/core/app' export default defineConfig({ typescript: true, @@ -207,7 +215,8 @@ export default defineConfig({ }) "` -exports[`Code transformer | setCommandAlias > set command alias in rc file 1`] = `"import { defineConfig } from '@adonisjs/core/app' +exports[`Code transformer | setCommandAlias > set command alias in rc file 1`] = + `"import { defineConfig } from '@adonisjs/core/app' export default defineConfig({ typescript: true, @@ -240,7 +249,8 @@ export default defineConfig({ }) "` -exports[`Code transformer | addPreloadFile > add preload file with specific environments 1`] = `"import { defineConfig } from '@adonisjs/core/app' +exports[`Code transformer | addPreloadFile > add preload file with specific environments 1`] = + `"import { defineConfig } from '@adonisjs/core/app' export default defineConfig({ typescript: true, @@ -274,7 +284,9 @@ export default defineConfig({ }) "` -exports[`Code transformer | addMiddlewareToStack > override duplicates when adding named middelware 1`] = `"import router from '@adonisjs/core/services/router' +exports[ + `Code transformer | addMiddlewareToStack > override duplicates when adding named middelware 1` +] = `"import router from '@adonisjs/core/services/router' import server from '@adonisjs/core/services/server' server.errorHandler(() => import('#exceptions/handler')) @@ -294,7 +306,8 @@ export const middleware = router.named({ }) "` -exports[`Code transformer | addMiddlewareToStack > do not add duplicate named middleware 1`] = `"import router from '@adonisjs/core/services/router' +exports[`Code transformer | addMiddlewareToStack > do not add duplicate named middleware 1`] = + `"import router from '@adonisjs/core/services/router' import server from '@adonisjs/core/services/server' server.errorHandler(() => import('#exceptions/handler')) @@ -314,7 +327,8 @@ export const middleware = router.named({ }) "` -exports[`Code transformer | addCommand > add command to rc file 1`] = `"import { defineConfig } from '@adonisjs/core/app' +exports[`Code transformer | addCommand > add command to rc file 1`] = + `"import { defineConfig } from '@adonisjs/core/app' export default defineConfig({ typescript: true, @@ -346,7 +360,9 @@ export default defineConfig({ }) "` -exports[`Code transformer | addCommand > should add command even if commands property is missing 1`] = `"import { defineConfig } from '@adonisjs/core/app' +exports[ + `Code transformer | addCommand > should add command even if commands property is missing 1` +] = `"import { defineConfig } from '@adonisjs/core/app' export default defineConfig({ typescript: true, @@ -397,4 +413,3 @@ export const plugins: Config['plugins'] = [ fooPlugin() ] "` - diff --git a/tests/bundler.spec.ts b/tests/bundler.spec.ts index d9c21a5..2d0b040 100644 --- a/tests/bundler.spec.ts +++ b/tests/bundler.spec.ts @@ -1,6 +1,15 @@ +/* + * @adonisjs/assembler + * + * (c) AdonisJS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import ts from 'typescript' import { test } from '@japa/runner' import { Bundler } from '../index.js' -import ts from 'typescript' test.group('Bundler', () => { test('should copy metafiles to the build directory', async ({ assert, fs }) => { diff --git a/tests/code_transformer.spec.ts b/tests/code_transformer.spec.ts index 317baa1..822f00d 100644 --- a/tests/code_transformer.spec.ts +++ b/tests/code_transformer.spec.ts @@ -1,8 +1,16 @@ +/* + * @adonisjs/assembler + * + * (c) AdonisJS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + import dedent from 'dedent' import { test } from '@japa/runner' import { readFile } from 'node:fs/promises' import type { FileSystem } from '@japa/file-system' - import { CodeTransformer } from '../src/code_transformer/main.js' async function setupFakeAdonisproject(fs: FileSystem) { @@ -162,19 +170,69 @@ test.group('Code transformer | defineEnvValidations', (group) => { const transformer = new CodeTransformer(fs.baseUrl) await transformer.defineEnvValidations({ + leadingComment: 'Redis configuration', variables: { - NODE_ENV: 'Env.schema.string.optional()', + REDIS_HOST: 'Env.schema.string.optional()', + REDIS_PORT: 'Env.schema.number()', }, }) - const file = await fs.contents('start/env.ts') - const occurrences = (file.match(/NODE_ENV/g) || []).length + await transformer.defineEnvValidations({ + leadingComment: 'Redis configuration', + variables: { + REDIS_HOST: 'Env.schema.string.optional()', + REDIS_PORT: 'Env.schema.number()', + }, + }) - assert.equal(occurrences, 1) - assert.fileContains( - 'start/env.ts', - `Env.schema.enum(['development', 'production', 'test'] as const)` - ) + assert.snapshot(await fs.contents('start/env.ts')).matchInline(` + "import { Env } from '@adonisjs/core/env' + + export default await Env.create(new URL('../', import.meta.url), { + NODE_ENV: Env.schema.enum(['development', 'production', 'test'] as const), + PORT: Env.schema.number(), + + /* + |---------------------------------------------------------- + | Redis configuration + |---------------------------------------------------------- + */ + REDIS_HOST: Env.schema.string.optional(), + REDIS_PORT: Env.schema.number() + }) + " + `) + }) + + test('do not overwrite validation for existing variable', async ({ assert, fs }) => { + const transformer = new CodeTransformer(fs.baseUrl) + + await transformer.defineEnvValidations({ + variables: { + REDIS_HOST: 'Env.schema.string.optional()', + REDIS_PORT: 'Env.schema.number()', + }, + }) + + await transformer.defineEnvValidations({ + variables: { + REDIS_HOST: 'Env.schema.string()', + REDIS_PORT: 'Env.schema.number()', + }, + }) + + assert.snapshot(await fs.contents('start/env.ts')).matchInline(` + "import { Env } from '@adonisjs/core/env' + + export default await Env.create(new URL('../', import.meta.url), { + NODE_ENV: Env.schema.enum(['development', 'production', 'test'] as const), + PORT: Env.schema.number(), + + REDIS_HOST: Env.schema.string.optional(), + REDIS_PORT: Env.schema.number() + }) + " + `) }) }) @@ -524,14 +582,32 @@ test.group('Code transformer | addJapaPlugin', (group) => { const transformer = new CodeTransformer(fs.baseUrl) - await transformer.addJapaPlugin('fooPlugin(app)', { - module: '@adonisjs/foo/plugin/japa', - identifier: 'fooPlugin', - isNamed: true, - }) + await transformer.addJapaPlugin('fooPlugin(app)', [ + { + module: '@adonisjs/foo/plugin/japa', + identifier: 'fooPlugin', + isNamed: true, + }, + { + module: '@adonisjs/core/services/app', + identifier: 'app', + isNamed: false, + }, + ]) const file = await fs.contents('tests/bootstrap.ts') - assert.snapshot(file).match() + assert.snapshot(file).matchInline(` + " + import app from '@adonisjs/core/services/app' + import { assert } from '@japa/assert' + import { fooPlugin } from '@adonisjs/foo/plugin/japa' + + export const plugins: Config['plugins'] = [ + assert(), + fooPlugin(app) + ] + " + `) }) test('add default import', async ({ assert, fs }) => { @@ -548,13 +624,81 @@ test.group('Code transformer | addJapaPlugin', (group) => { const transformer = new CodeTransformer(fs.baseUrl) - await transformer.addJapaPlugin('fooPlugin()', { - module: '@adonisjs/foo/plugin/japa', - identifier: 'fooPlugin', - isNamed: false, - }) + await transformer.addJapaPlugin('fooPlugin()', [ + { + module: '@adonisjs/foo/plugin/japa', + identifier: 'fooPlugin', + isNamed: false, + }, + ]) const file = await fs.contents('tests/bootstrap.ts') - assert.snapshot(file).match() + assert.snapshot(file).matchInline(` + " + import app from '@adonisjs/core/services/app' + import { assert } from '@japa/assert' + import fooPlugin from '@adonisjs/foo/plugin/japa' + + export const plugins: Config['plugins'] = [ + assert(), + fooPlugin() + ] + " + `) + }) + + test('ignore duplicate imports', async ({ assert, fs }) => { + await fs.create( + 'tests/bootstrap.ts', + ` + import app from '@adonisjs/core/services/app' + import { assert } from '@japa/assert' + + export const plugins: Config['plugins'] = [ + assert(), + ]` + ) + + const transformer = new CodeTransformer(fs.baseUrl) + + await transformer.addJapaPlugin('fooPlugin(app)', [ + { + module: '@adonisjs/foo/plugin/japa', + identifier: 'fooPlugin', + isNamed: true, + }, + { + module: '@adonisjs/core/services/app', + identifier: 'app', + isNamed: false, + }, + ]) + + await transformer.addJapaPlugin('fooPlugin(app)', [ + { + module: '@adonisjs/foo/plugin/japa', + identifier: 'fooPlugin', + isNamed: true, + }, + { + module: '@adonisjs/core/services/app', + identifier: 'app', + isNamed: false, + }, + ]) + + const file = await fs.contents('tests/bootstrap.ts') + assert.snapshot(file).matchInline(` + " + import app from '@adonisjs/core/services/app' + import { assert } from '@japa/assert' + import { fooPlugin } from '@adonisjs/foo/plugin/japa' + + export const plugins: Config['plugins'] = [ + assert(), + fooPlugin(app) + ] + " + `) }) }) diff --git a/tests/copy.spec.ts b/tests/copy.spec.ts index 3339bcf..6f4ada2 100644 --- a/tests/copy.spec.ts +++ b/tests/copy.spec.ts @@ -1,6 +1,15 @@ +/* + * @adonisjs/assembler + * + * (c) AdonisJS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { join } from 'node:path' import { test } from '@japa/runner' import { copyFiles } from '../src/helpers.js' -import { join } from 'node:path' test.group('Copy files', () => { test('expand glob patterns and copy files to the destination', async ({ assert, fs }) => { diff --git a/tests/helpers.spec.ts b/tests/helpers.spec.ts new file mode 100644 index 0000000..8105e0e --- /dev/null +++ b/tests/helpers.spec.ts @@ -0,0 +1,73 @@ +/* + * @adonisjs/assembler + * + * (c) AdonisJS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import ts from 'typescript' +import { join } from 'node:path' +import { test } from '@japa/runner' +import { getPort, isDotEnvFile, parseConfig } from '../src/helpers.js' + +test.group('Helpers | Parse config', () => { + test('report error when config file is missing', async ({ assert, fs }) => { + const result = parseConfig(fs.baseUrl, ts) + assert.isUndefined(result) + }) + + test('report config errors to console', async ({ assert, fs }) => { + await fs.createJson('tsconfig.json', { + include: ['**/*'], + }) + + const result = parseConfig(fs.baseUrl, ts) + assert.isUndefined(result) + }) + + test('parse tsconfig file', async ({ assert, fs }) => { + await fs.createJson('tsconfig.json', { + include: ['**/*'], + }) + await fs.create('foo.ts', '') + + const result = parseConfig(fs.baseUrl, ts) + assert.deepEqual(result?.fileNames, [join(fs.basePath, 'foo.ts')]) + }) +}) + +test.group('Helpers | Is DotEnv file', () => { + test('check if file is a dot-env file', ({ assert }) => { + assert.isTrue(isDotEnvFile('.env')) + assert.isTrue(isDotEnvFile('.env.prod')) + assert.isTrue(isDotEnvFile('.env.local')) + assert.isFalse(isDotEnvFile('.env-file')) + }) +}) + +test.group('Helpers | getPort', () => { + test('use port set via process.env.PORT', async ({ fs, assert, cleanup }) => { + process.env.PORT = '4000' + cleanup(() => { + delete process.env.PORT + }) + assert.equal(await getPort(fs.baseUrl), 4000) + }) + + test('use port from the .env file', async ({ fs, assert }) => { + await fs.create('.env', 'PORT=3000') + assert.equal(await getPort(fs.baseUrl), 3000) + }) + + test('give preference to .env.local file', async ({ fs, assert }) => { + await fs.create('.env', 'PORT=3000') + await fs.create('.env.local', 'PORT=5000') + assert.equal(await getPort(fs.baseUrl), 5000) + }) + + test('use port 3333 when no environment variable or files exists', async ({ fs, assert }) => { + assert.equal(await getPort(fs.baseUrl), 3333) + }) +}) From ba48335419ba9324dd87769e1eae52be58492df5 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Wed, 20 Dec 2023 13:08:11 +0530 Subject: [PATCH 120/131] refactor: fix linting errors --- src/code_transformer/main.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/code_transformer/main.ts b/src/code_transformer/main.ts index a0cc29a..961850f 100644 --- a/src/code_transformer/main.ts +++ b/src/code_transformer/main.ts @@ -22,7 +22,6 @@ import { import { RcFileTransformer } from './rc_file_transformer.js' import type { MiddlewareNode, EnvValidationNode } from '../types.js' -import { exists } from 'node:fs' /** * This class is responsible for updating @@ -285,7 +284,7 @@ export class CodeTransformer { importDeclarations.forEach((importDeclaration) => { const existingImport = existingImports.find( - (existingImport) => existingImport.getModuleSpecifierValue() === importDeclaration.module + (mod) => mod.getModuleSpecifierValue() === importDeclaration.module ) /** From f455e356ba94339a4722095e311c34073205d05d Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Wed, 20 Dec 2023 13:13:33 +0530 Subject: [PATCH 121/131] test: fix test broken on windows --- tests/helpers.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/helpers.spec.ts b/tests/helpers.spec.ts index 8105e0e..4443840 100644 --- a/tests/helpers.spec.ts +++ b/tests/helpers.spec.ts @@ -7,6 +7,7 @@ * file that was distributed with this source code. */ +import slash from 'slash' import ts from 'typescript' import { join } from 'node:path' import { test } from '@japa/runner' @@ -34,7 +35,7 @@ test.group('Helpers | Parse config', () => { await fs.create('foo.ts', '') const result = parseConfig(fs.baseUrl, ts) - assert.deepEqual(result?.fileNames, [join(fs.basePath, 'foo.ts')]) + assert.deepEqual(result?.fileNames, [slash(join(fs.basePath, 'foo.ts'))]) }) }) From e408ab13fd216c72cb9223fff05bb7dbf5a365a0 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Wed, 20 Dec 2023 13:40:47 +0530 Subject: [PATCH 122/131] refactor: ignore snapshots from prettier --- .prettierignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.prettierignore b/.prettierignore index e843a17..8b58fb4 100644 --- a/.prettierignore +++ b/.prettierignore @@ -6,3 +6,4 @@ config.json package.json *.html *.txt +tests/__snapshots__ \ No newline at end of file From 9f7860fd9bf17bc79ee7ed1c5e25d4b571ddf6d0 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Wed, 20 Dec 2023 13:48:24 +0530 Subject: [PATCH 123/131] test: update snapshots --- .../code_transformer.spec.ts.cjs | 45 +++++++------------ 1 file changed, 15 insertions(+), 30 deletions(-) diff --git a/tests/__snapshots__/code_transformer.spec.ts.cjs b/tests/__snapshots__/code_transformer.spec.ts.cjs index 4bc3b2c..9267556 100644 --- a/tests/__snapshots__/code_transformer.spec.ts.cjs +++ b/tests/__snapshots__/code_transformer.spec.ts.cjs @@ -1,5 +1,4 @@ -exports[`Code transformer | addMiddlewareToStack > set correct position when defined 1`] = - `"import router from '@adonisjs/core/services/router' +exports[`Code transformer | addMiddlewareToStack > set correct position when defined 1`] = `"import router from '@adonisjs/core/services/router' import server from '@adonisjs/core/services/server' server.errorHandler(() => import('#exceptions/handler')) @@ -20,8 +19,7 @@ router.use([ export const middleware = router.named({}) "` -exports[`Code transformer | addMiddlewareToStack > add a route middleware 1`] = - `"import router from '@adonisjs/core/services/router' +exports[`Code transformer | addMiddlewareToStack > add a route middleware 1`] = `"import router from '@adonisjs/core/services/router' import server from '@adonisjs/core/services/server' server.errorHandler(() => import('#exceptions/handler')) @@ -41,8 +39,7 @@ router.use([ export const middleware = router.named({}) "` -exports[`Code transformer | addMiddlewareToStack > add route and server middleware 1`] = - `"import router from '@adonisjs/core/services/router' +exports[`Code transformer | addMiddlewareToStack > add route and server middleware 1`] = `"import router from '@adonisjs/core/services/router' import server from '@adonisjs/core/services/server' server.errorHandler(() => import('#exceptions/handler')) @@ -62,8 +59,7 @@ router.use([ export const middleware = router.named({}) "` -exports[`Code transformer | defineEnvValidations > add leading comment 1`] = - `"import { Env } from '@adonisjs/core/env' +exports[`Code transformer | defineEnvValidations > add leading comment 1`] = `"import { Env } from '@adonisjs/core/env' export default await Env.create(new URL('../', import.meta.url), { NODE_ENV: Env.schema.enum(['development', 'production', 'test'] as const), @@ -79,8 +75,7 @@ export default await Env.create(new URL('../', import.meta.url), { }) "` -exports[`Code transformer | addProvider > add provider to rc file with specific environments 1`] = - `"import { defineConfig } from '@adonisjs/core/app' +exports[`Code transformer | addProvider > add provider to rc file with specific environments 1`] = `"import { defineConfig } from '@adonisjs/core/app' export default defineConfig({ typescript: true, @@ -114,8 +109,7 @@ export default defineConfig({ }) "` -exports[`Code transformer | addProvider > do no add environments when they are all specified 1`] = - `"import { defineConfig } from '@adonisjs/core/app' +exports[`Code transformer | addProvider > do no add environments when they are all specified 1`] = `"import { defineConfig } from '@adonisjs/core/app' export default defineConfig({ typescript: true, @@ -146,8 +140,7 @@ export default defineConfig({ }) "` -exports[`Code transformer | addMetaFile > add meta files to rc file with reload server 1`] = - `"import { defineConfig } from '@adonisjs/core/app' +exports[`Code transformer | addMetaFile > add meta files to rc file with reload server 1`] = `"import { defineConfig } from '@adonisjs/core/app' export default defineConfig({ typescript: true, @@ -181,8 +174,7 @@ export default defineConfig({ }) "` -exports[`Code transformer | setDirectory > set directory in rc file 1`] = - `"import { defineConfig } from '@adonisjs/core/app' +exports[`Code transformer | setDirectory > set directory in rc file 1`] = `"import { defineConfig } from '@adonisjs/core/app' export default defineConfig({ typescript: true, @@ -215,8 +207,7 @@ export default defineConfig({ }) "` -exports[`Code transformer | setCommandAlias > set command alias in rc file 1`] = - `"import { defineConfig } from '@adonisjs/core/app' +exports[`Code transformer | setCommandAlias > set command alias in rc file 1`] = `"import { defineConfig } from '@adonisjs/core/app' export default defineConfig({ typescript: true, @@ -249,8 +240,7 @@ export default defineConfig({ }) "` -exports[`Code transformer | addPreloadFile > add preload file with specific environments 1`] = - `"import { defineConfig } from '@adonisjs/core/app' +exports[`Code transformer | addPreloadFile > add preload file with specific environments 1`] = `"import { defineConfig } from '@adonisjs/core/app' export default defineConfig({ typescript: true, @@ -284,9 +274,7 @@ export default defineConfig({ }) "` -exports[ - `Code transformer | addMiddlewareToStack > override duplicates when adding named middelware 1` -] = `"import router from '@adonisjs/core/services/router' +exports[`Code transformer | addMiddlewareToStack > override duplicates when adding named middelware 1`] = `"import router from '@adonisjs/core/services/router' import server from '@adonisjs/core/services/server' server.errorHandler(() => import('#exceptions/handler')) @@ -306,8 +294,7 @@ export const middleware = router.named({ }) "` -exports[`Code transformer | addMiddlewareToStack > do not add duplicate named middleware 1`] = - `"import router from '@adonisjs/core/services/router' +exports[`Code transformer | addMiddlewareToStack > do not add duplicate named middleware 1`] = `"import router from '@adonisjs/core/services/router' import server from '@adonisjs/core/services/server' server.errorHandler(() => import('#exceptions/handler')) @@ -327,8 +314,7 @@ export const middleware = router.named({ }) "` -exports[`Code transformer | addCommand > add command to rc file 1`] = - `"import { defineConfig } from '@adonisjs/core/app' +exports[`Code transformer | addCommand > add command to rc file 1`] = `"import { defineConfig } from '@adonisjs/core/app' export default defineConfig({ typescript: true, @@ -360,9 +346,7 @@ export default defineConfig({ }) "` -exports[ - `Code transformer | addCommand > should add command even if commands property is missing 1` -] = `"import { defineConfig } from '@adonisjs/core/app' +exports[`Code transformer | addCommand > should add command even if commands property is missing 1`] = `"import { defineConfig } from '@adonisjs/core/app' export default defineConfig({ typescript: true, @@ -413,3 +397,4 @@ export const plugins: Config['plugins'] = [ fooPlugin() ] "` + From 5a3d878f53f455043b630728d1c5e6d562c114b9 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Wed, 20 Dec 2023 13:51:11 +0530 Subject: [PATCH 124/131] chore(release): 7.0.0-0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bc149ab..8e26dd2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@adonisjs/assembler", "description": "Provides utilities to run AdonisJS development server and build project for production", - "version": "6.1.3-30", + "version": "7.0.0-0", "engines": { "node": ">=18.16.0" }, From 49cea4d0317b5863cba2025c5770ead9718cdbd4 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Mon, 25 Dec 2023 17:40:29 +0530 Subject: [PATCH 125/131] feat: add addPolicy codemod --- README.md | 30 +++++++++++++ src/code_transformer/main.ts | 43 ++++++++++++++++++- src/types.ts | 15 +++++++ tests/code_transformer.spec.ts | 77 ++++++++++++++++++++++++++++++++++ 4 files changed, 164 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 881f9d2..fab50d5 100644 --- a/README.md +++ b/README.md @@ -390,6 +390,36 @@ export const plugins: Config['plugins'] = [ ] ``` +### addPolicy +Register AdonisJS bouncer policy to the list of `policies` object exported from the `app/policies/main.ts` file. + +> [!IMPORTANT] +> This codemod expects the `app/policies/main.ts` file to exist and must export a `policies` object from it. + +```ts +const transformer = new CodeTransformer(appRoot) + +try { + await transformer.addPolicy([ + { + name: 'PostPolicy', + path: '#policies/post_policy' + } + ]) +} catch (error) { + console.error('Unable to register policy') + console.error(error) +} +``` + +Output + +```ts +export const policies = { + UserPolicy: () => import('#policies/post_policy') +} +``` + ## Contributing One of the primary goals of AdonisJS is to have a vibrant community of users and contributors who believe in the framework's principles. diff --git a/src/code_transformer/main.ts b/src/code_transformer/main.ts index 961850f..44f0295 100644 --- a/src/code_transformer/main.ts +++ b/src/code_transformer/main.ts @@ -21,7 +21,7 @@ import { } from 'ts-morph' import { RcFileTransformer } from './rc_file_transformer.js' -import type { MiddlewareNode, EnvValidationNode } from '../types.js' +import type { MiddlewareNode, EnvValidationNode, BouncerPolicyNode } from '../types.js' /** * This class is responsible for updating @@ -139,6 +139,25 @@ export class CodeTransformer { } } + /** + * Add a policy to the list of pre-registered policy + */ + #addToPoliciesList(file: SourceFile, policyEntry: BouncerPolicyNode) { + const policiesObject = file + .getVariableDeclarationOrThrow('policies') + .getInitializerIfKindOrThrow(SyntaxKind.ObjectLiteralExpression) + + /** + * Only define policy when one with the existing name does not + * exist. + */ + const existingProperty = policiesObject.getProperty(policyEntry.name) + if (!existingProperty) { + const policy = `${policyEntry.name}: () => import('${policyEntry.path}')` + policiesObject!.insertProperty(0, policy) + } + } + /** * Write a leading comment */ @@ -338,4 +357,26 @@ export class CodeTransformer { file.formatText(this.#editorSettings) await file.save() } + + /** + * Adds a policy to the list of `policies` object configured + * inside the `app/policies/main.ts` file. + */ + async addPolicies(policies: BouncerPolicyNode[]) { + /** + * Get the `app/policies/main.ts` source file + */ + const kernelUrl = fileURLToPath(new URL('./app/policies/main.ts', this.#cwd)) + const file = this.#project.getSourceFileOrThrow(kernelUrl) + + /** + * Process each middleware entry + */ + for (const policy of policies) { + this.#addToPoliciesList(file, policy) + } + + file.formatText(this.#editorSettings) + await file.save() + } } diff --git a/src/types.ts b/src/types.ts index 45129b6..9004a1e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -238,6 +238,21 @@ export type MiddlewareNode = { position?: 'before' | 'after' } +/** + * Policy node to be added to the list of policies. + */ +export type BouncerPolicyNode = { + /** + * Policy name + */ + name: string + + /** + * Policy import path + */ + path: string +} + /** * Defines the structure of an environment variable validation * definition diff --git a/tests/code_transformer.spec.ts b/tests/code_transformer.spec.ts index 822f00d..35b7356 100644 --- a/tests/code_transformer.spec.ts +++ b/tests/code_transformer.spec.ts @@ -702,3 +702,80 @@ test.group('Code transformer | addJapaPlugin', (group) => { `) }) }) + +test.group('Code transformer | addPolicies', (group) => { + group.each.setup(async ({ context }) => setupFakeAdonisproject(context.fs)) + + test('add policies to polices/main.ts file', async ({ assert, fs }) => { + await fs.create('app/policies/main.ts', `export const policies = {}`) + + const transformer = new CodeTransformer(fs.baseUrl) + + await transformer.addPolicies([ + { + name: 'PostPolicy', + path: '#policies/post_policy', + }, + { + name: 'UserPolicy', + path: '#policies/user_policy', + }, + ]) + + const file = await fs.contents('app/policies/main.ts') + assert.snapshot(file).matchInline(` + "export const policies = { + UserPolicy: () => import('#policies/user_policy'), + PostPolicy: () => import('#policies/post_policy') + } + " + `) + }) + + test('throw error when policies/main.ts file is missing', async ({ fs }) => { + const transformer = new CodeTransformer(fs.baseUrl) + + await transformer.addPolicies([ + { + name: 'PostPolicy', + path: '#policies/post_policy', + }, + { + name: 'UserPolicy', + path: '#policies/user_policy', + }, + ]) + }).throws(/Could not find source file in project at the provided path:/) + + test('throw error when policies object is not defined', async ({ fs }) => { + await fs.create('app/policies/main.ts', `export const foo = {}`) + const transformer = new CodeTransformer(fs.baseUrl) + + await transformer.addPolicies([ + { + name: 'PostPolicy', + path: '#policies/post_policy', + }, + { + name: 'UserPolicy', + path: '#policies/user_policy', + }, + ]) + }).throws(`Expected to find variable declaration named 'policies'.`) + + test('throw error when policies declaration is not an object', async ({ fs }) => { + await fs.create('app/policies/main.ts', `export const policies = []`) + const transformer = new CodeTransformer(fs.baseUrl) + + await transformer.addPolicies([ + { + name: 'PostPolicy', + path: '#policies/post_policy', + }, + { + name: 'UserPolicy', + path: '#policies/user_policy', + }, + ]) + }).throws(/Expected to find an initializer of kind \'ObjectLiteralExpression\'./) +}) From dec67001978708ccebe4578fc21ee52ee96cabc3 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Mon, 25 Dec 2023 17:43:55 +0530 Subject: [PATCH 126/131] docs: fix incorrect method name --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fab50d5..92fc27b 100644 --- a/README.md +++ b/README.md @@ -390,8 +390,8 @@ export const plugins: Config['plugins'] = [ ] ``` -### addPolicy -Register AdonisJS bouncer policy to the list of `policies` object exported from the `app/policies/main.ts` file. +### addPolicies +Register AdonisJS bouncer policies to the list of `policies` object exported from the `app/policies/main.ts` file. > [!IMPORTANT] > This codemod expects the `app/policies/main.ts` file to exist and must export a `policies` object from it. @@ -400,7 +400,7 @@ Register AdonisJS bouncer policy to the list of `policies` object exported from const transformer = new CodeTransformer(appRoot) try { - await transformer.addPolicy([ + await transformer.addPolicies([ { name: 'PostPolicy', path: '#policies/post_policy' From 126275445e7f4864be2a3df9385a0d51c3d14387 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Mon, 25 Dec 2023 17:55:22 +0530 Subject: [PATCH 127/131] chore(release): 7.0.0-1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8e26dd2..eabdc70 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@adonisjs/assembler", "description": "Provides utilities to run AdonisJS development server and build project for production", - "version": "7.0.0-0", + "version": "7.0.0-1", "engines": { "node": ">=18.16.0" }, From 7dad0d4070c3acf5c2d2f5b5b241f8bf9eafdd00 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Mon, 8 Jan 2024 06:15:25 +0530 Subject: [PATCH 128/131] chore: update dependencies --- package.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index eabdc70..dd7d182 100644 --- a/package.json +++ b/package.json @@ -34,21 +34,21 @@ "quick:test": "cross-env NODE_DEBUG=adonisjs:assembler node --enable-source-maps --loader=ts-node/esm bin/test.ts" }, "devDependencies": { - "@adonisjs/application": "^8.0.0-3", - "@adonisjs/eslint-config": "^1.2.0", - "@adonisjs/prettier-config": "^1.2.0", - "@adonisjs/tsconfig": "^1.2.0", - "@commitlint/cli": "^18.4.3", - "@commitlint/config-conventional": "^18.4.3", + "@adonisjs/application": "^8.0.0", + "@adonisjs/eslint-config": "^1.2.1", + "@adonisjs/prettier-config": "^1.2.1", + "@adonisjs/tsconfig": "^1.2.1", + "@commitlint/cli": "^18.4.4", + "@commitlint/config-conventional": "^18.4.4", "@japa/assert": "^2.1.0", "@japa/file-system": "^2.1.0", "@japa/runner": "^3.1.1", "@japa/snapshot": "^2.0.4", - "@swc/core": "^1.3.101", - "@types/node": "^20.10.5", + "@swc/core": "^1.3.102", + "@types/node": "^20.10.7", "@types/picomatch": "^2.3.3", "@types/pretty-hrtime": "^1.0.3", - "c8": "^8.0.1", + "c8": "^9.0.0", "cross-env": "^7.0.3", "dedent": "^1.5.1", "del-cli": "^5.0.0", @@ -63,10 +63,10 @@ "typescript": "^5.3.3" }, "dependencies": { - "@adonisjs/env": "^4.2.0-7", + "@adonisjs/env": "^5.0.0", "@antfu/install-pkg": "^0.3.1", "@poppinss/chokidar-ts": "^4.1.3", - "@poppinss/cliui": "^6.2.3", + "@poppinss/cliui": "^6.3.0", "cpy": "^11.0.0", "execa": "^8.0.1", "fast-glob": "^3.3.2", From b4ed967e6c17eff5f1768b75cce1d76e50d8d973 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Mon, 8 Jan 2024 06:17:21 +0530 Subject: [PATCH 129/131] chore: bundle types.ts file via tsup as well --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index dd7d182..5270410 100644 --- a/package.json +++ b/package.json @@ -133,6 +133,7 @@ "tsup": { "entry": [ "./index.ts", + "./src/types.ts", "./src/code_transformer/main.ts" ], "outDir": "./build", From 831d361747fa17661da2773939d41e4a4adf4745 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Mon, 8 Jan 2024 06:19:20 +0530 Subject: [PATCH 130/131] refactor: remove usage --expiremental-meta-resolve flag --- src/helpers.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/helpers.ts b/src/helpers.ts index 943c578..969da8d 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -29,10 +29,6 @@ import debug from './debug.js' const DEFAULT_NODE_ARGS = [ // Use ts-node/esm loader. The project must install it '--loader=ts-node/esm', - // Disable annoying warnings - '--no-warnings', - // Enable expiremental meta resolve for cases where someone uses magic import string - '--experimental-import-meta-resolve', // Enable source maps, since TSNode source maps are broken '--enable-source-maps', ] From a02eaf59a59e08af8ef5579f6754f2b1e49a905c Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Mon, 8 Jan 2024 06:24:46 +0530 Subject: [PATCH 131/131] test: fix failing tests --- tests/run.spec.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/tests/run.spec.ts b/tests/run.spec.ts index 413422f..0a056b4 100644 --- a/tests/run.spec.ts +++ b/tests/run.spec.ts @@ -68,13 +68,7 @@ test.group('Child process', () => { assert.equal(childProcess.exitCode, 0) assert.deepEqual(payload, { - args: [ - '--loader=ts-node/esm', - '--no-warnings', - '--experimental-import-meta-resolve', - '--enable-source-maps', - '--throw-deprecation', - ], + args: ['--loader=ts-node/esm', '--enable-source-maps', '--throw-deprecation'], }) })